Advertisement
23Xor

improved control panel fix2

Jan 13th, 2023 (edited)
730
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 17.90 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name        Improved Control Panel
  3. // @description Sort threads by date, highlights/alerts you on unread posts, thread links go to newest unread post
  4. // @namespace   E-Hentai
  5. // @include     https://forums.e-hentai.org/index.php?*act=UserCP&CODE=26*
  6. // @version     2.2.2
  7. // @author      Superlatanium
  8. // ==/UserScript==
  9.  
  10.  
  11. var ICPUIFatherElement = document.getElementById('ucpcontent').parentElement;
  12. var ICPUIMainElement = document.createElement("div");
  13. // This HTML is minified
  14. ICPUIMainElement.id = "ICPinfo";
  15. ICPUIMainElement.innerHTML = '<div class="maintitle-like"><span>Threads monitoring: <span id="monitorStatus">ON</span> <button id="toggleMonitor">Stop</button></span></div><div class="maintitle-like"><span>Clear script data if you have modified forum timezone settings: <button id="clearData">Reset</button></span></div>';
  16. document.styleSheets[0].addRule('.maintitle-like', 'border: 1px solid #F7F7F7; color: #000; font-size: 8pt; font-weight: bold; margin: 0; padding: 8px;');
  17. ICPUIFatherElement.insertBefore(ICPUIMainElement, ICPUIFatherElement.firstChild);
  18. var ICPUIElements = {
  19.     monitorStatus: document.getElementById('monitorStatus'),
  20.     toggleMonitor: document.getElementById('toggleMonitor'),
  21.     clearData: document.getElementById('clearData'),
  22. };
  23.  
  24. var clearScriptData = function() {
  25.   localStorage.improvedControlPanel = '';
  26.   alert('Script data is cleared, please refresh to let it take effect.');
  27. }
  28. ICPUIElements.clearData.onclick = clearScriptData
  29.  
  30. var myForums = [7, 87, 74, 79, 72, 74, 76, 77];
  31. var interval = 180000; // updates every 5 minutes (but this value is 3 minutes bruh)
  32. var subforumInterval = 3600000; // an hour
  33. var timeout = 4000000; // updates stop after a bit more than 1 hour of inactivity
  34.  
  35.  
  36.  
  37.  
  38. var otherUnreadThreads = [];
  39.  
  40. if (!localStorage.improvedControlPanel){
  41.   getTimeZone();
  42.   return;
  43. }
  44. var storage = JSON.parse(localStorage.improvedControlPanel);
  45. var userId = new RegExp(/showuser=\d+/.exec(document.querySelector('a[href*="showuser"]').href)[0]);
  46.  
  47. var lastClicked = Date.now();
  48. var atLeastOneHighlighted;
  49.  
  50. document.getElementById('ucpcontent').style.backgroundColor = '#4286f4';
  51.  
  52. var audio = document.createElement('audio');
  53. audio.muted = true;
  54. function unmuteOnce() {
  55.     audio.muted = false;
  56.     document.body.removeEventListener("mouseover", unmuteOnce);
  57.     document.body.removeEventListener("click", unmuteOnce);
  58. }
  59. document.body.addEventListener("mouseover", unmuteOnce);
  60. document.body.addEventListener("click", unmuteOnce);
  61. var source = audio.appendChild(document.createElement('source'));
  62. source.src = 'https://reasoningtheory.net/ding.mp3';
  63. source.type = 'audio/mpeg';
  64.  
  65. var skin = document.getElementById('gfooter').querySelector('option[selected="selected"]').innerHTML;
  66. if (skin == 'Fusion')
  67.   var unreadColor = '#73464E';
  68. else if (skin == 'Ambience')
  69.   var unreadColor = '#E0E0E0';
  70.  
  71. function buildCss(){
  72.   document.head.appendChild(document.createElement('style')).innerHTML = `
  73. #peekButton {padding:5px}
  74. #peekButton:hover {background-color:yellow}
  75. #peekdiv {position:fixed; top:50%; left:15%; width:70%; height:40%; border:3px solid green;}
  76. #peekdiv > img {position:fixed; top:calc(50% + 4px); left:calc(85% - 23px)}
  77. #ucpcontent .maintitle > div:nth-child(1) {float:left}
  78. #ucpcontent .maintitle > div:nth-child(3) {float:right}
  79. #ignoreBtn {margin-left:5px; margin-right:5px}
  80. iframe {width:100%; height:100%;}
  81. `;
  82. }
  83. buildCss();
  84.  
  85. var table = document.getElementsByClassName('borderwrapm')[0].children[0].children[0];
  86. var peekDiv;
  87. function cleanContent(){
  88.   //Remove email notification text
  89.   [].forEach.call(document.getElementsByClassName('desc'), function(desc){
  90.     if (desc.childNodes.length > 1){
  91.       desc.removeChild(desc.childNodes[desc.childNodes.length - 1]);
  92.       desc.removeChild(desc.childNodes[desc.childNodes.length - 1]);
  93.     }
  94.   });
  95.  
  96.  
  97.   //Sort threads by date
  98.   var trs = [];
  99.   for (var i = table.children.length - 2; i > 0; i--){
  100.     var tr = table.children[i];
  101.     if (tr.children[0].className == 'row1'){
  102.       tr.parentElement.removeChild(tr);
  103.       continue;
  104.     }
  105.     tr.threadDate = dateStringToDate(tr.children[4].childNodes[0].textContent);
  106.     trs.push(tr);
  107.     tr.parentElement.removeChild(tr);
  108.   }
  109.   trs.sort(function(a, b){
  110.     return b.threadDate - a.threadDate;
  111.   });
  112.  
  113.   atLeastOneHighlighted = false;
  114.   var displayedThreads = [];
  115.   trs.forEach(function(tr){
  116.     if (tr.children[5].textContent != 'Ignore'){
  117.       displayedThreads.push(tr.getElementsByTagName('a')[0].href.match(/(t|showtopic)=(\d+)/)[2]);
  118.     }
  119.   });
  120.   var foundSubforumThread = false;
  121.   trs.forEach(function(tr){
  122.     var a = tr.getElementsByTagName('a')[0];
  123.     var threadNumber = a.href.match(/(t|showtopic)=(\d+)/)[2];
  124.     if (!/showtopic/.test(a.href)){
  125.       //Add &view=getnewpost to the end of each subscribed thread link
  126.       a.parentElement.removeChild(a.nextSibling);
  127.       a.parentElement.removeChild(a.nextSibling);
  128.       a.parentElement.removeChild(a.nextSibling);
  129.       a.target = '_blank';
  130.       a.onclick = function(){ read(a); }; //left click
  131.       a.oncontextmenu = function(){ read(a); }; //right click
  132.       a.onmouseup = function(e) { if (e.which === 2) read(a); }; //middle click
  133.       a.href = a.href + '&view=getnewpost';
  134.     }
  135.    
  136.     //If you haven't read the latest post, highlight the thread
  137.     if (!/Ignore/.test(tr.children[5].textContent) && ((!storage.latestRead[threadNumber] || storage.latestRead[threadNumber] < tr.threadDate) && !userId.test(tr.children[4].getElementsByTagName('a')[0].href))){
  138.       if (!/auction/i.test(tr.children[1].textContent))
  139.         atLeastOneHighlighted = true;
  140.       [].forEach.call(tr.children, function(td){
  141.         td.style.backgroundColor = unreadColor;
  142.       });
  143.     } else if (/Ignore/.test(tr.children[5].textContent)){
  144.       //add new threads from subscribed forums
  145.       if (displayedThreads.indexOf(threadNumber) != -1) //don't display threads already subscribed to
  146.         return;
  147.       if (storage.seenThreads.indexOf(threadNumber) != -1) //don't display threads Ignored
  148.         return;
  149.       foundSubforumThread = true;
  150.       [].forEach.call(tr.children, function(td){
  151.         td.style.backgroundColor = unreadColor;
  152.         td.style.padding = '12px 5px 12px 5px';
  153.       });
  154.       tr.children[1].children[0].onclick = setThreadRead;
  155.       tr.children[1].children[0].oncontextmenu = setThreadRead;
  156.       tr.children[5].children[0].onclick = function(){
  157.         tr.parentElement.removeChild(tr);
  158.         if (storage.seenThreads.indexOf(threadNumber) == -1)
  159.           storage.seenThreads.push(threadNumber);
  160.         saveStorage();
  161.       };
  162.     }
  163.    
  164.    
  165.     function setThreadRead(){
  166.       [].forEach.call(tr.children, function(td){
  167.         td.style.backgroundColor = '';
  168.       });
  169.       if (storage.seenThreads.indexOf(threadNumber) == -1 && displayedThreads.indexOf(threadNumber) == -1){
  170.         storage.seenThreads.push(threadNumber); //new threads gotten from subforums only
  171.         for (var i = otherUnreadThreads.length - 1; i >= 0; i--){
  172.           if (otherUnreadThreads[i].children[1].textContent === tr.children[1].textContent)
  173.             otherUnreadThreads.splice(0, 1);
  174.         }
  175.       } else
  176.         read(a);
  177.       saveStorage();
  178.     }
  179.     var peekButton = document.createElement('img');
  180.     tr.children[0].replaceChild(peekButton, tr.children[0].children[0]);
  181.     peekButton.id = 'peekButton';
  182.     peekButton.src = 'https://reasoningtheory.net/eye.png';
  183.     peekButton.onclick = function(){
  184.       function exitDiv(){
  185.         if (peekDiv)
  186.           document.body.removeChild(peekDiv);
  187.         peekDiv = '';
  188.       }
  189.       if (peekDiv){
  190.         exitDiv();
  191.         return;
  192.       }
  193.       peekDiv = document.body.appendChild(document.createElement('div'));
  194.       peekDiv.id = 'peekdiv';
  195.       var peekIFrame = peekDiv.appendChild(document.createElement('iframe'));
  196.       peekIFrame.src = a.href;
  197.       peekIFrame.onload = function(){
  198.         setThreadRead();
  199.         [].forEach.call(peekIFrame.contentDocument.body.getElementsByClassName('borderwrap'), function(post){
  200.           if (post.children.length != 4)
  201.             return;
  202.           var userProfile = post.getElementsByClassName('postdetails')[1];
  203.           userProfile.hiddenHTML = userProfile.innerHTML;
  204.           userProfile.innerHTML = userProfile.children[0].innerHTML;
  205.           var showProfile = userProfile.appendChild(peekIFrame.contentDocument.createElement('span'));
  206.           showProfile.textContent = ' ?';
  207.           showProfile.style.textDecoration = 'underline';
  208.           showProfile.onclick = function(){
  209.             userProfile.innerHTML = userProfile.hiddenHTML;
  210.           };
  211.         });
  212.       };
  213.       var exitDivButton = peekDiv.appendChild(document.createElement('img'));
  214.       exitDivButton.src = 'https://reasoningtheory.net/x.png';
  215.       exitDivButton.onclick = exitDiv;
  216.       setThreadRead();
  217.     };
  218.     table.insertBefore(tr, table.children[table.children.length - 1]);
  219.   });
  220.   if ((atLeastOneHighlighted && threadMonitor.isRunning) || foundSubforumThread)
  221.     audio.play();
  222.  
  223.  
  224.   function dateStringToDate(dateString){
  225.     var nowDate = new Date();
  226.     var threadDate;
  227.     if (/Today/.test(dateString))
  228.       threadDate = new Date(nowDate.getUTCFullYear(), nowDate.getUTCMonth(), nowDate.getUTCDate(), dateString.match(/(\d\d)\:/)[1], dateString.match(/\:(\d\d)/)[1]);
  229.     else if (/Yesterday/.test(dateString))
  230.       threadDate = new Date(nowDate.getUTCFullYear(), nowDate.getUTCMonth(), nowDate.getUTCDate() - 1, dateString.match(/(\d\d)\:/)[1], dateString.match(/\:(\d\d)/)[1]);
  231.     else
  232.       threadDate = new Date(dateString);
  233.     threadDate.setMinutes(threadDate.getMinutes() - threadDate.getTimezoneOffset() - storage.offset);
  234.     return threadDate;
  235.   }
  236. }
  237.  
  238. function read(a){
  239.   storage.latestRead[a.href.match(/&t=(\d+)/)[1]] = Date.now();
  240.   [].forEach.call(a.parentElement.parentElement.children, function(td){
  241.     td.style.backgroundColor = '';
  242.   });
  243.   saveStorage();
  244. }
  245.  
  246. document.body.addEventListener('click', function(){
  247.   lastClicked = Date.now() - 100; //ensure that updates get disabled after a missed thread and resume due to Date.now() - lastClicked < interval test coming out near 0
  248.   if (threadMonitor.isSuspended) { threadMonitor.start(); }
  249. });
  250. document.body.addEventListener('contextmenu', function(){
  251.   lastClicked = Date.now() - 100;
  252. });
  253.  
  254.  
  255.  
  256. var threadMonitor = {
  257.     isRunning: false,
  258.     isSuspended: false,
  259.     updateSheduler: null,
  260.     update: function() {
  261.         var idleTime = Date.now() - lastClicked;
  262.         if (idleTime > timeout || (atLeastOneHighlighted === true && (idleTime > interval))){
  263.             document.getElementById('ucpcontent').style.backgroundColor = 'red';
  264.             this.stop(); this.isSuspended = true; return;
  265.         }
  266.         document.getElementById('ucpcontent').style.backgroundColor = 'yellow';
  267.         get('https://forums.e-hentai.org/index.php?act=UserCP&CODE=26', function(data){
  268.             document.getElementById('ucpcontent').innerHTML = data.getElementById('ucpcontent').innerHTML; //threads
  269.             var tableHead = document.getElementsByClassName('maintitle')[2];
  270.             if (tableHead.textContent == 'Menu') //new PM
  271.                 tableHead = document.getElementsByClassName('maintitle')[3];
  272.             table = document.getElementsByClassName('borderwrapm')[0].children[0].children[0];
  273.             var userlinks = document.getElementById('userlinks');
  274.             userlinks.innerHTML = data.getElementById('userlinks').innerHTML; //PMs
  275.             var msgA = userlinks.children[1].children[3];
  276.             if (msgA.textContent.substring(0, 1) !== '0')
  277.                 userlinks.style.backgroundColor = '#3399ff';
  278.             msgA.onclick = function(){
  279.                 userlinks.style.backgroundColor = '';
  280.             };
  281.             msgA.oncontextmenu = msgA.onclick;
  282.             document.getElementById('gfooter').innerHTML = data.getElementById('gfooter').innerHTML; //Time is now
  283.             if (tableHead.children.length === 0) {
  284.                 // initialize tableHead...
  285.                 tableHead.id = 'tableHead';
  286.                 tableHead.style.textAlign = 'center';
  287.                 tableHead.innerHTML = `<div>Welcome to your control panel</div><div>${data.getElementById('gfooter').textContent.match(/Time\sis.+\d:\d+/)[0]}</div>`;
  288.             }
  289.            
  290.             if (Date.now() - storage.lastSubforumCheck > subforumInterval) {
  291.                 getSubforums(false);
  292.             } else {
  293.                 otherUnreadThreads.forEach(function(otherUnreadThread){
  294.                     table.insertBefore(otherUnreadThread, table.children[1]);
  295.                 });
  296.                 document.getElementById('ucpcontent').style.backgroundColor = '#4286f4';
  297.             }
  298.             cleanContent();
  299.         });
  300.     },
  301.     syncUIState: function() {
  302.         ICPUIElements.monitorStatus.textContent = this.isRunning ? 'ON' : 'OFF';
  303.         ICPUIElements.toggleMonitor.textContent = this.isRunning ? 'Stop' : 'Start';
  304.     },
  305.     launchUpdateLoop: function() {
  306.         // 此函数非阻塞
  307.         // this is NOT "blocking"
  308.         this.update();
  309.         this.updateSheduler = setTimeout(this.launchUpdateLoop, interval);
  310.     },
  311.     start: function() {
  312.         lastClicked = Date.now() - 2000;
  313.         this.isRunning = true;
  314.         this.isSuspended = false;
  315.         this.syncUIState();
  316.         this.launchUpdateLoop();
  317.     },
  318.     stop: function() {
  319.         // 此函数不会打断update函数
  320.         // this will not break update function halfway
  321.         document.getElementById('ucpcontent').style.backgroundColor = 'red';
  322.         this.isRunning = false;
  323.         this.syncUIState();
  324.         clearTimeout(this.updateSheduler);
  325.         this.updateSheduler = null;
  326.     },
  327.     toggleState: function() {
  328.         if (this.isRunning) {
  329.             this.stop();
  330.         } else {
  331.             this.start();
  332.         }
  333.     },
  334. }
  335.  
  336. ICPUIElements.toggleMonitor.onclick = function() { threadMonitor.toggleState(); }
  337. // ICPUIElements.toggleMonitor.onclick = threadMonitor.toggleState
  338. // this commented line is invalid because "this context" changes
  339. threadMonitor.start()
  340.  
  341. function getSubforums(ignoreAll){
  342.   var subforumIndex = 0;
  343.   storage.lastSubforumCheck = Date.now();
  344.   otherUnreadThreads = [];
  345.   getForum();
  346.   function getForum(){
  347.     get('https://forums.e-hentai.org/index.php?showforum=' + myForums[subforumIndex], function(data){
  348.       var getTable = data.getElementsByClassName('ipbtable')[1].children[0];
  349.       for (var j = getTable.children.length - 1; j > 0; j--){
  350.         var tr = getTable.children[j];
  351.         if (tr.getElementsByTagName('a').length === 0)
  352.           continue;
  353.         var threadTitleTBlock = tr.children[2]
  354.         var threadTitleContainer = threadTitleTBlock.getElementsByTagName('span')[0];
  355.         var threadHrefElement;
  356.         if (threadTitleContainer) {
  357.             // 正常,直接获取链接
  358.             threadHrefElement = threadTitleContainer.getElementsByTagName('a')[0]
  359.         } else {
  360.             // 没有span,fallback到格内第一个a元素的父元素
  361.             threadHrefElement = threadTitleTBlock.getElementsByTagName('a')[0]
  362.             threadTitleContainer = threadHrefElement.parentElement
  363.         }
  364.         var threadTitle = threadTitleContainer.textContent;
  365.         try {
  366.           var threadNumber = threadHrefElement.href.match(/showtopic=(\d+)/)[1];
  367.         } catch(err) {
  368.           continue;
  369.         }
  370.         var desc = tr.children[2].getElementsByClassName('desc');
  371.         if (storage.seenThreads.indexOf(threadNumber) != -1)
  372.           continue;
  373.         if (ignoreAll){
  374.           storage.seenThreads.push(threadNumber);
  375.           continue;
  376.         }
  377.         tr.children[2].innerHTML = '<a href=https://forums.e-hentai.org/index.php?showtopic=' + threadNumber + ' target=_blank>' + threadTitle + '</a>' + (desc ? desc[0].outerHTML : '');
  378.         tr.children[6].innerHTML = tr.children[6].children[0].innerHTML;
  379.         var dateNode = tr.children[6].childNodes[0];
  380.         var dateStr = dateNode.textContent;
  381.         if (!/Today,\s\d/.test(dateStr) && !/Yesterday,\s\d/.test(dateStr))
  382.           dateNode.textContent = dateStr.match(/\s([a-z]{3})/i)[1] + ' ' + dateStr.match(/^\d+/)[0] + ' ' + dateStr.match(/\d\d\d\d/)[0] + ', ' + dateStr.match(/\d+:\d+/)[0];
  383.         tr.removeChild(tr.children[4]);
  384.         tr.removeChild(tr.children[1]);
  385.        
  386.         tr.appendChild(document.createElement('td'));
  387.         var button = tr.children[5].appendChild(document.createElement('button'));
  388.         button.id = 'ignoreBtn';
  389.         button.textContent = 'Ignore';
  390.         [].forEach.call(tr.children, function(td){
  391.           td.className = 'row2';
  392.         });
  393.         otherUnreadThreads.push(tr);
  394.         table.insertBefore(tr, table.children[1]);
  395.       }
  396.      
  397.       subforumIndex++;
  398.       if (subforumIndex !== myForums.length){
  399.         getForum();
  400.         return;
  401.       }
  402.       saveStorage();
  403.       document.getElementById('ucpcontent').style.backgroundColor = '#4286f4';
  404.      
  405.       if (ignoreAll) {
  406.         window.location.href = window.location.href;
  407.       }
  408.       return;
  409.     });
  410.   }
  411. }
  412.  
  413. function saveStorage(){
  414.   localStorage.improvedControlPanel = JSON.stringify(storage);
  415. }
  416.  
  417.  
  418. function getTimeZone(){
  419.   var s = '';
  420.   if (/https/.exec(window.location.href))
  421.     s = 's';
  422.   get('https://forums.e-hentai.org/index.php?act=UserCP&CODE=04', function(data){
  423.     storage = {'seenThreads': [], 'lastSubforumCheck': 0};
  424.     var timeString = data.getElementsByTagName('fieldset')[0].querySelector('option[selected="selected"]').textContent;
  425.     if (!/\d/.exec(timeString))
  426.       storage.offset = 0;
  427.     else
  428.       storage.offset = parseInt(/[+-]/.exec(timeString) + '1') * (parseInt(/\d\d?(?=\:)/.exec(timeString)) * 60 + parseInt(/\d\d?(?=\s)/.exec(timeString)));
  429.    
  430.     storage.latestRead = {};
  431.     [].forEach.call(document.querySelectorAll('a[href*="forums.e-hentai.org/index.php?act=ST"]'), function(a){
  432.       var threadNumber = a.href.match(/&t=(\d+)/)[1];
  433.       if (Object.keys(storage.latestRead).indexOf(threadNumber) == -1)
  434.         storage.latestRead[threadNumber] = Date.now();
  435.     });
  436.     getSubforums(true);
  437.   });
  438. }
  439.  
  440. function get(url, done){
  441.   var r = new XMLHttpRequest();
  442.   r.open('GET', url, true);
  443.   r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
  444.   r.responseType = 'document';
  445.   r.onload = function () {
  446.     if (r.status >= 200 && r.status < 400){
  447.       done(r.response);
  448.     }
  449.   };
  450.   r.send();
  451. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement