Plugin
ClearURLs
Automatically removes tracking elements from URLs you send
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import {8
MessageObject9
} from "@api/MessageEvents";10
import { Devs } from "@utils/constants";11
import definePlugin from "@utils/types";12
13
const CLEAR_URLS_JSON_URL = "https:class="ts-cmt">//raw.githubusercontent.com/ClearURLs/Rules/master/data.min.json";14
15
interface 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
26
interface ClearUrlsData {27
providers: Record<string, Provider>;28
}29
30
interface RuleSet {31
name: string;32
urlPattern: RegExp;33
rules?: RegExp[];34
rawRules?: RegExp[];35
exceptions?: RegExp[];36
}37
38
export 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 errors87
try {88
var url = new URL(match);89
} catch (error) {90
// Don't modify anything if we can't parse the URL91
return match;92
}93
94
// Cheap way to check if there are any search params95
if (url.searchParams.entries().next().done) return match;96
97
// Check rules for each provider that matches98
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 list105
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 list113
toDelete.forEach(param => url.searchParams.delete(param));114
115
// Match and remove any raw rules116
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 URLs128
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