LRC Live

Live-update (auto-refresh) LetsRun.com threads and get notified when new posts come in

  1. // ==UserScript==
  2. // @name LRC Live
  3. // @namespace lrc-live
  4. // @description Live-update (auto-refresh) LetsRun.com threads and get notified when new posts come in
  5. // @match https://www.letsrun.com/forum/flat_read.php*
  6. // @version 1.3
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. const urlParams = new URLSearchParams(window.location.search);
  11. const pageNo = +urlParams.get('page') || 0;
  12. const pages = document.querySelector('span.items-stretch')?.querySelectorAll('a[aria-label="pagination.goto_page"]') || [];
  13. const lastPage = +pages[pages.length - 1]?.innerText || 0;
  14. if (pageNo === lastPage) {
  15. const template = document.createElement('template');
  16. template.innerHTML = '<a role="button" title="Toggle LRC Live" class="button font-bold button-red shadow-md mt-2" style="width: 100%">Start LRC Live</a>';
  17. const liveButton = template.content.firstChild;
  18.  
  19. const threadContainer = document.querySelector('div.forum-thread-page-container');
  20. threadContainer.parentNode.insertBefore(liveButton, threadContainer.nextSibling);
  21.  
  22. const thread = +urlParams.get('thread');
  23. const posts = document.querySelectorAll('li.forum-post-container');
  24. const postIds = [...posts].map(post => post.querySelector('div').getAttribute('id'));
  25. const postList = document.querySelector('ul.post-list');
  26. const postCountText = document.querySelector('p.text-gray-700');
  27. const postCounters = postCountText ? [...postCountText.querySelectorAll('span.font-semibold')].slice(1) : [];
  28. let wireKey = posts.length;
  29. let checkPageNo = posts.length === 20 ? pageNo + 1 : pageNo;
  30.  
  31. let enabled = false;
  32. let interval;
  33. liveButton.addEventListener('click', async () => {
  34. liveButton.classList.toggle('button-red');
  35. liveButton.classList.toggle('button-green');
  36. enabled = !enabled;
  37. if (enabled) {
  38. liveButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="default-icon dbr-spin mr-2 h-3 w-3"><path d="M296 48c0 22.091-17.909 40-40 40s-40-17.909-40-40 17.909-40 40-40 40 17.909 40 40zm-40 376c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zm248-168c0-22.091-17.909-40-40-40s-40 17.909-40 40 17.909 40 40 40 40-17.909 40-40zm-416 0c0-22.091-17.909-40-40-40S8 233.909 8 256s17.909 40 40 40 40-17.909 40-40zm20.922-187.078c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40c0-22.092-17.909-40-40-40zm294.156 294.156c-22.091 0-40 17.909-40 40s17.909 40 40 40c22.092 0 40-17.909 40-40s-17.908-40-40-40zm-294.156 0c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40z"></path></svg> LRC Live is running, new posts in this thread will appear above... (click to stop)';
  39. const perm = await Notification.requestPermission();
  40. const notify = perm === 'granted';
  41. interval = window.setInterval(async () => {
  42. try {
  43. const url = 'https://www.letsrun.com/forum/flat_read.php?' + new URLSearchParams({ thread, page: checkPageNo });
  44. const checkPage = await fetch(url);
  45. const checkPageText = await checkPage.text();
  46. const checkPageDoc = new DOMParser().parseFromString(checkPageText, 'text/html');
  47. const checkPosts = checkPageDoc.querySelectorAll('li.forum-post-container');
  48. [...checkPosts].forEach(post => {
  49. const postDiv = post.querySelector('div');
  50. const postId = postDiv.getAttribute('id');
  51. if (postIds.includes(postId)) return;
  52. postIds.push(postId);
  53. postDiv.setAttribute('wire:key', wireKey++);
  54. if (postDiv.classList.contains('mt-0')) {
  55. postDiv.classList.remove('mt-0');
  56. postDiv.classList.add('mt-1');
  57. }
  58. postList.appendChild(post);
  59. postCounters.forEach(pc => pc.innerText = +pc.innerText + 1);
  60. if (notify) {
  61. const n = new Notification(`LRC Live: New post in "${document.title}"`, { body: post.querySelector('div.post-body').innerText, icon: '/assets/images/letsrun-logo.png' });
  62. document.addEventListener('visibilitychange', () => {
  63. if (document.visibilityState === 'visible') n.close();
  64. });
  65. }
  66. });
  67. if (checkPosts.length === 20) checkPageNo++;
  68. } catch (err) { console.error(err); }
  69. }, 5000);
  70. } else {
  71. liveButton.innerText = 'Start LRC Live';
  72. window.clearInterval(interval);
  73. }
  74. });
  75. }