PreviewLinks

Preview links and show their titles with caching for 3 days

  1. // ==UserScript==
  2. // @name PreviewLinks
  3. // @namespace https://jirehlov.com
  4. // @version 0.2.8
  5. // @description Preview links and show their titles with caching for 3 days
  6. // @author Jirehlov
  7. // @include /^https?://(bangumi\.tv|bgm\.tv|chii\.in)/.*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. "use strict";
  14. const CACHE_PREFIX = "PLinksCache_";
  15. const CACHE_EXPIRATION = 7 * 24 * 60 * 60 * 1000;
  16. const getTitleFromLink = async link => {
  17. const linkURL = link.href;
  18. const cachedTitle = getCachedTitle(linkURL);
  19. if (cachedTitle !== null) {
  20. return cachedTitle;
  21. }
  22. try {
  23. let title = "";
  24. if (link.textContent === link.href) {
  25. const response = await fetch(linkURL);
  26. const data = await response.text();
  27. const parser = new DOMParser();
  28. const htmlDoc = parser.parseFromString(data, "text/html");
  29. const titleElement = htmlDoc.querySelector("h1.nameSingle a");
  30. const blogTitleElement = htmlDoc.querySelector("h1");
  31. const epTitleElement = htmlDoc.querySelector("h2.title");
  32. title = titleElement ? titleElement.textContent : "";
  33. const chineseName = titleElement ? titleElement.getAttribute("title") : "";
  34. if (chineseName && (link.href.includes("subject") || link.href.includes("ep"))) {
  35. title += title ? ` | ${ chineseName }` : chineseName;
  36. }
  37. if (link.href.includes("ep") && epTitleElement) {
  38. epTitleElement.querySelectorAll("small").forEach(small => small.remove());
  39. const epTitle = epTitleElement.textContent.trim();
  40. title += title ? ` | ${ epTitle }` : epTitle;
  41. }
  42. if ((link.href.includes("blog") || link.href.includes("topic") || link.href.includes("index")) && blogTitleElement) {
  43. title = blogTitleElement.textContent.trim();
  44. }
  45. const prefix = link.href.match(/\/(subject\/topic|subject|ep|character|person|blog|group\/topic|index)\/[^/]+/);
  46. const prefixText = prefix ? `[${ prefix[1] }] ` : "";
  47. title = prefixText + title;
  48. cacheTitle(linkURL, title);
  49. return title;
  50. } else {
  51. return null;
  52. }
  53. } catch (error) {
  54. console.error("Error fetching data:", error);
  55. return null;
  56. }
  57. };
  58. const cacheTitle = (url, title) => {
  59. const cacheKey = CACHE_PREFIX + url;
  60. const cacheValue = {
  61. title: title,
  62. timestamp: Date.now()
  63. };
  64. localStorage.setItem(cacheKey, JSON.stringify(cacheValue));
  65. };
  66. const getCachedTitle = url => {
  67. const cacheKey = CACHE_PREFIX + url;
  68. const cacheValue = localStorage.getItem(cacheKey);
  69. if (cacheValue !== null) {
  70. const {title, timestamp} = JSON.parse(cacheValue);
  71. if (Date.now() - timestamp < CACHE_EXPIRATION) {
  72. return title;
  73. } else {
  74. localStorage.removeItem(cacheKey);
  75. }
  76. }
  77. return null;
  78. };
  79. const domains = [
  80. "bgm.tv",
  81. "chii.in",
  82. "bangumi.tv"
  83. ];
  84. const paths = [
  85. "subject/",
  86. "ep/",
  87. "character/",
  88. "person/",
  89. "blog/",
  90. "group/topic/",
  91. "index/"
  92. ];
  93. const selectors = [];
  94. domains.forEach(domain => {
  95. paths.forEach(path => {
  96. selectors.push(`a[href^="https://${ domain }/${ path }"]`);
  97. });
  98. });
  99. const allLinks = document.querySelectorAll(selectors.join(","));
  100. const fetchAndReplaceLinkText = () => {
  101. let index = 0;
  102. const processNextLink = async () => {
  103. if (index < allLinks.length) {
  104. const link = allLinks[index];
  105. const cachedTitle = getCachedTitle(link.href);
  106. if (link.textContent === link.href) {
  107. if (cachedTitle) {
  108. link.textContent = cachedTitle;
  109. } else {
  110. const title = await getTitleFromLink(link);
  111. if (title) {
  112. link.textContent = title;
  113. }
  114. setTimeout(processNextLink, 3000);
  115. return;
  116. }
  117. }
  118. index++;
  119. processNextLink();
  120. }
  121. };
  122. processNextLink();
  123. };
  124. fetchAndReplaceLinkText();
  125. }());