diff --git a/src/core/plugins/notrack/index.ts b/src/core/plugins/notrack/index.ts index 58865de..8459303 100644 --- a/src/core/plugins/notrack/index.ts +++ b/src/core/plugins/notrack/index.ts @@ -1,36 +1,230 @@ -import network from "./lib/network"; -import console from "./lib/console"; -import miscellanous from "./lib/miscellanous"; -import sentry from "./lib/sentry"; import { defineCorePlugin } from ".."; +import { findByProps } from "@metro"; +import { instead } from "@lib/api/patcher"; import { logger } from "@lib/utils/logger"; -const patches = [ - network(), - console(), - miscellanous(), - // Sentry causes issues - // sentry(), -]; +// Type for functions that need to be cleaned up when plugin is disabled +type PatchCleanupFn = () => void; + +// Store all active patches for cleanup +let patches: PatchCleanupFn[] = []; + +// AnalyticsUtils and other tracking utilities +const AnalyticsUtils = findByProps("AnalyticsActionHandlers"); +const SuperPropUtils = findByProps("encodeProperties", "track"); +const VoiceStateUtils = findByProps("getVoiceStateMetadata"); +const CrashReportUtils = findByProps("submitLiveCrashReport"); +const MetricsUtils = findByProps("_metrics"); + +// Sentry interface definition to avoid TypeScript errors +interface SentryClient { + getOptions: () => { enabled: boolean }; + close: () => void; + open: () => void; +} + +interface SentryScope { + clear: () => void; +} + +interface SentryHub { + getClient: () => SentryClient | undefined; + getStackTop: () => { scope: SentryScope }; + getScope: () => SentryScope; + addBreadcrumb?: (breadcrumb: unknown) => void; +} + +interface GlobalSentry { + hub?: SentryHub; +} + +// Get Sentry objects with proper type checking +const sentryGlobal = (globalThis as unknown as { __SENTRY__?: GlobalSentry }) + .__SENTRY__; +const sentryHub = sentryGlobal?.hub; +const sentryClient = sentryHub?.getClient(); + +// Sentry related objects with type safety +const Sentry = { + initializer: findByProps("initSentry"), + main: sentryHub, + client: sentryClient, +}; + +// Utility function to create no-operation patches +const noop = (prop: string, parent: Record): PatchCleanupFn => { + try { + return instead(prop, parent, () => undefined); + } catch (e) { + return () => false; + } +}; + +// Network tracking prevention +function patchNetwork(): PatchCleanupFn { + // Analytics and tracking endpoints to block + const analyticsTest = + /client-analytics\.braintreegateway\.com|discord\.com\/api\/v9\/(science|track)|app\.adjust\..*|.*\.ingest\.sentry\.io/; + + try { + const unpatch = instead( + "send", + XMLHttpRequest.prototype, + function ( + this: XMLHttpRequest & { __sentry_xhr__?: { url: string } }, + args: unknown[], + orig: Function, + ) { + if ( + this.__sentry_xhr__?.url && + analyticsTest.test(this.__sentry_xhr__.url) + ) { + return undefined; + } + return orig.apply(this, args); + }, + ); + return unpatch; + } catch (e) { + return () => false; + } +} + +// Console patching to remove Sentry wrappers +function patchConsole(): PatchCleanupFn { + type ConsoleFunction = Function & { __sentry_original__?: Function }; + type ConsoleFunctions = Record; + + const sentrified: ConsoleFunctions = {}; + + try { + // We need to use Object.keys because TypeScript doesn't allow indexing Console with strings + (Object.keys(console) as Array).forEach((key) => { + const consoleFunc = (console as unknown as ConsoleFunctions)[key]; + if (consoleFunc) { + sentrified[key] = consoleFunc; + const originalFunc = consoleFunc.__sentry_original__; + (console as unknown as ConsoleFunctions)[key] = + originalFunc ?? consoleFunc; + } + }); + } catch (e) { + logger.log("Failed to de-sentrify console functions!", e); + } + + return () => { + Object.keys(sentrified).forEach((key) => { + if (sentrified[key]) { + (console as unknown as ConsoleFunctions)[key] = sentrified[key]; + } + }); + }; +} + +// Miscellaneous tracking functions +function patchMiscellaneous(): PatchCleanupFn { + const miscPatches = [ + // Global analytics utilities + AnalyticsUtils?.AnalyticsActionHandlers && + noop("handleTrack", AnalyticsUtils.AnalyticsActionHandlers), + AnalyticsUtils?.AnalyticsActionHandlers && + noop("handleFingerprint", AnalyticsUtils.AnalyticsActionHandlers), + + // Super properties tracking + SuperPropUtils && noop("track", SuperPropUtils), + + // Voice state metadata tracking + VoiceStateUtils && noop("trackWithMetadata", VoiceStateUtils), + + // Crash reporter + CrashReportUtils && noop("submitLiveCrashReport", CrashReportUtils), + + // Metrics + MetricsUtils?._metrics && noop("push", MetricsUtils._metrics), + ].filter(Boolean) as PatchCleanupFn[]; + + return () => miscPatches.forEach((p) => p()); +} + +// Sentry error reporting prevention +function patchSentry(): PatchCleanupFn { + const sentryPatches: PatchCleanupFn[] = []; + + // Add patches if Sentry components exist + if (Sentry.initializer) { + sentryPatches.push(noop("initSentry", Sentry.initializer)); + } + + if (Sentry.main && Sentry.main.addBreadcrumb) { + sentryPatches.push(noop("addBreadcrumb", Sentry.main)); + } + + // Disable Sentry client + if (Sentry.client) { + try { + Sentry.client.getOptions().enabled = false; + Sentry.client.close(); + + if (Sentry.main) { + if (Sentry.main.getStackTop) { + Sentry.main.getStackTop().scope.clear(); + } + + if (Sentry.main.getScope) { + Sentry.main.getScope().clear(); + } + } + } catch (e) { + // Silent fail + } + } + + return () => { + try { + sentryPatches.forEach((p) => p()); + + // Re-enable Sentry client on cleanup + if (Sentry.client) { + Sentry.client.getOptions().enabled = true; + Sentry.client.open(); + } + } catch (e) { + // Silent fail + } + }; +} export default defineCorePlugin({ - manifest: { - id: "vendetta.notrack", - version: "1.0.0", - type: "plugin", - spec: 3, - main: "", - display: { - name: "NoTrack", - description: "Disables Discord's telemetry", - authors: [{ name: "maisymoe" }] - } + manifest: { + id: "bunny.notrack", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "NoTrack", + description: "Disables Discord's telemetry", + authors: [{ name: "maisymoe" }], }, - start() { - network(), - console(), - miscellanous(), - sentry() - } -}); + }, + start() { + // Apply all patches + patches = [ + patchNetwork(), + patchConsole(), + patchMiscellaneous(), + patchSentry(), + ].filter(Boolean); + + logger.log("NoTrack: Enabled - all telemetry tracking disabled"); + }, + + stop() { + // Call all cleanup functions + patches.forEach((p) => p?.()); + patches = []; + + logger.log("NoTrack: Disabled - telemetry tracking restored"); + }, +}); diff --git a/src/core/plugins/notrack/lib/console.ts b/src/core/plugins/notrack/lib/console.ts deleted file mode 100644 index 7a69471..0000000 --- a/src/core/plugins/notrack/lib/console.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { logger } from "@lib/utils/logger"; - -export default () => { - const sentrified = {}; - - try { - Object.keys(console).forEach((x) => { - sentrified[x] = console[x]; - console[x] = console[x].__sentry_original__ ?? console[x]; - }); - } catch(e) { - logger.log("Failed to de-sentrify console functions!", e); - }; - - return () => Object.keys(console).forEach((x) => console[x] = sentrified[x]); -} diff --git a/src/core/plugins/notrack/lib/miscellanous.ts b/src/core/plugins/notrack/lib/miscellanous.ts deleted file mode 100644 index b6a6093..0000000 --- a/src/core/plugins/notrack/lib/miscellanous.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { findByProps } from "@metro"; -import { noop } from "./utils"; - -// Stuff in here is non-specific tracking scattered around the app - -const AnalyticsUtils = findByProps("AnalyticsActionHandlers"); -const SuperPropUtils = findByProps("encodeProperties", "track"); -const VoiceStateUtils = findByProps("getVoiceStateMetadata"); -const CrashReportUtils = findByProps("submitLiveCrashReport"); -const MetricsUtils = findByProps("_metrics"); - -export default () => { - const patches = [ - // Global analytics utilities - ...(["handleTrack", "handleFingerprint"].map((i) => noop(i, AnalyticsUtils.AnalyticsActionHandlers))), - // TODO: This is a getter - look into patching it - // noop("analyticsTrackingStoreMaker", AnalyticsUtils), - - // Super properties tracking - noop("track", SuperPropUtils), - - // Voice state metadata tracking - noop("trackWithMetadata", VoiceStateUtils), - - // Crash reporter - noop("submitLiveCrashReport", CrashReportUtils), - - // Whatever "metrics" implies - noop("push", MetricsUtils._metrics), - ]; - - return () => patches.forEach((p) => p()); -} diff --git a/src/core/plugins/notrack/lib/network.ts b/src/core/plugins/notrack/lib/network.ts deleted file mode 100644 index 68f762e..0000000 --- a/src/core/plugins/notrack/lib/network.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { instead } from "@lib/api/patcher"; - -export default () => { - // https://github.com/yellowsink/shelter-plugins/blob/3a62d1557b704f4617f72882edba826218eb8c6e/plugins/antitrack/index.js#L10-L25 - const analyticsTest = /client-analytics\.braintreegateway\.com|discord\.com\/api\/v9\/(science|track)|app\.adjust\..*|.*\.ingest\.sentry\.io/; - - return instead( - "send", - XMLHttpRequest.prototype, - function (args, orig) { if (!analyticsTest.test(this.__sentry_xhr__?.url)) return orig.apply(this, args) }, - ); -} diff --git a/src/core/plugins/notrack/lib/sentry.ts b/src/core/plugins/notrack/lib/sentry.ts deleted file mode 100644 index 5b01b7e..0000000 --- a/src/core/plugins/notrack/lib/sentry.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { findByProps } from "@metro"; -import { noop } from "./utils"; - -const Sentry = { - initializer: findByProps("initSentry"), - main: globalThis.__SENTRY__?.hub, - client: globalThis.__SENTRY__?.hub?.getClient(), -}; - -export default () => { - const patches = [ - noop("initSentry", Sentry.initializer), - noop("addBreadcrumb", Sentry.main), - ]; - - Sentry.client.getOptions().enabled = false; - Sentry.client.close(); - Sentry.main.getStackTop().scope.clear(); - Sentry.main.getScope().clear(); - - return () => { - patches.forEach((p) => p()); - Sentry.client.getOptions().enabled = true; - Sentry.client.open(); - }; -} diff --git a/src/core/plugins/notrack/lib/utils.ts b/src/core/plugins/notrack/lib/utils.ts deleted file mode 100644 index 55f871f..0000000 --- a/src/core/plugins/notrack/lib/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { instead } from "@lib/api/patcher"; - -export const noop = (prop: string, parent: any) => instead(prop, parent, () => {});