Plugin

NoTrack

Disable Discord's tracking (analytics/'science'), metrics and Sentry crash reporting

index.ts
Download

Source

src/plugins/_core/noTrack/index.ts
1import { definePluginSettings } from "@api/Settings";
2import { Devs } from "@utils/constants";
3import { Logger } from "@utils/Logger";
4import definePlugin, { OptionType, StartAt } from "@utils/types";
5import { WebpackRequire } from "@vencord/discord-types/webpack";
6
7const settings = definePluginSettings({
8 disableAnalytics: {
9 type: OptionType.BOOLEAN,
10 description: "Disable Discord's tracking (analytics/'science')",
11 default: true,
12 restartNeeded: true
13 }
14});
15
16export default definePlugin({
17 name: "NoTrack",
18 description: "Disable Discord's tracking (analytics/'science'), metrics and Sentry crash reporting",
19 authors: [Devs.Cyn, Devs.Ven, Devs.Nuckyz, Devs.Arrow],
20 required: true,
21
22 settings,
23
24 patches: [
25 {
26 find: "AnalyticsActionHandlers.handle",
27 predicate: () => settings.store.disableAnalytics,
28 replacement: {
29 match: /\(0,\i\.analyticsTrackingStoreMaker\)/,
30 replace: "(()=>{})",
31 },
32 },
33 {
34 find: ".METRICS_V2",
35 replacement: [
36 {
37 match: /this\._intervalId=/,
38 replace: "this._intervalId=void 0&&"
39 },
40 {
41 match: /(?:increment|distribution)\(\i(?:,\i)?\){/g,
42 replace: "$&return;"
43 }
44 ]
45 },
46 {
47 find: ".BetterDiscord||null!=",
48 replacement: {
49 // Make hasClientMods return false
50 match: /(?=let \i=window;)/,
51 replace: "return false;"
52 }
53 }
54 ],
55
56 // The TRACK event takes an optional `resolve` property that is called when the tracking event was submitted to the server.
57 // A few spots in Discord await this callback before continuing (most notably the Voice Debug Logging toggle).
58 // Since we NOOP the AnalyticsActionHandlers module, there is no handler for the TRACK event, so we have to handle it ourselves
59 flux: {
60 TRACK(event) {
61 event?.resolve?.();
62 }
63 },
64
65 startAt: StartAt.Init,
66 start() {
67 // Sentry is initialized in its own WebpackInstance.
68 // It has everything it needs preloaded, so, it doesn't include any chunk loading functionality.
69 // Because of that, its WebpackInstance doesnt export wreq.m or wreq.c
70
71 // To circuvent this and disable Sentry we are gonna hook when wreq.d of its WebpackInstance is set.
72 // When that happens we are gonna forcefully throw an error and abort everything.
73 Object.defineProperty(Function.prototype, "d", {
74 configurable: true,
75
76 set(this: WebpackRequire, esmDeclareFunc: WebpackRequire["d"]) {
77 Object.defineProperty(this, "d", {
78 value: esmDeclareFunc,
79 configurable: true,
80 enumerable: true,
81 writable: true
82 });
83
84 // Ensure this is most likely the Sentry WebpackInstance.
85 // Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
86 const { stack } = new Error();
87 if (this.c != null || !stack?.includes("http") || !String(this).includes("exports:{}")) {
88 return;
89 }
90
91 const assetPath = stack.match(/http.+?(?=:\d+?:\d+?$)/m)?.[0];
92 if (!assetPath) {
93 return;
94 }
95
96 const srcRequest = new XMLHttpRequest();
97 srcRequest.open("GET", assetPath, false);
98 srcRequest.send();
99
100 // Final condition to see if this is the Sentry WebpackInstance
101 // This is matching window.DiscordSentry=, but without `window` to avoid issues on some proxies
102 if (!srcRequest.responseText.includes(".DiscordSentry=")) {
103 return;
104 }
105
106 new Logger("NoTrack", "#8caaee").info("Disabling Sentry by erroring its WebpackInstance");
107
108 Reflect.deleteProperty(Function.prototype, "d");
109 Reflect.deleteProperty(window, "DiscordSentry");
110
111 throw new Error("Sentry successfully disabled");
112 }
113 });
114
115 Object.defineProperty(window, "DiscordSentry", {
116 configurable: true,
117
118 set() {
119 new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry");
120
121 Reflect.deleteProperty(Function.prototype, "d");
122 Reflect.deleteProperty(window, "DiscordSentry");
123 }
124 });
125 }
126});
127