Enhancement Userscript for LIHKG

An Enhancement Userscript for LIHKG

От 09.05.2022. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Enhancement Userscript for LIHKG
// @version      0.6.1
// @description  An Enhancement Userscript for LIHKG
// @include        /https?\:\/\/lihkg\.com/
// @icon         https://www.google.com/s2/favicons?domain=lihkg.com
// @grant        GM_addStyle
// @license MIT
// @namespace https://greatest.deepsurf.us/users/371179
// ==/UserScript==
(function() {
    'use strict';

    GM_addStyle([

`
html.skip-drag-upload .EGBBkGyEbfIEpHMLTW84H{
display:none !important;
}

`,


        // css fix for thread posts positioning
        `
    body ._21IQKhlBjN2jlHS_TVgI3l:after {left:0.4rem}
    body ._21IQKhlBjN2jlHS_TVgI3l .vv9keWAXpwoonDah6rSIU ._3D2lzCKDMcdgEkexZrTSUh{margin-left: -6px;width: 16px;}
    `,
        // css fix for like and dislike due to js hack of like count and dislike count (reply posts)
        `
    body label[for*="-dislike-like"]{display:inline-block !important;}
    body label[for*="-like-like"]{display:inline-block !important;}
    body ._3ExaynSI6tUp5h1U50MHtI ._3imUf8qB9LmLpk_t5PjDm4>div:first-child+div:last-child{margin-left:-6px;}
`,
        // css fix for like and dislike due to js hack of like count and dislike count (main thread)
        // empty full space char for maintaining padding when the count is not yet shown
        `
    span[data-tip="正評"]:not([data-score])::after{content: " ";
    font-size: .6rem;
    font-weight: 400;
    margin-top: .3rem;}
    span[data-tip="負評"]:not([data-score])::after{content: " ";
    font-size: .6rem;
    font-weight: 400;
    margin-top: .3rem;}
    span[data-tip="正評"],span[data-tip="負評"]{padding-top:0px !important;}
    `,
        // kiwi browser css fix

        `
        @supports not (padding-bottom: env(safe-area-inset-bottom)){
        ._3dwGLtjqTgI2gc9wpc7FuT {
    padding: 1rem .6rem calc(1rem + 0px) calc(.7rem + 0px);
}
}`

    ].map(x => x.trim()).join('\n'))


    let isNumCheck = function(n) {
        return n > 0 || n < 0 || n === 0
    }
    let postDetails = {}
    let threadDetails = {}
    let pendingRefreshThread = false;

    let testBlockElm = function(elm) {
        if (elm && elm.nodeType == 1) {
            switch (elm.tagName) {
                case 'DIV':
                case 'P':
                case 'BLOCKQUOTE':
                    return true;

                default:
                    return false;

            }

        }
    }


    document.cssAll = function() {
        return [...document.querySelectorAll.apply(this, arguments)]
    }

    function urlConvert(url) {
        let src = url.replace(/\w+\:\/\//, '')
        let replacements = [...src.matchAll(/[\w\.]+/g)].filter((t) => /\./.test(t))
        if (replacements.length > 1) {
            replacements.length--;

        }
        replacements.forEach((s) => {
            src = src.replace(s, '')
        })

        src = src.replace(/\/+/g, '/')

        return src;

    }

    let emoji = {};
    setTimeout(function() {
        console.log(emoji)
    }, 1500)

    setInterval(() => {

        document.cssAll('img[src*="lihkg.com"][alt]:not([title])').forEach(function(imgElm) {
            let src = imgElm.getAttribute('src');
            let erc = urlConvert(src)
            let imgAlt = imgElm.getAttribute('alt') || "";
            if (/^[\x20-\x7E]+$/.test(imgAlt) && /\W/.test(imgAlt)) {
                emoji[erc] = imgAlt.trim()
            }

            imgElm.setAttribute('title', imgAlt)

        })


        document.cssAll('a[href*="profile/"]:not([href*="//"]):not([title])').forEach(function(aElm) {
            aElm.setAttribute('title', aElm.getAttribute('href'))
        })

        document.cssAll('[data-ic~="hkgmoji"]:not([title])>img[src*="lihkg.com"]:not([alt])').forEach(function(imgElm) {
            let src = imgElm.getAttribute('src');
            let erc = urlConvert(src)
            let text = emoji[erc] ? emoji[erc] : "[img]" + erc + "[/img]"
            imgElm.parentNode.setAttribute('title', text)
            imgElm.setAttribute('alt', text)


        })



        document.cssAll('img[src]:not([alt]),img[src][alt=""]').forEach((el) => {

            if (el.getAttribute('alt') || el.getAttribute('title')) return;

            let text = '';
            if (el.tagName.toLowerCase() == 'img' && el.getAttribute('data-original')) {
                text = '[img]' + el.getAttribute('data-original') + '[/img]';
            } else if (el.tagName.toLowerCase() == 'img' && el.getAttribute('src')) {
                text = '[img]' + el.getAttribute('src') + '[/img]';
            }
            if (text) el.setAttribute('alt', text)
            if (text) el.setAttribute('title', text)

        })




        document.cssAll('[data-post-id]:not([hacked])').forEach((el) => {

            el.setAttribute('hacked', 'true');
            let post_id = el.getAttribute('data-post-id');
            if (!post_id) return;

            //console.log(post_id, postDetails)
            let post_detail = postDetails[post_id]
            if (post_detail) {
                // console.log(55,post_detail)

            }

        })



    }, 33)



    function refreshingThreadEvent(thread_id) {


        console.log("refreshingThreadEvent", threadDetails[thread_id])
        if (thread_id && threadDetails[thread_id]) {


            document.cssAll('span[data-tip="正評"]').forEach((elm) => {

                elm.setAttribute('data-score', threadDetails[thread_id]["like_count"]);
                elm.style.paddingTop = '0px';
            })


            document.cssAll('span[data-tip="負評"]').forEach((elm) => {

                elm.setAttribute('data-score', threadDetails[thread_id]["dislike_count"]);
                elm.style.paddingTop = '0px';
            })



        }


    }


    let cid_refreshingThread = 0;

    function refreshingThreadRunning() {

        if (!cid_refreshingThread) return;


        let titlespan = document.cssAll('a[href^="/category/"]+span');
        if (titlespan.length == 1) {
            let titlespanElm = titlespan[0]

            if (!titlespanElm.querySelector('noscript')) {
                titlespanElm.appendChild(document.createElement('noscript'))


                if (pendingRefreshThread) {

                    let thread_id = pendingRefreshThread === true ? (/thread\/(\d+)\//.exec(location + "") || [null, null])[1] : pendingRefreshThread

                    pendingRefreshThread = false;
                    clearInterval(cid_refreshingThread);
                    cid_refreshingThread = 0;
                    refreshingThreadEvent(thread_id)


                }


            }
        }

    }




    let makePlain = false;


    document.addEventListener("dragstart", function(evt) {
        console.log(evt.target)
        if(!evt || !evt.target) return;

        let type = 0
        if(evt.target.nodeType!==1 && evt.target.parentElement/* && evt.target.parentElement.closest('[data-post-id]')*/){
            type=1;
        }

        if(evt.target.nodeType ===1){
        if(!evt.target.matches('img[alt][src][title]'))return;
        let alt = evt.target.getAttribute('alt')+'';
          if(!alt)return;
        if(/https?\:\/\//.test(alt))return;
        if(/^[a-zA-Z0-9]+$/.test(alt))return;
          if(/[\u0100-\uFFFF]/.test(alt))return;
        type = 2;

            console.log(alt)

            evt.dataTransfer.setData('text/plain', alt);

            evt.stopPropagation()
            evt.stopImmediatePropagation();

        }
        if(type>0){
        evt.dropEffect='copy';
            evt.effectAllowed = "all";

        document.documentElement.classList.add('skip-drag-upload')
        }
    }, true);



function makeRangeFromXY(evt){

    let range=null;
    if (document.caretRangeFromPoint) { // Chrome
        range=document.caretRangeFromPoint(evt.clientX,evt.clientY);
    }
    else if (evt.rangeParent) { // Firefox
        range=document.createRange(); range.setStart(evt.rangeParent,evt.rangeOffset);
    }
return range;
}
    document.addEventListener("drop", function(evt) {

        if(!evt || !evt.target)return;

        let node = evt.target;

        if(node.nodeType!==1 && node.parentNode && node.parentNode.nodeType===1) node= node.parentNode;

        if(node.nodeType===1 &&node.closest('div.ProseMirror[contenteditable]')){
            evt.preventDefault();
let range = makeRangeFromXY(evt)
    var sel = window.getSelection();
    sel.removeAllRanges(); sel.addRange(range);

    let p = sel.anchorNode
    while(p&&p.parentNode){
        if(p.nodeType===1&&p.matches('div.ProseMirror[contenteditable]')){
            p.focus();
            p.classList.add('ProseMirror-focused');
        break;
        }
        p=p.parentNode;
    }

            range.collapse(true);

            let text = evt.dataTransfer.getData('text/plain')
    document.execCommand('insertHTML',false,text);

    //sel.removeAllRanges();

            let mRange = window.getSelection().getRangeAt(0);

            mRange.setStart(mRange.endContainer,mRange.endOffset-text.length);
            mRange.setEnd(mRange.endContainer,mRange.endOffset);

         //   nRange.collapse(true);
   // sel.addRange(nRange)


        }

        document.documentElement.classList.remove('skip-drag-upload')

    },true)


    document.addEventListener("dragend", function(evt) {

        document.documentElement.classList.remove('skip-drag-upload')

        let p = document.querySelector('div.EGBBkGyEbfIEpHMLTW84H[style]')
        if(p){
            p.style.opacity='0';
            p.style.pointerEvents='none';
        }

    },true)

/*
    document.addEventListener("dragover", function(evt) {
        if(!evt || !evt.target)return;
        try{

        if(!evt.target.matches('[contenteditable], textarea, div.EGBBkGyEbfIEpHMLTW84H, div._1xaNo-2jhq5KooKoBBRKwe '))return;
        }catch(e){return;}
        console.log(evt)
        evt.stopPropagation();
    }, true);
    */


    let injection = function() {


        function extractRawURL(thumbnailURL) {

            let u = [...thumbnailURL.matchAll(/[\?\&]\w+\=([\x21-\x25\x27-\x3E\x40-\x7E]+)/g)].map(d => d[1])
            if (u.length) {
                let uMaxT = Math.max(...u.map(t => t.length))
                let u0 = u.filter(t => t.length == uMaxT)[0]

                if (u0) {

                    let v0 = null
                    try {
                        v0 = decodeURIComponent(u0)
                    } catch (e) {}
                    //console.log(v0,u0)
                    if (v0) {
                        return v0
                    }

                }
            }
            return null
        }

        if (!JSON._parse && JSON.parse) {
            JSON._parse = JSON.parse
            JSON.parse = function(text, r) {
/*
                if (text && typeof text == "string" && text.indexOf('display_vote') > 0) {
                    text = text.replace(/([\'\"])display_vote[\'\"]\s*:\s*false/gi, '$1display_vote$1:true')
                }
                */
                let res = JSON._parse.apply(this, arguments)

                let contentFix = (resObj) => {
                    if(!resObj || typeof resObj!='object')return;
                    for (let k in resObj) {
                        if (typeof resObj[k] == 'object') contentFix(resObj[k]);
                        else if (k=='display_vote' && resObj[k]===false){
                        resObj[k]=true;
                        }
                        else if (k == 'msg' && typeof resObj[k] == 'string') {

                            let msg = resObj[k];
                            let replace = false;
                            let bmsg=msg
                            msg = msg.replace(/(\<img\s+src\=\")(https?\:\/\/i\.lih\.kg\/thumbnail\?[^\"]+)(\"[^\>]+\>)/g, function(s, a, b, c) {

                                let v0 = extractRawURL(b)
                                if (v0) {
                                    replace = true;
return s.replace(b,v0).replace(b,v0).replace(b,v0)

                                    /*
                                    console.log(v0, b)

                                    let v1 = '<img src="' + v0 + '" data-thumbnail-src="' + b + '" />';

                                    return v1
*/
                                }

                                return a + b + c

                            })

                            msg = msg.replace(

                                /<a\s+href=\"(https\:\/\/i\.lih\.kg\/thumbnail\?u=[^\?\s\x00-\x20\x7F-\xFF\"]+)\"[^<>]+>\1<\/a>/g,
                                function(_, a) {

                                    let b = extractRawURL(a)
                                    if (b) {
                                        replace = true
                                        return _.replace(a,b).replace(a,b)
                                        /*
                                        let a01 = encodeURIComponent(a)
                                        let a02 = a01.replace(/\%26amp\%3B/gi, '%26')
                                        let b01 = encodeURIComponent(b)
                                        let c = _.replace(a, b).replace(a, b).replace(a02, b01).replace(a01, b01)

                                        return c*/
                                    }
                                    return _
                                })


                            if (replace) {
                                console.log(333,bmsg,msg)
                                resObj[k] = msg;

                            }
                        }
                    }
                }

                contentFix(res)

                return res;
            }
        }

        let api_callback = "uleccyqjstui"

        ;
        ((xmlhr, xmlhr_pt) => {
            if (!xmlhr_pt._open) {
                xmlhr_pt._open = xmlhr_pt.open;


                xmlhr_pt.open = function() {
                    // console.log('xmlhr_open', arguments)
                    if (/https?\:\/\/[\x20-2E\x30-5B\x5D-\x7E]*lihkg\.com\/[\x20-\x7E]*api[\x20-\x7E]+/.test(arguments[1])) {
                        this._url = arguments[1];

                        console.log('_url', this._url)
                    }
                    this._open.apply(this, arguments)
                }
            }



            if (!xmlhr_pt._send) {
                xmlhr_pt._send = xmlhr_pt.send;


                xmlhr_pt.send = function() {
                    if (this._url) {
                        this.addEventListener('load', function() {
                            let resText = this.responseText;
                            let jsonObj = null;
                            if (resText && typeof resText == 'string') {
                                try {
                                    jsonObj = JSON.parse(resText);
                                } catch (e) {}
                            }

                            if (jsonObj) {
                                //like_count

                                let code_num = 0;

                                if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.item_data && jsonObj.response.item_data.length >= 1 && jsonObj.response.item_data[0]["post_id"]) {
                                    code_num |= 16;
                                }
                                if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.thread_id) {
                                    code_num |= 8;
                                }
                                // console.log('code', code_num);
                                let event = new CustomEvent(api_callback, {
                                    detail: {
                                        code: code_num,
                                        responseJSON: jsonObj
                                    }
                                });
                                document.dispatchEvent(event);



                                //console.log(jsonObj)
                            }

                        })
                    }
                    // console.log('xmlhr_send', arguments)
                    this._send.apply(this, arguments)
                }
            }


        })(XMLHttpRequest, XMLHttpRequest.prototype)

    }

    let jsscript = document.createElement('script');
    jsscript.type = 'text/javascript';
    jsscript.innerHTML = '(' + injection + ')()';
    document.documentElement.appendChild(jsscript)

    let api_callback = "uleccyqjstui"
    //data-post-id="5226a9cb7b395fbc182d183a6ee9b35c8adfd2fe"
    document.addEventListener(api_callback, function(e) {
        if (!e || !e.detail) return;
        console.log("API_CALLBACK", e.detail)
        let jsonObj;
        let code_num = e.detail.code
        switch (true) {

            case (code_num & 8) == 8: //main thread

            case (code_num & 16) == 16: //posts


                jsonObj = e.detail.responseJSON;


                if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.item_data && jsonObj.response.item_data.length >= 1 && jsonObj.response.item_data[0]["post_id"]) {
                    let reply_post_fx = (reply_item) => {
                        if ('dislike_count' in reply_item && 'like_count' in reply_item && reply_item["post_id"]) {

                            let like_count = +reply_item['like_count']
                            let dislike_count = +reply_item['dislike_count']
                            let post_id = reply_item['post_id']

                            if (isNumCheck(like_count) && isNumCheck(dislike_count) && post_id) {
                                postDetails[post_id] = {
                                    'like_count': like_count,
                                    'dislike_count': dislike_count
                                }
                            }

                        }
                    };
                    jsonObj.response.item_data.forEach(reply_post_fx)
                    if (jsonObj.response.pinned_post && jsonObj.response.pinned_post["post_id"]) reply_post_fx(jsonObj.response.pinned_post)

                }



                if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.thread_id) {
                    let thread_fx = (thread_item) => {
                        if ('like_count' in thread_item && 'dislike_count' in thread_item && thread_item["thread_id"]) {

                            let like_count = +thread_item['like_count']
                            let dislike_count = +thread_item['dislike_count']
                            let thread_id = thread_item['thread_id']

                            if (isNumCheck(like_count) && isNumCheck(dislike_count) && thread_id) {
                                threadDetails[thread_id] = {
                                    'like_count': like_count,
                                    'dislike_count': dislike_count
                                }
                                pendingRefreshThread = thread_id;
                                if (!cid_refreshingThread) cid_refreshingThread = setInterval(refreshingThreadRunning, 1);
                            }

                        }
                    };
                    thread_fx(jsonObj.response)
                    //console.log(99, threadDetails)

                }

                //console.log(jsonObj)
                break;


            default:
        }

    });


    // Your code here...
})();