// ==UserScript== // @name Enhancement Userscript for LIHKG // @version 0.6.3 // @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://greasyfork.org/users/371179 // @downloadURL https://update.greasyfork.icu/scripts/427766/Enhancement%20Userscript%20for%20LIHKG.user.js // @updateURL https://update.greasyfork.icu/scripts/427766/Enhancement%20Userscript%20for%20LIHKG.meta.js // ==/UserScript== (function() { 'use strict'; GM_addStyle([ ` /* html.skip-drag-upload .EGBBkGyEbfIEpHMLTW84H{ display:none !important; } */ /* main textarea */ ._2ME7dqW8n0YSe687nDnGvI ._3ILx6bqxXMKzVd1P5DRS9F{ padding-bottom: 40px; } /* div 放開圖片即可上載*/ .EGBBkGyEbfIEpHMLTW84H{ opacity: 1 !important; pointer-events: all !important; left: 5%; right: 5%; top: calc(100% - 35px); bottom: 0; width: auto; height: auto; } /* div Preview ~ div + div 放開圖片即可上載*/ ._1TdM0E7HcWQwmxXpfa6yJg ~ ._2ME7dqW8n0YSe687nDnGvI .EGBBkGyEbfIEpHMLTW84H{ visibility: collapse; } `, // 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); } } `, // po reply - userselect for icons ` html body ._1Ku9qL4qhkBDwAgVLYcQdi[class], html body ._1Ku9qL4qhkBDwAgVLYcQdi[class]:hover { user-select:none !important; } ` ].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(/(\]+\>)/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 = ''; return v1 */ } return a + b + c }) msg = msg.replace( /]+>\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: } }); const makePopup=(obj)=>{ const {header, content1,content2, placeholder, remarks, btn1, btn2, closeFn, btn1Fn, btn2Fn}=obj; let mcontent2=content2?`
${content2}
`:''; let df = document.createElement('div'); df.innerHTML= `
${header}
${content1}${mcontent2}
${remarks}
` df.querySelector('._10tWW0o-L-5oSH8lCBl9ai').addEventListener('click', closeFn) let btnFns = [btn1Fn, btn2Fn]; for(const [idx,a] of [...df.querySelectorAll('a[href*="#"]')].entries()) a.addEventListener('click', btnFns[idx]) return df.querySelector('div'); } document.addEventListener('keyup',function(evt){ if(evt.code=='KeyB'){ let memberID = getSelection()+"" let m = null if(m=/\s*\#(\d+)\s*/.exec(memberID)){ let mPopup = makePopup({header:'Block', content1:`Memeber ID:${m[1]}`,content2:'', placeholder:'Type the Reason Here', remarks:'Please be careful', btn1:'Cancel', btn2:'Block', closeFn:function(){ mPopup.remove() }, btn1Fn:function(){ mPopup.remove() }, btn2Fn:function(){ let reason = mPopup.querySelector('input[type="text"]').value || ''; fetch(`https://lihkg.com/api_v2/user/${m[1]}/block?reason=${reason}`) .then(response => response.json()) .then(data => { if(data&& 'success' in data) alert( data.success?'成功':'失敗'); console.log(data) }); mPopup.remove() }, }) document.querySelector('body').appendChild(mPopup) } }else if(evt.code=='KeyU'){ let memberID = getSelection()+"" let m = null if(m=/\s*\#(\d+)\s*/.exec(memberID)){ fetch(`https://lihkg.com/api_v2/user/${m[1]}/unblock`) .then(response => response.json()) .then(data => { if(data&& 'success' in data) alert( data.success?'成功':'失敗'); console.log(data) }); } } }) // Your code here... })();