diff --git a/src/core/plugins/notrack/index.ts b/src/core/plugins/notrack/index.ts index 8459303..58865de 100644 --- a/src/core/plugins/notrack/index.ts +++ b/src/core/plugins/notrack/index.ts @@ -1,230 +1,36 @@ +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"; -// 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 - } - }; -} +const patches = [ + network(), + console(), + miscellanous(), + // Sentry causes issues + // sentry(), +]; export default defineCorePlugin({ - manifest: { - id: "bunny.notrack", - version: "1.0.0", - type: "plugin", - spec: 3, - main: "", - display: { - name: "NoTrack", - description: "Disables Discord's telemetry", - authors: [{ name: "maisymoe" }], + manifest: { + id: "vendetta.notrack", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "NoTrack", + description: "Disables Discord's telemetry", + authors: [{ name: "maisymoe" }] + } }, - }, - - 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"); - }, + start() { + network(), + console(), + miscellanous(), + sentry() + } }); + diff --git a/src/core/plugins/notrack/lib/console.ts b/src/core/plugins/notrack/lib/console.ts new file mode 100644 index 0000000..7a69471 --- /dev/null +++ b/src/core/plugins/notrack/lib/console.ts @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..b6a6093 --- /dev/null +++ b/src/core/plugins/notrack/lib/miscellanous.ts @@ -0,0 +1,33 @@ +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 new file mode 100644 index 0000000..68f762e --- /dev/null +++ b/src/core/plugins/notrack/lib/network.ts @@ -0,0 +1,12 @@ +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 new file mode 100644 index 0000000..5b01b7e --- /dev/null +++ b/src/core/plugins/notrack/lib/sentry.ts @@ -0,0 +1,26 @@ +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 new file mode 100644 index 0000000..55f871f --- /dev/null +++ b/src/core/plugins/notrack/lib/utils.ts @@ -0,0 +1,3 @@ +import { instead } from "@lib/api/patcher"; + +export const noop = (prop: string, parent: any) => instead(prop, parent, () => {});