Greasy Fork

Greasy Fork is available in English.

open the link directly

点击链接直接跳转

当前为 2022-03-28 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         open the link directly
// @namespace    http://tampermonkey.net/
// @version      0.1.3
// @description  点击链接直接跳转
// @author       nediiii
// @match        *://*.csdn.net/*
// @match        *://*.gitee.com/*
// @match        *://*.uisdc.com/*
// @match        *://*.logonews.cn/*
// @match        *://*.afdian.net/*
// @match        *://*.tianyancha.com/*
// @match        *://*.oschina.net/*
// @match        *://*.pixiv.net/*
// @match        *://*.jianshu.com/*
// @match        *://*.juejin.cn/*
// @match        *://*.weibo.cn/*
// @match        *://*.weibo.com/*
// @match        *://*.yuque.com/*
// @match        *://*.segmentfault.com/*
// @match        *://*.zhihu.com/*
// @match        *://*.bookmarkearth.com/*
// @run-at       document-start
// @license      GPLv3 License
// @icon         https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// ==/UserScript==

(function () {
    'use strict';

    // 保存原始方法
    unsafeWindow.__otld_open = unsafeWindow.open;
    // 重写 open 方法
    var myopen = function (url, name, features) {
        console.log({ url }, { name }, { features });
        // debugger;
        return unsafeWindow.__otld_open(url, name, features);
    }
    // 屏蔽 JS 中对原生函数 native 属性的检测
    var _myopen = myopen.bind(null);
    _myopen.toString = unsafeWindow.__otld_open.toString;
    Object.defineProperty(unsafeWindow, 'open', {
        value: _myopen
    });

    const getValidURL = (url) => {
        try {
            let u = new URL(url);
            return u;
        } catch (error) {
            return getValidURLWithBase(url);
        }
    }

    const getValidURLWithBase = (url) => {
        try {
            let u = new URL(url, getCurrentURLBase());
            return u;
        } catch (error) {
            return null;
        }
    }

    const getCurrentURLBase = () => {
        return window.location.origin;
    }

    const urlReg = /\bhttps?:\/\/\S+/gi;

    const weiboResolver = async (href) => {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: href,
                onload: function (response) {
                    console.log({ response });
                    if (response.status != 200) {
                        reject(href);
                        return;
                    }

                    // weibo跳转会区分目标网址是否备案
                    // 未备案的, 中转
                    // 已备案的, 直跳
                    let realURI = href;
                    if (response.finalUrl === href) {
                        // 未备案, 中转网址
                        // 网址在html里
                        let doc = new DOMParser().parseFromString(response.responseText, "text/html");
                        let node = doc.querySelector('body > div > div:nth-child(2)');
                        let str = node.innerText;
                        console.log({ doc });
                        console.log({ node });
                        console.log({ str });

                        let extractURL = str.match(urlReg);
                        console.log({ extractURL });
                        if (extractURL) {
                            realURI = extractURL[0];
                        }
                    }
                    else {
                        // 已备案
                        // 网址在finalUrl里
                        let extractURL = response.finalUrl.match(urlReg);
                        if (extractURL) {
                            realURI = extractURL[0];
                        }
                    }

                    resolve(realURI)
                },
                onerror: function (error) {
                    console.log({ error });
                    reject(href)
                }
            });
        });
    }

    const segfaultResolver = async (href) => {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: href,
                onload: function (response) {
                    console.log({ response });
                    if (response.status == 200) {
                        let doc = new DOMParser().parseFromString(response.responseText, "text/html");
                        let node = doc.querySelector('body > p')
                        let str = node.innerText;
                        console.log({ doc });
                        console.log({ node });
                        console.log({ str });

                        let realURI = href;
                        let extractURL = str.match(urlReg);
                        console.log({ extractURL });
                        if (extractURL) {
                            realURI = extractURL[0];
                        }
                        resolve(realURI)
                    } else {
                        reject(href)
                    }
                },
                onerror: function (error) {
                    console.log({ error });
                    reject(href)
                }
            });
        });
    }

    const bmeResolver = async (href) => {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: href,
                onload: function (response) {
                    console.log({ response });
                    if (response.status == 200) {
                        let doc = new DOMParser().parseFromString(response.responseText, "text/html");
                        let node = doc.querySelector('body > div.row.box > div.col-lg-6.jump-box > div > div.content > p.link');
                        let str = node.innerText;
                        console.log({ doc });
                        console.log({ node });
                        console.log({ str });

                        let realURI = href;
                        let extractURL = str.match(urlReg);
                        console.log({ extractURL });
                        if (extractURL) {
                            realURI = extractURL[0];
                        }
                        resolve(realURI)
                    } else {
                        reject(href)
                    }
                },
                onerror: function (error) {
                    console.log({ error });
                    reject(href)
                }
            });
        });
    }

    const patter_match = {

        // 注意这里的pattern需要去看对应网站dom里的a标签的实际herf值, console也会打印日志, 可以自己添加正则来增加网站支持

        // https://zhuanlan.zhihu.com/p/23333042
        // https://link.zhihu.com/?target=https%3A//getkap.co/
        // https://link.zhihu.com/?target=https%3A//greasyfork.org/en/scripts/5029-yet-another-%25E8%2587%25AA%25E5%258F%25A4cb%25E5%2587%25BA%25E8%25AF%2584%25E8%25AE%25BA-sharing-plugin
        zhihu: { pattern: /https?:\/\/link\.zhihu\.com\/?\?target=(.+)$/ },

        // https://www.jianshu.com/p/a6a63a0c6e53
        // https://links.jianshu.com/go?to=https%3A%2F%2Fnpm.taobao.org%2Fmirrors%2Felectron
        jianshu2: { pattern: /https?:\/\/links\.jianshu\.com\/go\?to=(.+)$/ },

        // href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.aliyun.com%2Fgroup%2Falisoftwaretech%2F"
        juejin: { pattern: /https?:\/\/link\.juejin\.cn\/?\?target=(.+)$/ },

        // https://gitee.com/meetqy/acss-dnd
        // href="https://gitee.com/link?target=https%3A%2F%2Fcuyang.me%2Facss-dnd%2F"
        // https://blog.gitee.com/2022/01/20/lowcodetools/
        // https://link.juejin.cn/?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2Fnocobase%2Fnocobase
        gitee: { pattern: /https?:\/\/gitee\.com\/link\?target=(.+)$/ },

        // https://www.uisdc.com/build-b-end-grid-system
        // https://link.uisdc.com/?redirect=https%3A%2F%2Fuxdesign.cc%2Fresponsive-grids-and-how-to-actually-use-them-970de4c16e01
        uisdc: { pattern: /https?:\/\/link\.uisdc\.com\/?\?redirect=(.+)$/ },

        // https://www.logonews.cn/apple-sues-sex-topic-blog-for-logo-infringement.html
        // https://link.logonews.cn/?url=http://aasd.k12.wi.us/district
        logonews: { pattern: /https?:\/\/link\.logonews\.cn\/?\?url=(.+)$/ },

        // https://afdian.net/@AdventCirno
        // https://afdian.net/link?target=https%3A%2F%2Fwww.patreon.com%2Fuser%3Fu%3D6139561
        afdian: { pattern: /https?:\/\/afdian\.net\/link\?target=(.+)$/ },

        // https://www.pixiv.net/users/25237
        // href="/jump.php?url=https%3A%2F%2Ftwitter.com%2Fomiya_io"
        // href="/jump.php?https%3A%2F%2Finstagram.com%2Fsnatti89%2F"
        pixiv: { pattern: /https?:\/\/www\.pixiv\.net\/jump\.php\?(?:url=)?(.+)$/ },

        // https://my.oschina.net/androiddevs/blog/5496556
        // https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fmp.weixin.qq.com%2Fmp%2Fappmsgalbum%3F__biz%3DMzk0NDIwMTExNw%3D%3D%26action%3Dgetalbum%26album_id%3D1879128471667326981%23wechat_redirect
        // href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fmp.weixin.qq.com%2Fmp%2Fappmsgalbum%3F__biz%3DMzk0NDIwMTExNw%3D%3D%26action%3Dgetalbum%26album_id%3D1879128471667326981%23wechat_redirect"
        oschina: { pattern: /https?:\/\/www\.oschina\.net\/action\/GoToLink\?url=(.+)$/ },

        // https://weibo.cn/sinaurl?u=https%3A%2F%2Fwww.freebsd.org%2F
        // https://weibo.cn/sinaurl?toasturl=https%3A%2F%2Ftime.geekbang.org%2F
        // https://weibo.cn/sinaurl?luicode=10000011&lfid=230259&u=http%3A%2F%2Ft.cn%2FA6qHeVlf
        // https://weibo.cn/sinaurl?f=w&u=http%3A%2F%2Ft.cn%2FA66XY2gI&ep=LlAsNz3HD%2C1683963007%2CLlpkandl6%2C7276218544
        // href="https://weibo.cn/sinaurl?f=w&u=http%3A%2F%2Ft.cn%2FA66XY2gI&ep=LlAsNz3HD%2C1683963007%2CLlpkandl6%2C7276218544"
        weibo: { pattern: /https?:\/\/weibo\.cn\/sinaurl\?f=w&u=(.+)$/, resolver: weiboResolver },

        // http://t.cn/A66926Pm  未备案的, 跳转到中转网址,  response.finalUrl仍然还是http://t.cn/A66926Pm 目标网址出现在response.responseText里
        // http://t.cn/A669K964  已备案的, 直接跳转到目标网址, 出现在response.finalUrl里
        weibo2: { pattern: /(https?:\/\/t\.cn\/.+)$/, resolver: weiboResolver },


        // segmentfault对链接进行加密处理, 不知道如何decode, 所以只能写一个函数去单独处理
        // https://link.segmentfault.com/?enc=LZyRulLABKpXOHl2vbA%2F4w%3D%3D.MWhFMvjhyBk1ReIRoGxyxa0VxGtg%2Foyk0DMtfzZTJoKbsgoJFtGCPHe8%2BZ1HbRdcvNsGaVfll9oGQXLsZCHK7w%3D%3D
        // https://segmentfault.com/a/1190000017434150
        segfault: { pattern: /https?:\/\/link\.segmentfault\.com\/?\?enc=(.+)$/, resolver: segfaultResolver },

        // https://www.bookmarkearth.com/detail/097c687c98974691b2174bc1e85103d4
        // https://show.bookmarkearth.com/view/801
        bookmarkearth: { pattern: /(https?:\/\/show\.bookmarkearth\.com\/view\/.+)$/, resolver: bmeResolver },


        // 以下网站a标签的herf未修改, 推测是js做的弹窗, 所以不需要匹配, 也匹配不出来
        // csdn https://link.csdn.net/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FRegExp
        // 语雀 https://www.yuque.com/r/goto?url=https%3A%2F%2Fwww.canva.cn%2F

        // https://blog.csdn.net/weixin_41010294/article/details/85289852
        csdn: { host: 'csdn.net' },

        // https://www.yuque.com/yuque/gpvawt/fuu6h3
        yuque: { host: 'yuque.com' },

        // https://www.tianyancha.com/company/28723141
        // href="https://ss.knet.cn/verifyseal.dll?sn=e18042711010873571xsuv000000&pa=111332"
        // https://www.tianyancha.com/security?target=https%3A%2F%2Fss.knet.cn%2Fverifyseal.dll%3Fsn%3De18042711010873571xsuv000000%26pa%3D111332
        tianyancha: { host: 'tianyancha.com' },

    }

    const matchHostResolver = (url, host) => {
        // 1. 需要当前网站与host匹配
        if (!window.location.host.includes(host)) {
            return false;
        }
        // 2. url为外链
        if (url.host.includes(host)) {
            return false;
        }

        return true;
    }

    const getMatchPattern = (url) => {
        for (let i in patter_match) {

            if (patter_match[i].hasOwnProperty('host') && matchHostResolver(url, patter_match[i].host)) {
                return patter_match[i];
            }
            if (patter_match[i].hasOwnProperty('pattern') && url.href.match(patter_match[i].pattern)) {
                return patter_match[i];
            }
        }
        return null;
    }

    const resolveRealURI = async (href) => {
        // TODO 兼容套娃链接, 如 https://link.juejin.cn/?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2Fnocobase%2Fnocobase
        const fallbackURI = href;

        for (let i in patter_match) {
            const matcher = href.match(patter_match[i].pattern);
            if (!matcher) {
                continue;
            }
            console.log({ matcher });

            if (patter_match[i].hasOwnProperty('resolver')) {
                // complex customize resolver
                console.log(patter_match[i].resolver)
                let realURI = await patter_match[i].resolver(href);
                return realURI;
            }

            const encodeURI = matcher[1];
            // simple reg resolver
            return decodeURIComponent(encodeURI);
        }
        return fallbackURI;
    }

    const patternResolve = async (patter_match, href) => {

        if (patter_match.hasOwnProperty('resolver')) {
            // complex customize resolver
            console.log(patter_match.resolver)
            let realURI = await patter_match.resolver(href);
            return realURI;
        }

        if (patter_match.hasOwnProperty('pattern')) {
            const matcher = href.match(patter_match.pattern);
            if (!matcher) { return href; }
            const encodeURI = matcher[1];
            return decodeURIComponent(encodeURI);
        }

        return href;
    }

    const getAnchorElement = (e) => {
        let target = e.target;
        while (target) {
            if (target.tagName.toLowerCase() === 'a' && target.hasAttribute('href')) {
                return target;
            }
            target = target.parentElement;
        }
        return null;
    }

    document.addEventListener('click', (e) => {
        console.log({ e })

        // 找到a标签
        let anchor = getAnchorElement(e);
        if (!anchor) {
            return;
        }

        console.log({ anchor })

        let href = anchor.getAttribute('href');
        if (!href) {
            return;
        }

        // 不是url, 则不做处理
        // 兼容如 href设置为 'javascript:void(0);' 等的情况
        let url = getValidURL(href);
        if (url === null) {
            return;
        }

        console.log({ url });

        const matchPattern = getMatchPattern(url);
        console.log({ matchPattern });
        if (!matchPattern) {
            console.log("不匹配处理规则, 如有误, 请反馈给我, 非常感谢");
            return;
        }

        e.stopPropagation();
        e.preventDefault();

        let target = '_self';
        if (anchor.hasAttribute('target')) {
            target = anchor.getAttribute('target');
        }

        // csdn 打开新标签页兼容处理
        if (matchPattern == patter_match.csdn) {
            target = '_blank';
        }

        console.log({ target });
        patternResolve(matchPattern, url.href).then((realURI) => {
            console.log({ realURI });
            window.open(realURI, target);
        }).catch(() => { window.open(href, target); });
    }, { capture: true })

})();