aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/clearURLs/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/clearURLs/index.ts')
-rw-r--r--src/plugins/clearURLs/index.ts134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/plugins/clearURLs/index.ts b/src/plugins/clearURLs/index.ts
new file mode 100644
index 0000000..abdeefd
--- /dev/null
+++ b/src/plugins/clearURLs/index.ts
@@ -0,0 +1,134 @@
+import { defaultRules } from "./defaultRules";
+import {
+ addPreSendListener,
+ addPreEditListener,
+ MessageObject,
+ removePreSendListener,
+ removePreEditListener,
+} from "../../api/MessageEvents";
+import definePlugin from "../../utils/types";
+
+// From lodash
+const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
+const reHasRegExpChar = RegExp(reRegExpChar.source);
+
+export default definePlugin({
+ name: "clearURLs",
+ description: "Removes tracking garbage from URLs",
+ authors: [
+ {
+ name: "adryd",
+ id: 0n,
+ },
+ ],
+ dependencies: ["MessageEventsAPI"],
+
+ escapeRegExp(str: string) {
+ return (str && reHasRegExpChar.test(str))
+ ? str.replace(reRegExpChar, "\\$&")
+ : (str || "");
+ },
+
+ createRules() {
+ // Can be extended upon once user configs are available
+ // Eg. (useDefaultRules: boolean, customRules: Array[string])
+ const rules = defaultRules;
+
+ this.universalRules = new Set();
+ this.rulesByHost = new Map();
+ this.hostRules = new Map();
+
+ for (const rule of rules) {
+ const splitRule = rule.split("@");
+ const paramRule = new RegExp(
+ "^" +
+ this.escapeRegExp(splitRule[0]).replace(/\\\*/, ".+?") +
+ "$"
+ );
+
+ if (!splitRule[1]) {
+ this.universalRules.add(paramRule);
+ continue;
+ }
+ const hostRule = new RegExp(
+ "^(www\\.)?" +
+ this.escapeRegExp(splitRule[1])
+ .replace(/\\\./, "\\.")
+ .replace(/^\\\*\\\./, "(.+?\\.)?")
+ .replace(/\\\*/, ".+?") +
+ "$"
+ );
+ const hostRuleIndex = hostRule.toString();
+
+ this.hostRules.set(hostRuleIndex, hostRule);
+ if (this.rulesByHost.get(hostRuleIndex) == null) {
+ this.rulesByHost.set(hostRuleIndex, new Set());
+ }
+ this.rulesByHost.get(hostRuleIndex).add(paramRule);
+ }
+ },
+
+ removeParam(rule: string | RegExp, param: string, parent: URLSearchParams) {
+ if (param === rule || rule instanceof RegExp && rule.test(param)) {
+ parent.delete(param);
+ }
+ },
+
+ 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) {
+ // If there are none, we don't need to modify anything
+ return match;
+ }
+
+ // Check all universal rules
+ this.universalRules.forEach((rule) => {
+ url.searchParams.forEach((_value, param, parent) => {
+ this.removeParam(rule, param, parent);
+ });
+ });
+
+ // Check rules for each hosts that match
+ this.hostRules.forEach((regex, hostRuleName) => {
+ if (!regex.test(url.hostname)) return;
+ this.rulesByHost.get(hostRuleName).forEach((rule) => {
+ url.searchParams.forEach((_value, param, parent) => {
+ this.removeParam(rule, param, parent);
+ });
+ });
+ });
+
+ return url.toString();
+ },
+
+ onSend(msg: MessageObject) {
+ // Only run on messages that contain URLs
+ if (msg.content.match(/http(s)?:\/\//)) {
+ msg.content = msg.content.replace(
+ /(https?:\/\/[^\s<]+[^<.,:;"'>)|\]\s])/g,
+ (match) => this.replacer(match)
+ );
+ }
+ },
+
+ start() {
+ this.createRules();
+ this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
+ this.preEdit = addPreEditListener((_cid, _mid, msg) =>
+ this.onSend(msg)
+ );
+ },
+
+ stop() {
+ removePreSendListener(this.preSend);
+ removePreEditListener(this.preEdit);
+ },
+});