RedGifs Downloader

Creates a sidebar button to download the currently playing gif in the currently selected quality.

  1. // ==UserScript==
  2. // @name RedGifs Downloader
  3. // @namespace burrito.scripts
  4. // @match http*://*.redgifs.com/*
  5. // @match http*://redgifs.com/*
  6. // @run-at document-idle
  7. // @grant none
  8. // @version 1.6
  9. // @author hunkyburrito
  10. // @description Creates a sidebar button to download the currently playing gif in the currently selected quality.
  11. // @homepage https://gist.github.com/hunkyburrito/f588fa77e75e29f9eeabcd24b21e35f8#file-redgif_downloader-js
  12. // @license GNU GPLv3
  13. // ==/UserScript==
  14.  
  15. // Object to store cached GIF information
  16. const gifCache = {};
  17.  
  18. async function getGif(gifId) {
  19. // Check if the GIF information exists in the cache
  20. if (gifCache[gifId]) {
  21. return gifCache[gifId]; // Return cached data if available
  22. }
  23.  
  24. // If not cached, fetch GIF info from the API
  25. const token = JSON.parse(localStorage.getItem('session_data')).token;
  26. let gifReq = await fetch(`https://api.redgifs.com/v2/gifs/${gifId}`, {
  27. method: 'GET',
  28. headers: {
  29. Authorization: `Bearer ${token}`
  30. }
  31. });
  32.  
  33. let gifInfo = await gifReq.json();
  34.  
  35. // Cache the fetched GIF information
  36. gifCache[gifId] = gifInfo;
  37.  
  38. return gifInfo;
  39. }
  40.  
  41. async function download(gifInfo) {
  42. let quality = localStorage.getItem('gifQuality');
  43. if (quality) {
  44. quality = quality.replace(/^"|"$/g, ''); // remove double quotes (i.e. '"hd"' => "hd")
  45. }
  46. let dlLink = gifInfo.gif.urls[quality ? quality : 'sd'];
  47.  
  48. // Fetch the video data
  49. let response = await fetch(dlLink);
  50. let data = await response.blob();
  51.  
  52. // Create a link element and trigger the download
  53. const link = document.createElement('a');
  54. link.href = URL.createObjectURL(data);
  55. link.download = `${gifInfo.gif.id}.mp4`;
  56. link.setAttribute('class', "download");
  57.  
  58. document.body.appendChild(link);
  59. link.click();
  60.  
  61. // Clean up
  62. document.body.removeChild(link);
  63. URL.revokeObjectURL(link.href);
  64. }
  65.  
  66. async function addButton (target, gifInfo) {
  67. // Check if the download button already exists for this GIF
  68. if (document.querySelector(`#DL_Btn_${gifInfo.gif.id}`)) {
  69. return;
  70. }
  71.  
  72. // Sidebar item and button
  73. let sb_itm = document.createElement('li')
  74. sb_itm.setAttribute('class', 'SideBar-Item')
  75. let dl_btn = document.createElement('button')
  76. dl_btn.setAttribute('class', 'DL_Btn')
  77. dl_btn.setAttribute('id', `DL_Btn_${gifInfo.gif.id}`); // Unique ID for each button
  78.  
  79. // Button icon
  80. let dl_icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  81. dl_icon.setAttribute('width', '24')
  82. dl_icon.setAttribute('height', '24')
  83. dl_icon.setAttribute('viewBox', '0 0 24 24')
  84. dl_icon.setAttribute('fill', 'white')
  85. dl_icon.innerHTML = "<path d='M11.29 15.71a1 1 0 0 0 .33.21 1 1 0 0 0 .76 0 1 1 0 0 0 .33-.21l3-3a1 1 0 0 0-1.42-1.42L13 12.59V9a1 1 0 0 0-2 0v3.59l-1.29-1.3a1 1 0 0 0-1.42 0 1 1 0 0 0 0 1.42zM12 22A10 10 0 1 0 2 12a10 10 0 0 0 10 10zm0-18a8 8 0 1 1-8 8 8 8 0 0 1 8-8z' stroke='currentColor' stroke-width='0.5' stroke-linecap='round' stroke-linejoin='round'></path>"
  86.  
  87. dl_btn.appendChild(dl_icon)
  88. sb_itm.appendChild(dl_btn)
  89.  
  90. let parent_node = await waitForElm('.SideBar', target)
  91. let sibling_node = await waitForElm('.FSButton', target)
  92.  
  93. // Copy styles of other button
  94. const styles = window.getComputedStyle(sibling_node);
  95. let cssText = styles.cssText;
  96. if (!cssText) {
  97. cssText = Array.from(styles).reduce((str, property) => {
  98. return `${str}${property}:${styles.getPropertyValue(property)};`;
  99. }, '');
  100. }
  101. dl_btn.style.cssText = cssText;
  102.  
  103. // Insert button into sidebar
  104. sibling_node = sibling_node.parentNode
  105. parent_node.insertBefore(sb_itm, sibling_node.nextSibling)
  106.  
  107. dl_btn.addEventListener('click', function(){ download(gifInfo) } )
  108. }
  109.  
  110. function waitForElm(selector, target=document) {
  111. return new Promise(resolve => {
  112. if (target.querySelector(selector)) {
  113. return resolve(target.querySelector(selector));
  114. }
  115.  
  116. const observer = new MutationObserver(mutations => {
  117. if (target.querySelector(selector)) {
  118. observer.disconnect();
  119. resolve(target.querySelector(selector));
  120. }
  121. });
  122.  
  123. observer.observe(target.body, {
  124. childList: true,
  125. subtree: true
  126. });
  127. });
  128. }
  129.  
  130. async function init() {
  131. let activeGif = null;
  132.  
  133. while (true) {
  134. // Wait for an active GIF element
  135. const gif = await waitForElm('.GifPreview_isActive');
  136.  
  137. // If the new active GIF is different from the previous one, update the button
  138. if (gif !== activeGif) {
  139. activeGif = gif;
  140. const info = await getGif(gif.id.split('_')[1]);
  141. addButton(gif, info);
  142. }
  143.  
  144. // Introduce a delay before the next check (e.g., 100 milliseconds)
  145. await new Promise(resolve => setTimeout(resolve, 30));
  146. }
  147. }
  148.  
  149. init();