SCP Tooltips

Add tooltips to SCP links on the wiki.

  1. // ==UserScript==
  2. // @name SCP Tooltips
  3. // @namespace alexchandel
  4. // @version 1
  5. // @description Add tooltips to SCP links on the wiki.
  6. // @author Alex Chandel
  7. // @match *://*.scpwiki.com/*
  8. // @match *://*.scp-wiki.net/*
  9. // @match *://*.scpwiki.wikidot.com/*
  10. // @grant GM.getValue
  11. // @grant GM.setValue
  12. // ==/UserScript==
  13. /* jshint esversion: 6 */
  14.  
  15. 'use strict';
  16.  
  17. /** -> Promise<Dict<path, name>> */
  18. function updateNames () {
  19. const re = new RegExp(' - (.+)')
  20. const reqs = ['', '-2', '-3', '-4', '-5', '-6'].map(
  21. (sfx, i) => fetch(`/scp-series${sfx}`)
  22. .then(rsp => rsp.text())
  23. .then(text => {
  24.  
  25. return Array.from((new DOMParser().parseFromString(text, 'text/html')).querySelectorAll('.series:first-of-type > ul'))
  26. .slice(1)
  27. .flatMap((ul, iUL) => Array.from(ul.querySelectorAll('li'))
  28. .map((li, iLI) => {
  29. const a = li.querySelector('a') // a.parentElement.removeChild(a)
  30. const m = re.exec(li.textContent.slice(a.textContent.length))
  31. if (m == null) {
  32. console.warn(`${i * 1000 + iUL * 100 + iLI}: ${li.innerHTML}`)
  33. return [a.pathname, a.textContent] // .innerText misses invisible text
  34. } else {
  35. return [a.pathname, m[1]]
  36. }
  37. })
  38. )
  39. })
  40. )
  41. return Promise.all(reqs).then(series => Object.fromEntries(series.flat()))
  42. }
  43.  
  44. /** Dict<path, name> -> () */
  45. function tipLinks (names) {
  46. for (const link of window.document.querySelectorAll('a:not([title])')) {
  47. const name = names[link.pathname]
  48. if (name != undefined) {
  49. link.title = name
  50. }
  51. }
  52. }
  53.  
  54. function updateAndTip () {
  55. updateNames().then(names => {
  56. GM.setValue('names', JSON.stringify(names)).then(
  57. _ => GM.setValue('lastupdate', Date.now())
  58. ).finally(tipLinks(names))
  59. })
  60. }
  61.  
  62. const DAYMS = 86400000
  63.  
  64. window.addEventListener('load', function() {
  65. GM.getValue('lastupdate', 0).then(
  66. lastupdate => {
  67. if (lastupdate < Date.now() - DAYMS * 7) {
  68. updateAndTip()
  69. } else {
  70. GM.getValue('names').then(
  71. names => tipLinks(JSON.parse(names)),
  72. error => GM.setValue('lastupdate', 0).then(_ => updateAndTip())
  73. )
  74. }
  75. }) // else catastrophic failure…
  76. })