ao3 download buttons

Adds download buttons to each work blurb on AO3's works index pages.

  1. // ==UserScript==
  2. // @name ao3 download buttons
  3. // @description Adds download buttons to each work blurb on AO3's works index pages.
  4. // @namespace ao3
  5. // @include http*://archiveofourown.org/*works*
  6. // @include http*://archiveofourown.org/*bookmarks*
  7. // @include http*://archiveofourown.org/*readings*
  8. // @include http*://archiveofourown.org/series/*
  9. // @grant none
  10. // @version 2.4
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. const blurbs = Array.from(document.querySelectorAll('li.blurb'));
  15.  
  16. if (!blurbs.length) {
  17. return;
  18. }
  19.  
  20. const style = document.createElement('style');
  21.  
  22. style.innerHTML = `
  23. .blurb .download.actions {
  24. position: absolute;
  25. right: 0.5em;
  26. top: 2.2em;
  27. white-space: nowrap;
  28. }
  29.  
  30. .blurb .download .expandable {
  31. position: absolute;
  32. right: calc(100% + 0.5em);
  33. top: -0.5em;
  34. }
  35.  
  36. .blurb .download .expandable li {
  37. display: inline-block;
  38. margin: 0;
  39. }
  40.  
  41. @media only screen and (min-width: 800px) {
  42. .blurb .download.actions {
  43. right: 7em;
  44. top: 0.5em;
  45. }
  46. }
  47. `;
  48.  
  49. document.head.appendChild(style);
  50.  
  51. blurbs.forEach(blurb => {
  52. let workId;
  53. let title;
  54.  
  55. try {
  56. const titleLink = blurb.querySelector('.header.module .heading a');
  57.  
  58. title = titleLink.textContent.trim();
  59. workId = (titleLink.href.match(/\/works\/(\d+)\b/) || [])[1];
  60. } catch (ex) {
  61. }
  62. if (!workId) {
  63. console.log('[ao3 download buttons] - skipping non-downloadable blurb:', blurb);
  64. return;
  65. }
  66.  
  67. const formats = ['azw3', 'epub', 'mobi', 'pdf', 'html'];
  68. const tuples = formats
  69. .map(ext => [
  70. ext.toUpperCase(),
  71. `/downloads/${workId}/${encodeURIComponent(title)}.${ext}?updated_at=${Date.now()}`
  72. ]);
  73.  
  74. blurb.innerHTML += `
  75. <div class="download actions" aria-haspopup="true">
  76. <a href="#" class="collapsed">Download</a>
  77. <ul class="expandable secondary hidden">
  78. ${
  79. tuples.map(([label, href]) => `
  80. <li>
  81. <a href=${href}>
  82. ${label}
  83. </a>
  84. </li>
  85. `)
  86. .join('')
  87. }
  88. </ul>
  89. </div>
  90. `;
  91.  
  92. blurb.querySelector('.download.actions > a').addEventListener('click', ev => {
  93. const button = ev.currentTarget;
  94.  
  95. button.classList.toggle('collapsed');
  96. button.classList.toggle('expanded');
  97. button.parentNode
  98. .querySelector('.expandable')
  99. .classList.toggle('hidden');
  100.  
  101. ev.preventDefault();
  102. });
  103. });
  104. })();