// ==UserScript== // @name Enhancement Userscript for LIHKG // @version 0.5.3 // @description An Enhancement Userscript for LIHKG // @include /https?\:\/\/lihkg\.com/ // @icon https://www.google.com/s2/favicons?domain=lihkg.com // @grant GM_addStyle // @namespace https://greasyfork.org/users/371179 // @downloadURL none // ==/UserScript== (function() { 'use strict'; GM_addStyle([ // drag & drop area for image upload `.EGBBkGyEbfIEpHMLTW84H:not([dragmode]),.EGBBkGyEbfIEpHMLTW84H[dragmode="text"]{position:fixed;left:-9999px;top:-9999px;width:2px;height:2px;}`, // copy reply content from reply preview `div[contenteditable] p>img~br:last-child,div[contenteditable] p>a~br:last-child,div[contenteditable] p>div~br:last-child{content:'';}`, // 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 `body ._3dwGLtjqTgI2gc9wpc7FuT { padding: 1rem .6rem calc(1rem + 0px) calc(.7rem + 0px); padding: 1rem .6rem calc(1rem + env(safe-area-inset-bottom)) calc(.7rem + env(safe-area-inset-left)); }` ].map(x => x.trim()).join('\n')) var isNumCheck = function(n) { return n > 0 || n < 0 || n === 0 } var postDetails = {} var threadDetails = {} var pendingRefreshThread = false; var testBlockElm = function(elm) { if (elm && elm.nodeType == 1) { switch (elm.tagName) { case 'DIV': case 'P': case 'BLOCKQUOTE': return true; default: return false; } } } var getElementText = function(el) { var text = ''; // Text node (3) or CDATA node (4) - return its text if ((el.nodeType === 3) || (el.nodeType === 4)) { text = el.nodeValue; // If node is an element (1) and an img, input[type=image], or area element, return its alt text } else if ((el.nodeType === 1) && ( (el.tagName.toLowerCase() == 'img') || (el.tagName.toLowerCase() == 'area') || ((el.tagName.toLowerCase() == 'input') && el.getAttribute('type') && (el.getAttribute('type').toLowerCase() == 'image')) )) { text = el.getAttribute('alt') || ''; if (el.tagName.toLowerCase() == 'img' && text == '' && el.getAttribute('data-original')) { text = '[img]' + el.getAttribute('data-original') + '[/img]'; } else if (el.tagName.toLowerCase() == 'img' && text == '' && el.getAttribute('src')) { text = '[img]' + el.getAttribute('src') + '[/img]'; } // Traverse children unless this is a script or style element } else if ((el.nodeType === 1) && ( (el.tagName.toLowerCase() == 'br') )) { text = '\n'; // Traverse children unless this is a script or style element } else if ((el.nodeType === 1) && !el.tagName.match(/^(script|style)$/i)) { var children = el.childNodes; if (el && testBlockElm(el) && el.previousSibling ? testBlockElm(el.previousSibling) : false) { text += '\n' } if (el.tagName.toLowerCase() == 'blockquote') { text += '[quote]' } for (var i = 0, l = children.length; i < l; i++) { text += getElementText(children[i]); } if (el.tagName.toLowerCase() == 'blockquote') { text += '[/quote]' } } return text; }; document.cssAll = function() { var s = document.querySelectorAll.apply(this, arguments) s = Array.prototype.slice.call(s, 0) return s } function urlConvert(url) { var src = url.replace(/\w+\:\/\//, '') var 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; } var emoji = {}; setTimeout(function() { console.log(emoji) }, 1500) setInterval(() => { document.cssAll('img[src*="lihkg.com"][alt]:not([title])').forEach(function(imgElm) { var src = imgElm.getAttribute('src'); var erc = urlConvert(src) var 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) { var src = imgElm.getAttribute('src'); var erc = urlConvert(src) var text = emoji[erc] ? emoji[erc] : "[img]" + erc + "[/img]" imgElm.parentNode.setAttribute('title', text) imgElm.setAttribute('alt', text) }) document.cssAll('a[href*="local.lihkg.com"]>img:not([anchored])').forEach(function(img) { img.setAttribute('anchored', 'true') var originalSrc = img.getAttribute('src') || img.getAttribute('data-original') || "" var newSrc = originalSrc.replace('local.lihkg.com', 'cdn.lihkg.com'); if (newSrc && originalSrc != newSrc) { // console.log(originalSrc, newSrc) var fx = function() { if (img.complete == false) return setTimeout(fx, 33); if (img.currentSrc == "") { var b = img.cloneNode(false); b.removeAttribute('data-original'); b.removeAttribute('data-src'); if (b.getAttribute('alt') == "") b.removeAttribute('alt') if (b.getAttribute('title') == "") b.removeAttribute('title') b.setAttribute('src', newSrc); img.parentNode.replaceChild(b, img) if (b.parentNode.getAttribute('href')) { b.parentNode.setAttribute('href', b.parentNode.getAttribute('href').replace(originalSrc, newSrc)); if (b.nextElementSibling && b.nextElementSibling.hasAttribute('data-error')) b.nextElementSibling.parentNode.removeChild(b.nextElementSibling); if (b.nextElementSibling && b.nextElementSibling.outerHTML.toLocaleLowerCase() == '') b.nextElementSibling.parentNode.removeChild(b.nextElementSibling); } b.removeAttribute('anchored') } } fx(); } }) document.cssAll('div[contenteditable] div[data-ic][contenteditable="false"]').forEach((elm) => { elm.removeAttribute('contenteditable') }) document.cssAll('img[src]:not([alt]),img[src][alt=""]').forEach((el) => { if (el.getAttribute('alt') || el.getAttribute('title')) return; var 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'); var post_id = el.getAttribute('data-post-id'); if (!post_id) return; //console.log(post_id, postDetails) var 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'; }) } } var cid_refreshingThread = 0; function refreshingThreadRunning() { if (!cid_refreshingThread) return; var titlespan = document.cssAll('a[href^="/category/"]+span'); if (titlespan.length == 1) { var titlespanElm = titlespan[0] if (!titlespanElm.querySelector('noscript')) { titlespanElm.appendChild(document.createElement('noscript')) if (pendingRefreshThread) { var thread_id = pendingRefreshThread === true ? (/thread\/(\d+)\//.exec(location + "") || [null, null])[1] : pendingRefreshThread pendingRefreshThread = false; clearInterval(cid_refreshingThread); cid_refreshingThread = 0; refreshingThreadEvent(thread_id) } } } } var makePlain = false; document.addEventListener('drop', function(evt) { // console.log(evt, makePlain, evt.target) var p = evt.target; var contenteditable = false; while (p) { if (p.hasAttribute('contenteditable') && p.getAttribute('contenteditable') != 'false') { contenteditable = true; break; } p = p.parentNode; } if (contenteditable) { if (makePlain && evt.dataTransfer.getData('text/html')) { var text = evt.dataTransfer.getData('text/html'); setTimeout(function() { var sel = window.getSelection(); if (sel.getRangeAt && sel.rangeCount) { var range = sel.getRangeAt(0); var cloneContents = range.cloneContents(); var hh = document.createElement('html') hh.innerHTML = text; console.log(hh.cloneNode(true)) var hhPlain = getElementText(hh) console.log(hhPlain) hh.innerText = hhPlain if (cloneContents.textContent !== hhPlain) { range.deleteContents(); var trueHTML = '

' + hh.innerHTML.toLowerCase().replace(//g, "

") + "

" console.log(trueHTML) document.execCommand('insertHTML', false, trueHTML); } } }, 10); } } }, true) document.addEventListener('dragstart', function(evt) { var editable = document.querySelector('div[contenteditable]') var dragFrom = evt.target if (editable && dragFrom) { if (editable == dragFrom || editable.contains(dragFrom)) { } else { makePlain = true; } } // console.log(evt) }, false); document.addEventListener('dragend', function(evt) { if (Event.prototype._preventDefault) { Event.prototype.preventDefault = Event.prototype._preventDefault Event.prototype._preventDefault = null } setTimeout(function() { makePlain = false; }, 77); }, false); document.addEventListener('drop', function(evt) { setTimeout(function() { makePlain = false; }, 77); if (Event.prototype._preventDefault) { Event.prototype.preventDefault = Event.prototype._preventDefault Event.prototype._preventDefault = null } var dropZone = document.querySelector('.EGBBkGyEbfIEpHMLTW84H'); if (!dropZone) return; dropZone.removeAttribute('dragmode'); }, true) document.addEventListener('dragenter', function(evt) { var dropZone = document.querySelector('.EGBBkGyEbfIEpHMLTW84H'); var isFileTransfer = false; if (evt.dataTransfer.types) { for (var i = 0; i < evt.dataTransfer.types.length; i++) { if (evt.dataTransfer.types[i] == "Files") { isFileTransfer = true; break; } } } if (!isFileTransfer) { if (dropZone) dropZone.setAttribute('dragmode', 'text'); // evt.dataTransfer.effectAllowed='copy'; if (!Event.prototype._preventDefault) { Event.prototype._preventDefault = Event.prototype.preventDefault; Event.prototype.preventDefault = function() { if (this.type == 'dragover') { } else { return this._preventDefault(); } //console.log(this) }; } } else { makePlain = false; if (dropZone) dropZone.setAttribute('dragmode', 'file'); } // console.log('dragenter',!isFileTransfer) }, false) var injection = function() { 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') } var res = JSON._parse.apply(this, arguments) return res; } } var 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() { var resText = this.responseText; var jsonObj = null; if (resText && typeof resText == 'string') { try { jsonObj = JSON.parse(resText); } catch (e) {} } if (jsonObj) { //like_count var 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); var 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) } var jsscript = document.createElement('script'); jsscript.type = 'text/javascript'; jsscript.innerHTML = '(' + injection + ')()'; document.documentElement.appendChild(jsscript) var api_callback = "uleccyqjstui" //data-post-id="5226a9cb7b395fbc182d183a6ee9b35c8adfd2fe" document.addEventListener(api_callback, function(e) { if (!e || !e.detail) return; console.log("API_CALLBACK",e.detail) var jsonObj; var 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"]) { var reply_post_fx = (reply_item) => { if ('dislike_count' in reply_item && 'like_count' in reply_item && reply_item["post_id"]) { var like_count = +reply_item['like_count'] var dislike_count = +reply_item['dislike_count'] var 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) { var thread_fx = (thread_item) => { if ('like_count' in thread_item && 'dislike_count' in thread_item && thread_item["thread_id"]) { var like_count = +thread_item['like_count'] var dislike_count = +thread_item['dislike_count'] var 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... })();