Skip to content

Commit

Permalink
refactor: external-link-resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
Fog3211 committed Sep 18, 2024
1 parent 944d31b commit 4afa624
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 87 deletions.
25 changes: 25 additions & 0 deletions src/@types/tampermonkey.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
declare namespace TampermonkeyTypes {
interface GMXMLHttpRequestDetails {
method?: "GET" | "POST" | "HEAD";
url: string;
headers?: { [key: string]: string };
data?: string;
onload?: (response: GMXMLHttpRequestResponse) => void;
onerror?: (response: GMXMLHttpRequestResponse) => void;
onabort?: (response: GMXMLHttpRequestResponse) => void;
ontimeout?: (response: GMXMLHttpRequestResponse) => void;
}

interface GMXMLHttpRequestResponse {
responseText: string;
readyState: number;
status: number;
statusText: string;
responseHeaders: string;
finalUrl: string;
}

function GM_xmlhttpRequest(details: GMXMLHttpRequestDetails): void;
}

declare function GM_xmlhttpRequest(details: TampermonkeyTypes.GMXMLHttpRequestDetails): void;
81 changes: 0 additions & 81 deletions src/convert-link-to-safty.ts

This file was deleted.

127 changes: 127 additions & 0 deletions src/external-link-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// ==UserScript==
// @name Universal External Link Direct Redirect
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description Process external links on various websites to enable direct redirection to target sites
// @match https://juejin.cn/*
// @match https://link.juejin.cn/*
// @match https://segmentfault.com/*
// @match https://link.segmentfault.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==

interface SiteConfig {
directMatch: string;
linkSelector: string;
extractTarget: (url: URL) => Promise<string | null> | string | null;
}

interface SiteConfigs {
[key: string]: SiteConfig;
}

(function () {
"use strict";

const siteConfigs: SiteConfigs = {
"juejin": {
directMatch: "link.juejin.cn",
linkSelector: "a[href^=\"https://link.juejin.cn\"]",
extractTarget: async (url: URL): Promise<string | null> => {
const params = new URLSearchParams(url.search);
const target = params.get("target");
if (target) {
if (target.startsWith("https://link.juejin.cn")) {
return siteConfigs["juejin"].extractTarget(new URL(target));
}
return decodeURIComponent(target);
}
return null;
}
},
"segmentfault": {
directMatch: "link.segmentfault.com",
linkSelector: "a[href^=\"https://link.segmentfault.com\"]",
extractTarget: async (url: URL): Promise<string> => {
// TODO: Optimize SegmentFault link processing logic, consider more efficient methods
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: url.href,
headers: {
"Referer": "https://segmentfault.com",
},
onload: function (response: TampermonkeyTypes.GMXMLHttpRequestResponse) {
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, "text/html");
const dataUrl = doc.body.getAttribute("data-url");
resolve(dataUrl || url.href);
},
onerror: function () {
resolve(url.href);
}
});
});
}
},
"csdn": {
directMatch: "link.csdn.net",
linkSelector: "a[href^=\"https://link.csdn.net/\"]",
extractTarget: (url: URL): string | null => {
const params = new URLSearchParams(url.search);
return params.get("target");
}
}
};

async function processRule(config: SiteConfig): Promise<void> {
if (window.location.hostname === config.directMatch) {
let targetUrl = await config.extractTarget(new URL(window.location.href));
while (targetUrl && targetUrl.startsWith("https://link.juejin.cn")) {
// Keep resolving until we get the final non-Juejin link
targetUrl = await siteConfigs["juejin"].extractTarget(new URL(targetUrl));
}
if (targetUrl) {
window.location.href = targetUrl;
}
return;
}

await replaceLinks(config);
}

async function replaceLinks(config: SiteConfig): Promise<void> {
const links = document.querySelectorAll<HTMLAnchorElement>(config.linkSelector);
for (const link of links) {
const href = link.getAttribute("href");
if (href) {
const targetUrl = await config.extractTarget(new URL(href));
if (targetUrl) {
link.href = decodeURIComponent(targetUrl);
link.target = "_blank";
link.rel = "noopener noreferrer";
}
}
}
}

function observeDOMChanges(config: SiteConfig): void {
const observer = new MutationObserver(() => replaceLinks(config));
observer.observe(document.body, { childList: true, subtree: true });
}

async function init(): Promise<void> {
const currentSite = Object.keys(siteConfigs).find(site =>
window.location.hostname.includes(site) ||
window.location.hostname.includes(siteConfigs[site].directMatch)
);

if (currentSite) {
const config = siteConfigs[currentSite];
await processRule(config);
observeDOMChanges(config);
}
}

init();
})();
10 changes: 4 additions & 6 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"strict": true,
"module": "commonjs",
"skipLibCheck": true,
"target": "es5",
"target": "esnext",
"outDir": "dist",
"baseUrl": "./",
"paths": {
Expand All @@ -21,13 +21,11 @@
"lib": [
"dom",
"dom.iterable",
"es2015",
"esnext"
]
],
"typeRoots": ["./src/@types", "./node_modules/@types"]
},
"include": [
"src"
],
"include": ["src/**/*"],
"exclude": [
"node_modules",
"build",
Expand Down

0 comments on commit 4afa624

Please sign in to comment.