Plugin

AnonymiseFileNames

Anonymise uploaded file names

Privacy Utility
index.tsx
Download

Source

src/plugins/anonymiseFileNames/index.tsx
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import { definePluginSettings } from "@api/Settings";
8import ErrorBoundary from "@components/ErrorBoundary";
9import { Devs } from "@utils/constants";
10import definePlugin, { OptionType } from "@utils/types";
11import { CloudUpload } from "@vencord/discord-types";
12import { findByCodeLazy } from "@webpack";
13import { useState } from "@webpack/common";
14
15const ActionBarIcon = findByCodeLazy("Children.map", "isValidElement", "dangerous:");
16
17const enum Methods {
18 Random,
19 Consistent,
20 Timestamp,
21}
22
23const ANONYMISE_UPLOAD_SYMBOL = Symbol("vcAnonymise");
24const tarExtMatcher = /\.tar\.\w+$/;
25
26const settings = definePluginSettings({
27 anonymiseByDefault: {
28 description: "Whether to anonymise file names by default",
29 type: OptionType.BOOLEAN,
30 default: true,
31 },
32 method: {
33 description: "Anonymising method",
34 type: OptionType.SELECT,
35 options: [
36 { label: "Random Characters", value: Methods.Random, default: true },
37 { label: "Consistent", value: Methods.Consistent },
38 { label: "Timestamp", value: Methods.Timestamp },
39 ],
40 },
41 randomisedLength: {
42 description: "Random characters length",
43 type: OptionType.NUMBER,
44 default: 7
45 },
46 consistent: {
47 description: "Consistent filename",
48 type: OptionType.STRING,
49 default: "image"
50 },
51}, {
52 randomisedLength: {
53 disabled() { return this.store.method !== Methods.Random; },
54 },
55 consistent: {
56 disabled() { return this.store.method !== Methods.Consistent; },
57 }
58});
59
60export default definePlugin({
61 name: "AnonymiseFileNames",
62 authors: [Devs.fawn],
63 description: "Anonymise uploaded file names",
64 tags: ["Privacy", "Utility"],
65 settings,
66
67 patches: [
68 {
69 find: "async uploadFiles(",
70 replacement: [
71 {
72 match: /async uploadFiles\((\i)\){/,
73 replace: "$&$1.forEach($self.anonymise);"
74 }
75 ],
76 },
77 {
78 find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}",
79 replacement: {
80 match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/,
81 replace: "arguments[0].canEdit!==false?$self.AnonymiseUploadButton(arguments[0]):null,"
82 },
83 },
84 ],
85
86 AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: CloudUpload; }) => {
87 const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault);
88
89 function onToggleAnonymise() {
90 upload[ANONYMISE_UPLOAD_SYMBOL] = !anonymise;
91 setAnonymise(!anonymise);
92 }
93
94 return (
95 <ActionBarIcon
96 tooltip={anonymise ? "Using anonymous file name" : "Using normal file name"}
97 onClick={onToggleAnonymise}
98 >
99 {anonymise
100 ? <svg xmlns="http:class="ts-cmt">//www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M17.06 13C15.2 13 13.64 14.33 13.24 16.1C12.29 15.69 11.42 15.8 10.76 16.09C10.35 14.31 8.79 13 6.94 13C4.77 13 3 14.79 3 17C3 19.21 4.77 21 6.94 21C9 21 10.68 19.38 10.84 17.32C11.18 17.08 12.07 16.63 13.16 17.34C13.34 19.39 15 21 17.06 21C19.23 21 21 19.21 21 17C21 14.79 19.23 13 17.06 13M6.94 19.86C5.38 19.86 4.13 18.58 4.13 17S5.39 14.14 6.94 14.14C8.5 14.14 9.75 15.42 9.75 17S8.5 19.86 6.94 19.86M17.06 19.86C15.5 19.86 14.25 18.58 14.25 17S15.5 14.14 17.06 14.14C18.62 14.14 19.88 15.42 19.88 17S18.61 19.86 17.06 19.86M22 10.5H2V12H22V10.5M15.53 2.63C15.31 2.14 14.75 1.88 14.22 2.05L12 2.79L9.77 2.05L9.72 2.04C9.19 1.89 8.63 2.17 8.43 2.68L6 9H18L15.56 2.68L15.53 2.63Z" /></svg>
101 : <svg xmlns="http:class="ts-cmt">//www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ transform: "scale(-1,1)" }}><path fill="currentColor" d="M22.11 21.46L2.39 1.73L1.11 3L6.31 8.2L6 9H7.11L8.61 10.5H2V12H10.11L13.5 15.37C13.38 15.61 13.3 15.85 13.24 16.1C12.29 15.69 11.41 15.8 10.76 16.09C10.35 14.31 8.79 13 6.94 13C4.77 13 3 14.79 3 17C3 19.21 4.77 21 6.94 21C9 21 10.68 19.38 10.84 17.32C11.18 17.08 12.07 16.63 13.16 17.34C13.34 19.39 15 21 17.06 21C17.66 21 18.22 20.86 18.72 20.61L20.84 22.73L22.11 21.46M6.94 19.86C5.38 19.86 4.13 18.58 4.13 17C4.13 15.42 5.39 14.14 6.94 14.14C8.5 14.14 9.75 15.42 9.75 17C9.75 18.58 8.5 19.86 6.94 19.86M17.06 19.86C15.5 19.86 14.25 18.58 14.25 17C14.25 16.74 14.29 16.5 14.36 16.25L17.84 19.73C17.59 19.81 17.34 19.86 17.06 19.86M22 12H15.2L13.7 10.5H22V12M17.06 13C19.23 13 21 14.79 21 17C21 17.25 20.97 17.5 20.93 17.73L19.84 16.64C19.68 15.34 18.66 14.32 17.38 14.17L16.29 13.09C16.54 13.03 16.8 13 17.06 13M12.2 9L7.72 4.5L8.43 2.68C8.63 2.17 9.19 1.89 9.72 2.04L9.77 2.05L12 2.79L14.22 2.05C14.75 1.88 15.32 2.14 15.54 2.63L15.56 2.68L18 9H12.2Z" /></svg>
102 }
103 </ActionBarIcon>
104 );
105 }, { noop: true }),
106
107 anonymise(upload: CloudUpload) {
108 if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) {
109 return;
110 }
111
112 const originalFileName = upload.filename;
113 const tarMatch = tarExtMatcher.exec(originalFileName);
114 const extIdx = tarMatch?.index ?? originalFileName.lastIndexOf(".");
115 const ext = extIdx !== -1 ? originalFileName.slice(extIdx) : "";
116
117 const newFilename = (() => {
118 switch (settings.store.method) {
119 case Methods.Random:
120 const chars = "0123456789bdfhjkmnpqrstvwxz";
121 return Array.from(
122 { length: settings.store.randomisedLength },
123 () => chars[Math.floor(Math.random() * chars.length)]
124 ).join("") + ext;
125 case Methods.Consistent:
126 return settings.store.consistent + ext;
127 case Methods.Timestamp:
128 return Date.now() + ext;
129 }
130 })();
131
132 upload.filename = newFilename;
133 }
134});
135