Equicord/src/plugins/clearURLs/index.ts
thororen 8eabb11125
ClearURLs: use rules from ClearURLs browser extension (#3657)
Co-authored-by: V <vendicated@riseup.net>
2025-09-09 01:35:01 +00:00

146 lines
4.3 KiB
TypeScript

/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {
MessageObject
} from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
const CLEAR_URLS_JSON_URL = "https://raw.githubusercontent.com/ClearURLs/Rules/master/data.min.json";
interface Provider {
urlPattern: string;
completeProvider: boolean;
rules?: string[];
rawRules?: string[];
referralMarketing?: string[];
exceptions?: string[];
redirections?: string[];
forceRedirection?: boolean;
}
interface ClearUrlsData {
providers: Record<string, Provider>;
}
interface RuleSet {
name: string;
urlPattern: RegExp;
rules?: RegExp[];
rawRules?: RegExp[];
exceptions?: RegExp[];
}
export default definePlugin({
name: "ClearURLs",
description: "Automatically removes tracking elements from URLs you send",
authors: [Devs.adryd, Devs.thororen],
rules: [] as RuleSet[],
async start() {
await this.createRules();
},
stop() {
this.rules = [];
},
onBeforeMessageSend(_, msg) {
return this.cleanMessage(msg);
},
onBeforeMessageEdit(_cid, _mid, msg) {
return this.cleanMessage(msg);
},
async createRules() {
const res = await fetch(CLEAR_URLS_JSON_URL)
.then(res => res.json()) as ClearUrlsData;
this.rules = [];
for (const [name, provider] of Object.entries(res.providers)) {
const urlPattern = new RegExp(provider.urlPattern, "i");
const rules = provider.rules?.map(rule => new RegExp(rule, "i"));
const rawRules = provider.rawRules?.map(rule => new RegExp(rule, "i"));
const exceptions = provider.exceptions?.map(ex => new RegExp(ex, "i"));
this.rules.push({
name,
urlPattern,
rules,
rawRules,
exceptions,
});
}
},
replacer(match: string) {
// Parse URL without throwing errors
try {
var url = new URL(match);
} catch (error) {
// Don't modify anything if we can't parse the URL
return match;
}
// Cheap way to check if there are any search params
if (url.searchParams.entries().next().done) return match;
// Check rules for each provider that matches
this.rules.forEach(({ urlPattern, exceptions, rawRules, rules }) => {
if (!urlPattern.test(url.href) || exceptions?.some(ex => ex.test(url.href))) return;
const toDelete: string[] = [];
if (rules) {
// Add matched params to delete list
url.searchParams.forEach((_, param) => {
if (rules.some(rule => rule.test(param))) {
toDelete.push(param);
}
});
}
// Delete matched params from list
toDelete.forEach(param => url.searchParams.delete(param));
// Match and remove any raw rules
let cleanedUrl = url.href;
rawRules?.forEach(rawRule => {
cleanedUrl = cleanedUrl.replace(rawRule, "");
});
url = new URL(cleanedUrl);
});
return url.toString();
},
cleanMessage(msg: MessageObject) {
// Only run on messages that contain URLs
if (/http(s)?:\/\//.test(msg.content)) {
msg.content = msg.content.replace(
/(https?:\/\/[^\s<]+[^<.,:;"'>)|\]\s])/g,
match => this.replacer(match)
);
}
},
});