Greasy Fork is available in English.

Enhancement Userscript for LIHKG

An Enhancement Userscript for LIHKG

2021-06-11 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

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