Plugin

ClearURLs

Automatically removes tracking elements from URLs you send

Privacy Utility
index.ts
Download

Source

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