Plugin

ClearURLs

Automatically removes tracking elements from URLs you send

Privacy Utility
index.ts
Download

Source

src/plugins/clearURLs/index.ts
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import {
8 MessageObject
9} from "@api/MessageEvents";
10import { Devs } from "@utils/constants";
11import definePlugin from "@utils/types";
12
13const CLEAR_URLS_JSON_URL = "https:class="ts-cmt">//raw.githubusercontent.com/ClearURLs/Rules/master/data.min.json";
14
15interface Provider {
16 urlPattern: string;
17 completeProvider: boolean;
18 rules?: string[];
19 rawRules?: string[];
20 referralMarketing?: string[];
21 exceptions?: string[];
22 redirections?: string[];
23 forceRedirection?: boolean;
24}
25
26interface ClearUrlsData {
27 providers: Record<string, Provider>;
28}
29
30interface RuleSet {
31 name: string;
32 urlPattern: RegExp;
33 rules?: RegExp[];
34 rawRules?: RegExp[];
35 exceptions?: RegExp[];
36}
37
38export default definePlugin({
39 name: "ClearURLs",
40 description: "Automatically removes tracking elements from URLs you send",
41 tags: ["Privacy", "Utility"],
42 authors: [Devs.adryd, Devs.thororen],
43
44 rules: [] as RuleSet[],
45
46 async start() {
47 await this.createRules();
48 },
49
50 stop() {
51 this.rules = [];
52 },
53
54 onBeforeMessageSend(_, msg) {
55 return this.cleanMessage(msg);
56 },
57
58 onBeforeMessageEdit(_cid, _mid, msg) {
59 return this.cleanMessage(msg);
60 },
61
62 async createRules() {
63 const res = await fetch(CLEAR_URLS_JSON_URL)
64 .then(res => res.json()) as ClearUrlsData;
65
66 this.rules = [];
67
68 for (const [name, provider] of Object.entries(res.providers)) {
69 const urlPattern = new RegExp(provider.urlPattern, "i");
70
71 const rules = provider.rules?.map(rule => new RegExp(rule, "i"));
72 const rawRules = provider.rawRules?.map(rule => new RegExp(rule, "i"));
73 const exceptions = provider.exceptions?.map(ex => new RegExp(ex, "i"));
74
75 this.rules.push({
76 name,
77 urlPattern,
78 rules,
79 rawRules,
80 exceptions,
81 });
82 }
83 },
84
85 replacer(match: string) {
86 // Parse URL without throwing errors
87 try {
88 var url = new URL(match);
89 } catch (error) {
90 // Don't modify anything if we can't parse the URL
91 return match;
92 }
93
94 // Cheap way to check if there are any search params
95 if (url.searchParams.entries().next().done) return match;
96
97 // Check rules for each provider that matches
98 this.rules.forEach(({ urlPattern, exceptions, rawRules, rules }) => {
99 if (!urlPattern.test(url.href) || exceptions?.some(ex => ex.test(url.href))) return;
100
101 const toDelete: string[] = [];
102
103 if (rules) {
104 // Add matched params to delete list
105 url.searchParams.forEach((_, param) => {
106 if (rules.some(rule => rule.test(param))) {
107 toDelete.push(param);
108 }
109 });
110 }
111
112 // Delete matched params from list
113 toDelete.forEach(param => url.searchParams.delete(param));
114
115 // Match and remove any raw rules
116 let cleanedUrl = url.href;
117 rawRules?.forEach(rawRule => {
118 cleanedUrl = cleanedUrl.replace(rawRule, "");
119 });
120 url = new URL(cleanedUrl);
121 });
122
123 return url.toString();
124 },
125
126 cleanMessage(msg: MessageObject) {
127 // Only run on messages that contain URLs
128 if (/http(s)?:\/\class="ts-cmt">//.test(msg.content)) {
129 msg.content = msg.content.replace(
130 /(https?:\/\/[^\s<]+[^<.,:;"&#039;>)|\]\s])/g,
131 match => this.replacer(match)
132 );
133 }
134 },
135});
136