Enhancement Userscript for LIHKG

An Enhancement Userscript for LIHKG

当前为 2021-06-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Enhancement Userscript for LIHKG
  3. // @version 0.5.2
  4. // @description An Enhancement Userscript for LIHKG
  5. // @include /https?\:\/\/lihkg\.com/
  6. // @icon https://www.google.com/s2/favicons?domain=lihkg.com
  7. // @grant GM_addStyle
  8. // @namespace https://gf.qytechs.cn/users/371179
  9. // ==/UserScript==
  10. (function() {
  11. 'use strict';
  12.  
  13. GM_addStyle([
  14. // drag & drop area for image upload
  15. `.EGBBkGyEbfIEpHMLTW84H:not([dragmode]),.EGBBkGyEbfIEpHMLTW84H[dragmode="text"]{position:fixed;left:-9999px;top:-9999px;width:2px;height:2px;}`,
  16. // copy reply content from reply preview
  17. `div[contenteditable] p>img~br:last-child,div[contenteditable] p>a~br:last-child,div[contenteditable] p>div~br:last-child{content:'';}`,
  18. // css fix for thread posts positioning
  19. `
  20. body ._21IQKhlBjN2jlHS_TVgI3l:after {left:0.4rem}
  21. body ._21IQKhlBjN2jlHS_TVgI3l .vv9keWAXpwoonDah6rSIU ._3D2lzCKDMcdgEkexZrTSUh{margin-left: -6px;width: 16px;}
  22. `,
  23. // css fix for like and dislike due to js hack of like count and dislike count (reply posts)
  24. `
  25. body label[for*="-dislike-like"]{display:inline-block !important;}
  26. body label[for*="-like-like"]{display:inline-block !important;}
  27. body ._3ExaynSI6tUp5h1U50MHtI ._3imUf8qB9LmLpk_t5PjDm4>div:first-child+div:last-child{margin-left:-6px;}
  28. `,
  29. // css fix for like and dislike due to js hack of like count and dislike count (main thread)
  30. // empty full space char for maintaining padding when the count is not yet shown
  31. `
  32. span[data-tip="正評"]:not([data-score])::after{content: " ";
  33. font-size: .6rem;
  34. font-weight: 400;
  35. margin-top: .3rem;}
  36. span[data-tip="負評"]:not([data-score])::after{content: " ";
  37. font-size: .6rem;
  38. font-weight: 400;
  39. margin-top: .3rem;}
  40. span[data-tip="正評"],span[data-tip="負評"]{padding-top:0px !important;}
  41. `
  42.  
  43. ].map(x => x.trim()).join('\n'))
  44.  
  45.  
  46. var isNumCheck = function(n) {
  47. return n > 0 || n < 0 || n === 0
  48. }
  49. var postDetails = {}
  50. var threadDetails = {}
  51. var pendingRefreshThread = false;
  52.  
  53. var testBlockElm = function(elm) {
  54. if (elm && elm.nodeType == 1) {
  55. switch (elm.tagName) {
  56. case 'DIV':
  57. case 'P':
  58. case 'BLOCKQUOTE':
  59. return true;
  60.  
  61. default:
  62. return false;
  63.  
  64. }
  65.  
  66. }
  67. }
  68.  
  69. var getElementText = function(el) {
  70. var text = '';
  71. // Text node (3) or CDATA node (4) - return its text
  72. if ((el.nodeType === 3) || (el.nodeType === 4)) {
  73. text = el.nodeValue;
  74. // If node is an element (1) and an img, input[type=image], or area element, return its alt text
  75. } else if ((el.nodeType === 1) && (
  76. (el.tagName.toLowerCase() == 'img') ||
  77. (el.tagName.toLowerCase() == 'area') ||
  78. ((el.tagName.toLowerCase() == 'input') && el.getAttribute('type') && (el.getAttribute('type').toLowerCase() == 'image'))
  79. )) {
  80. text = el.getAttribute('alt') || '';
  81. if (el.tagName.toLowerCase() == 'img' && text == '' && el.getAttribute('data-original')) {
  82. text = '[img]' + el.getAttribute('data-original') + '[/img]';
  83. } else if (el.tagName.toLowerCase() == 'img' && text == '' && el.getAttribute('src')) {
  84. text = '[img]' + el.getAttribute('src') + '[/img]';
  85. }
  86. // Traverse children unless this is a script or style element
  87. } else if ((el.nodeType === 1) && (
  88. (el.tagName.toLowerCase() == 'br')
  89. )) {
  90. text = '\n';
  91. // Traverse children unless this is a script or style element
  92. } else if ((el.nodeType === 1) && !el.tagName.match(/^(script|style)$/i)) {
  93. var children = el.childNodes;
  94.  
  95. if (el && testBlockElm(el) && el.previousSibling ? testBlockElm(el.previousSibling) : false) {
  96. text += '\n'
  97. }
  98. if (el.tagName.toLowerCase() == 'blockquote') {
  99. text += '[quote]'
  100. }
  101. for (var i = 0, l = children.length; i < l; i++) {
  102. text += getElementText(children[i]);
  103. }
  104. if (el.tagName.toLowerCase() == 'blockquote') {
  105. text += '[/quote]'
  106. }
  107.  
  108. }
  109. return text;
  110. };
  111.  
  112.  
  113. document.cssAll = function() {
  114.  
  115. var s = document.querySelectorAll.apply(this, arguments)
  116.  
  117. s = Array.prototype.slice.call(s, 0)
  118. return s
  119. }
  120.  
  121. function urlConvert(url) {
  122. var src = url.replace(/\w+\:\/\//, '')
  123. var replacements = [...src.matchAll(/[\w\.]+/g)].filter((t) => /\./.test(t))
  124. if (replacements.length > 1) {
  125. replacements.length--;
  126.  
  127. }
  128. replacements.forEach((s) => {
  129. src = src.replace(s, '')
  130. })
  131.  
  132. src = src.replace(/\/+/g, '/')
  133.  
  134. return src;
  135.  
  136. }
  137.  
  138. var emoji = {};
  139. setTimeout(function() {
  140. console.log(emoji)
  141. }, 1500)
  142.  
  143. setInterval(() => {
  144.  
  145. document.cssAll('img[src*="lihkg.com"][alt]:not([title])').forEach(function(imgElm) {
  146. var src = imgElm.getAttribute('src');
  147. var erc = urlConvert(src)
  148. var imgAlt = imgElm.getAttribute('alt') || "";
  149. if (/^[\x20-\x7E]+$/.test(imgAlt) && /\W/.test(imgAlt)) {
  150. emoji[erc] = imgAlt.trim()
  151. }
  152.  
  153. imgElm.setAttribute('title', imgAlt)
  154.  
  155. })
  156.  
  157.  
  158. document.cssAll('a[href*="profile/"]:not([href*="//"]):not([title])').forEach(function(aElm) {
  159. aElm.setAttribute('title', aElm.getAttribute('href'))
  160. })
  161.  
  162. document.cssAll('[data-ic~="hkgmoji"]:not([title])>img[src*="lihkg.com"]:not([alt])').forEach(function(imgElm) {
  163. var src = imgElm.getAttribute('src');
  164. var erc = urlConvert(src)
  165. var text = emoji[erc] ? emoji[erc] : "[img]" + erc + "[/img]"
  166. imgElm.parentNode.setAttribute('title', text)
  167. imgElm.setAttribute('alt', text)
  168.  
  169.  
  170. })
  171.  
  172. document.cssAll('a[href*="local.lihkg.com"]>img:not([anchored])').forEach(function(img) {
  173. img.setAttribute('anchored', 'true')
  174.  
  175.  
  176. var originalSrc = img.getAttribute('src') || img.getAttribute('data-original') || ""
  177. var newSrc = originalSrc.replace('local.lihkg.com', 'cdn.lihkg.com');
  178.  
  179. if (newSrc && originalSrc != newSrc) {
  180.  
  181. // console.log(originalSrc, newSrc)
  182.  
  183. var fx = function() {
  184. if (img.complete == false) return setTimeout(fx, 33);
  185.  
  186. if (img.currentSrc == "") {
  187.  
  188. var b = img.cloneNode(false);
  189. b.removeAttribute('data-original');
  190. b.removeAttribute('data-src');
  191.  
  192. if (b.getAttribute('alt') == "") b.removeAttribute('alt')
  193. if (b.getAttribute('title') == "") b.removeAttribute('title')
  194.  
  195. b.setAttribute('src', newSrc);
  196. img.parentNode.replaceChild(b, img)
  197.  
  198. if (b.parentNode.getAttribute('href')) {
  199. b.parentNode.setAttribute('href', b.parentNode.getAttribute('href').replace(originalSrc, newSrc));
  200. if (b.nextElementSibling && b.nextElementSibling.hasAttribute('data-error')) b.nextElementSibling.parentNode.removeChild(b.nextElementSibling);
  201. if (b.nextElementSibling && b.nextElementSibling.outerHTML.toLocaleLowerCase() == '<ins></ins>') b.nextElementSibling.parentNode.removeChild(b.nextElementSibling);
  202.  
  203.  
  204.  
  205. }
  206.  
  207. b.removeAttribute('anchored')
  208.  
  209.  
  210. }
  211.  
  212. }
  213. fx();
  214.  
  215.  
  216. }
  217.  
  218. })
  219.  
  220.  
  221. document.cssAll('div[contenteditable] div[data-ic][contenteditable="false"]').forEach((elm) => {
  222.  
  223. elm.removeAttribute('contenteditable')
  224. })
  225.  
  226.  
  227. document.cssAll('img[src]:not([alt]),img[src][alt=""]').forEach((el) => {
  228.  
  229. if (el.getAttribute('alt') || el.getAttribute('title')) return;
  230.  
  231. var text = '';
  232. if (el.tagName.toLowerCase() == 'img' && el.getAttribute('data-original')) {
  233. text = '[img]' + el.getAttribute('data-original') + '[/img]';
  234. } else if (el.tagName.toLowerCase() == 'img' && el.getAttribute('src')) {
  235. text = '[img]' + el.getAttribute('src') + '[/img]';
  236. }
  237. if (text) el.setAttribute('alt', text)
  238. if (text) el.setAttribute('title', text)
  239.  
  240. })
  241.  
  242.  
  243.  
  244.  
  245. document.cssAll('[data-post-id]:not([hacked])').forEach((el) => {
  246.  
  247. el.setAttribute('hacked', 'true');
  248. var post_id = el.getAttribute('data-post-id');
  249. if (!post_id) return;
  250.  
  251. //console.log(post_id, postDetails)
  252. var post_detail = postDetails[post_id]
  253. if (post_detail) {
  254. // console.log(55,post_detail)
  255.  
  256. }
  257.  
  258. })
  259.  
  260.  
  261.  
  262. }, 33)
  263.  
  264.  
  265.  
  266. function refreshingThreadEvent(thread_id) {
  267.  
  268.  
  269. console.log("refreshingThreadEvent",threadDetails[thread_id])
  270. if (thread_id && threadDetails[thread_id]) {
  271.  
  272.  
  273. document.cssAll('span[data-tip="正評"]').forEach((elm) => {
  274.  
  275. elm.setAttribute('data-score', threadDetails[thread_id]["like_count"]);
  276. elm.style.paddingTop = '0px';
  277. })
  278.  
  279.  
  280. document.cssAll('span[data-tip="負評"]').forEach((elm) => {
  281.  
  282. elm.setAttribute('data-score', threadDetails[thread_id]["dislike_count"]);
  283. elm.style.paddingTop = '0px';
  284. })
  285.  
  286.  
  287.  
  288. }
  289.  
  290.  
  291. }
  292.  
  293.  
  294. var cid_refreshingThread = 0;
  295.  
  296. function refreshingThreadRunning() {
  297.  
  298. if (!cid_refreshingThread) return;
  299.  
  300.  
  301. var titlespan = document.cssAll('a[href^="/category/"]+span');
  302. if (titlespan.length == 1) {
  303. var titlespanElm = titlespan[0]
  304.  
  305. if (!titlespanElm.querySelector('noscript')) {
  306. titlespanElm.appendChild(document.createElement('noscript'))
  307.  
  308.  
  309. if (pendingRefreshThread) {
  310.  
  311. var thread_id = pendingRefreshThread === true ? (/thread\/(\d+)\//.exec(location + "") || [null, null])[1] : pendingRefreshThread
  312.  
  313. pendingRefreshThread = false;
  314. clearInterval(cid_refreshingThread);
  315. cid_refreshingThread = 0;
  316. refreshingThreadEvent(thread_id)
  317.  
  318.  
  319. }
  320.  
  321.  
  322. }
  323. }
  324.  
  325. }
  326.  
  327.  
  328.  
  329.  
  330. var makePlain = false;
  331. document.addEventListener('drop', function(evt) {
  332. // console.log(evt, makePlain, evt.target)
  333.  
  334. var p = evt.target;
  335. var contenteditable = false;
  336. while (p) {
  337. if (p.hasAttribute('contenteditable') && p.getAttribute('contenteditable') != 'false') {
  338. contenteditable = true;
  339. break;
  340. }
  341. p = p.parentNode;
  342.  
  343. }
  344.  
  345. if (contenteditable) {
  346.  
  347. if (makePlain && evt.dataTransfer.getData('text/html')) {
  348.  
  349. var text = evt.dataTransfer.getData('text/html');
  350.  
  351.  
  352. setTimeout(function() {
  353. var sel = window.getSelection();
  354. if (sel.getRangeAt && sel.rangeCount) {
  355.  
  356.  
  357. var range = sel.getRangeAt(0);
  358.  
  359. var cloneContents = range.cloneContents();
  360.  
  361.  
  362. var hh = document.createElement('html')
  363. hh.innerHTML = text;
  364. console.log(hh.cloneNode(true))
  365. var hhPlain = getElementText(hh)
  366.  
  367. console.log(hhPlain)
  368. hh.innerText = hhPlain
  369.  
  370.  
  371. if (cloneContents.textContent !== hhPlain) {
  372.  
  373. range.deleteContents();
  374.  
  375. var trueHTML = '<p>' + hh.innerHTML.toLowerCase().replace(/<br\s*\/?>/g, "<br></p><p>") + "</p>"
  376.  
  377. console.log(trueHTML)
  378.  
  379. document.execCommand('insertHTML', false, trueHTML);
  380. }
  381.  
  382. }
  383.  
  384. }, 10);
  385. }
  386.  
  387. }
  388.  
  389.  
  390. }, true)
  391.  
  392.  
  393. document.addEventListener('dragstart', function(evt) {
  394. var editable = document.querySelector('div[contenteditable]')
  395. var dragFrom = evt.target
  396. if (editable && dragFrom) {
  397.  
  398. if (editable == dragFrom || editable.contains(dragFrom)) {
  399.  
  400. } else {
  401. makePlain = true;
  402.  
  403. }
  404.  
  405. }
  406. // console.log(evt)
  407. }, false);
  408.  
  409.  
  410. document.addEventListener('dragend', function(evt) {
  411. if (Event.prototype._preventDefault) {
  412. Event.prototype.preventDefault = Event.prototype._preventDefault
  413. Event.prototype._preventDefault = null
  414.  
  415. }
  416. setTimeout(function() {
  417. makePlain = false;
  418. }, 77);
  419. }, false);
  420.  
  421. document.addEventListener('drop', function(evt) {
  422.  
  423. setTimeout(function() {
  424. makePlain = false;
  425. }, 77);
  426. if (Event.prototype._preventDefault) {
  427. Event.prototype.preventDefault = Event.prototype._preventDefault
  428. Event.prototype._preventDefault = null
  429. }
  430.  
  431. var dropZone = document.querySelector('.EGBBkGyEbfIEpHMLTW84H');
  432. if (!dropZone) return;
  433. dropZone.removeAttribute('dragmode');
  434.  
  435.  
  436. }, true)
  437.  
  438.  
  439.  
  440. document.addEventListener('dragenter', function(evt) {
  441.  
  442. var dropZone = document.querySelector('.EGBBkGyEbfIEpHMLTW84H');
  443.  
  444.  
  445.  
  446. var isFileTransfer = false;
  447. if (evt.dataTransfer.types) {
  448. for (var i = 0; i < evt.dataTransfer.types.length; i++) {
  449. if (evt.dataTransfer.types[i] == "Files") {
  450. isFileTransfer = true;
  451. break;
  452. }
  453. }
  454. }
  455.  
  456. if (!isFileTransfer) {
  457.  
  458. if (dropZone)
  459. dropZone.setAttribute('dragmode', 'text');
  460. // evt.dataTransfer.effectAllowed='copy';
  461. if (!Event.prototype._preventDefault) {
  462. Event.prototype._preventDefault = Event.prototype.preventDefault;
  463. Event.prototype.preventDefault = function() {
  464. if (this.type == 'dragover') {
  465.  
  466. } else {
  467. return this._preventDefault();
  468. }
  469. //console.log(this)
  470. };
  471. }
  472.  
  473.  
  474.  
  475. } else {
  476. makePlain = false;
  477. if (dropZone)
  478. dropZone.setAttribute('dragmode', 'file');
  479.  
  480.  
  481. }
  482. // console.log('dragenter',!isFileTransfer)
  483.  
  484.  
  485.  
  486. }, false)
  487.  
  488.  
  489.  
  490. var injection = function() {
  491. if (!JSON._parse && JSON.parse) {
  492. JSON._parse = JSON.parse
  493. JSON.parse = function(text, r) {
  494.  
  495. if (text && typeof text == "string" && text.indexOf('display_vote') > 0) {
  496. text = text.replace(/([\'\"])display_vote[\'\"]\s*:\s*false/gi, '$1display_vote$1:true')
  497. }
  498. var res = JSON._parse.apply(this, arguments)
  499.  
  500. return res;
  501. }
  502. }
  503.  
  504. var api_callback = "uleccyqjstui"
  505.  
  506. ;
  507. ((xmlhr, xmlhr_pt) => {
  508. if (!xmlhr_pt._open) {
  509. xmlhr_pt._open = xmlhr_pt.open;
  510.  
  511.  
  512. xmlhr_pt.open = function() {
  513. // console.log('xmlhr_open', arguments)
  514. if (/https?\:\/\/[\x20-2E\x30-5B\x5D-\x7E]*lihkg\.com\/[\x20-\x7E]*api[\x20-\x7E]+/.test(arguments[1])) {
  515. this._url = arguments[1];
  516.  
  517. console.log('_url', this._url)
  518. }
  519. this._open.apply(this, arguments)
  520. }
  521. }
  522.  
  523.  
  524.  
  525. if (!xmlhr_pt._send) {
  526. xmlhr_pt._send = xmlhr_pt.send;
  527.  
  528.  
  529. xmlhr_pt.send = function() {
  530. if (this._url) {
  531. this.addEventListener('load', function() {
  532. var resText = this.responseText;
  533. var jsonObj = null;
  534. if (resText && typeof resText == 'string') {
  535. try {
  536. jsonObj = JSON.parse(resText);
  537. } catch (e) {}
  538. }
  539.  
  540. if (jsonObj) {
  541. //like_count
  542.  
  543. var code_num = 0;
  544.  
  545. if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.item_data && jsonObj.response.item_data.length >= 1 && jsonObj.response.item_data[0]["post_id"]) {
  546. code_num |= 16;
  547. }
  548. if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.thread_id) {
  549. code_num |= 8;
  550. }
  551. // console.log('code', code_num);
  552. var event = new CustomEvent(api_callback, {
  553. detail: {
  554. code: code_num,
  555. responseJSON: jsonObj
  556. }
  557. });
  558. document.dispatchEvent(event);
  559.  
  560.  
  561.  
  562. //console.log(jsonObj)
  563. }
  564.  
  565. })
  566. }
  567. // console.log('xmlhr_send', arguments)
  568. this._send.apply(this, arguments)
  569. }
  570. }
  571.  
  572.  
  573. })(XMLHttpRequest, XMLHttpRequest.prototype)
  574.  
  575. }
  576.  
  577. var jsscript = document.createElement('script');
  578. jsscript.type = 'text/javascript';
  579. jsscript.innerHTML = '(' + injection + ')()';
  580. document.documentElement.appendChild(jsscript)
  581.  
  582. var api_callback = "uleccyqjstui"
  583. //data-post-id="5226a9cb7b395fbc182d183a6ee9b35c8adfd2fe"
  584. document.addEventListener(api_callback, function(e) {
  585. if (!e || !e.detail) return;
  586. console.log("API_CALLBACK",e.detail)
  587. var jsonObj;
  588. var code_num = e.detail.code
  589. switch (true) {
  590.  
  591. case (code_num & 8) == 8: //main thread
  592.  
  593. case (code_num & 16) == 16: //posts
  594.  
  595.  
  596. jsonObj = e.detail.responseJSON;
  597.  
  598.  
  599. if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.item_data && jsonObj.response.item_data.length >= 1 && jsonObj.response.item_data[0]["post_id"]) {
  600. var reply_post_fx = (reply_item) => {
  601. if ('dislike_count' in reply_item && 'like_count' in reply_item && reply_item["post_id"]) {
  602.  
  603. var like_count = +reply_item['like_count']
  604. var dislike_count = +reply_item['dislike_count']
  605. var post_id = reply_item['post_id']
  606.  
  607. if (isNumCheck(like_count) && isNumCheck(dislike_count) && post_id) {
  608. postDetails[post_id] = {
  609. 'like_count': like_count,
  610. 'dislike_count': dislike_count
  611. }
  612. }
  613.  
  614. }
  615. };
  616. jsonObj.response.item_data.forEach(reply_post_fx)
  617. if (jsonObj.response.pinned_post && jsonObj.response.pinned_post["post_id"]) reply_post_fx(jsonObj.response.pinned_post)
  618.  
  619. }
  620.  
  621.  
  622.  
  623. if (jsonObj.success == 1 && jsonObj.response && jsonObj.response.thread_id) {
  624. var thread_fx = (thread_item) => {
  625. if ('like_count' in thread_item && 'dislike_count' in thread_item && thread_item["thread_id"]) {
  626.  
  627. var like_count = +thread_item['like_count']
  628. var dislike_count = +thread_item['dislike_count']
  629. var thread_id = thread_item['thread_id']
  630.  
  631. if (isNumCheck(like_count) && isNumCheck(dislike_count) && thread_id) {
  632. threadDetails[thread_id] = {
  633. 'like_count': like_count,
  634. 'dislike_count': dislike_count
  635. }
  636. pendingRefreshThread = thread_id;
  637. if (!cid_refreshingThread) cid_refreshingThread = setInterval(refreshingThreadRunning, 1);
  638. }
  639.  
  640. }
  641. };
  642. thread_fx(jsonObj.response)
  643. //console.log(99, threadDetails)
  644.  
  645. }
  646.  
  647. //console.log(jsonObj)
  648. break;
  649.  
  650.  
  651. default:
  652. }
  653.  
  654. });
  655.  
  656.  
  657. // Your code here...
  658. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址