Fetch Download Links on FreeSound.org

Adds download links on freesound.org. Works with a button press, or automatically.

  1. // ==UserScript==
  2. // @name Fetch Download Links on FreeSound.org
  3. // @namespace Violentmonkey Scripts
  4. // @version 1.0.1
  5. // @description Adds download links on freesound.org. Works with a button press, or automatically.
  6. // @author Jupiter Liar
  7. // @license CC BY
  8. // @match https://freesound.org/*
  9. // @description 4/23/2024, 10:30:00 AM
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. // Function to handle grabbing HTML from linked page and replacing Grab button
  14. function grabHtmlAndReplaceButton(button) {
  15. button.textContent = "Fetching...";
  16. // Find the parent search result element
  17. const searchResult = button.closest('.bw-search__result');
  18.  
  19. // Find the first <a> element within the search result if available
  20. var link;
  21. if (searchResult) {
  22. link = searchResult.querySelector('a');
  23. } else {
  24. // If searchResult is not found, traverse up the DOM tree until we find an ancestor containing .bw-player
  25. var ancestor = button.parentElement;
  26. while (ancestor && !(ancestor.querySelector('.bw-player') && ancestor.querySelector('a'))) {
  27. ancestor = ancestor.parentElement;
  28. }
  29. // Once we find the ancestor containing .bw-player, find the first <a> element within it
  30. if (ancestor) {
  31. link = ancestor.querySelector('a');
  32. }
  33. }
  34.  
  35. // Check if a link is found
  36. if (link) {
  37. // Fetch the linked page
  38. fetch(link.href)
  39. .then(response => response.text())
  40. .then(html => {
  41. // Create a temporary element to hold the fetched HTML
  42. const tempElement = document.createElement('div');
  43. tempElement.innerHTML = html;
  44. // Find the element with class "sound-download-button"
  45. const soundDownloadButton = tempElement.querySelector('.sound-download-button');
  46. const sidebarDownloadButton = tempElement.querySelector('.bw-sound__sidebar .btn-primary');
  47. // Replace the Grab button with the sound-download-button element
  48. if (soundDownloadButton) {
  49. // Replace the Grab button with the sound-download-button element
  50. button.parentNode.replaceChild(soundDownloadButton, button);
  51. } else if (sidebarDownloadButton) {
  52. button.parentNode.replaceChild(sidebarDownloadButton, button);
  53. } else {
  54. console.log('No download button found on the linked page');
  55. // Update the button text content if no download button is found
  56. button.innerText = "No button found";
  57. }
  58. })
  59. .catch(error => {
  60. console.error('Error fetching linked page:', error);
  61. button.textContent = "Error fetching page";
  62. });
  63. } else {
  64. console.log('No link found in search result');
  65. }
  66. }
  67.  
  68. // Function to create and append grab buttons
  69. function createGrabButtons(parentElement, grabMode, additionalClass) {
  70. // If grabMode is true, insert grab buttons
  71. if (grabMode) {
  72. const grabberDiv = document.createElement('div');
  73. grabberDiv.classList.add('grabber');
  74. if (additionalClass) {
  75. grabberDiv.classList.add(additionalClass); // Add additional class if it exists
  76. }
  77. // grabberDiv.classList.add('in-row'); // Add in-row class
  78. // Create the Grab button
  79. const grabButton = document.createElement('button');
  80. grabButton.classList.add('grab-button');
  81. grabButton.textContent = 'Get download button';
  82.  
  83. // Add event listener to the Grab button
  84. grabButton.addEventListener('click', function () {
  85. grabHtmlAndReplaceButton(this);
  86. });
  87.  
  88. // Append the Grab button to the grabber div
  89. grabberDiv.appendChild(grabButton);
  90.  
  91. // Append the grabber div to the parent element
  92. if (parentElement.classList.contains('bw-search__result')) {
  93. // If parentElement has class bw-search__result, append the grabberDiv as the second to last element
  94. const lastChild = parentElement.lastElementChild;
  95. parentElement.insertBefore(grabberDiv, lastChild);
  96. } else {
  97. // Else, appendChild normally
  98. parentElement.appendChild(grabberDiv);
  99. }
  100. }
  101. }
  102.  
  103. function createInsertAllButton(topLevelElement) {
  104. // Create the container for "Insert all download buttons" button
  105. const insertAllContainer = document.createElement('div');
  106. insertAllContainer.classList.add('show-all-download-buttons');
  107.  
  108. // Create the "Insert all download buttons" button
  109. const insertAllButton = document.createElement('button');
  110. insertAllButton.classList.add('btn-primary', 'w-100', 'insert-all-download-buttons');
  111. insertAllButton.textContent = 'Insert all download buttons';
  112.  
  113. // Add event listener to the "Insert all download buttons" button
  114. insertAllButton.addEventListener('click', function () {
  115. clickAllGrabButtons();
  116. });
  117.  
  118. // Append the button to the container
  119. insertAllContainer.appendChild(insertAllButton);
  120.  
  121. // Insert the container before the very first search result
  122. topLevelElement.parentNode.insertBefore(insertAllContainer, topLevelElement);
  123.  
  124. // Create the div for auto-insert option
  125. const autoInsertDiv = document.createElement('div');
  126. autoInsertDiv.classList.add('auto-insert-div');
  127.  
  128. // Create the checkbox for auto-insert option
  129. const autoInsertCheckbox = document.createElement('input');
  130. autoInsertCheckbox.type = 'checkbox';
  131. autoInsertCheckbox.checked = localStorage.getItem('autoInsertCheckbox') === 'true'; // Check local storage for value
  132.  
  133. // Add event listener to the auto-insert checkbox
  134. autoInsertCheckbox.addEventListener('change', handleAutoInsertChange);
  135.  
  136. // Create the span for auto-insert option
  137. const autoInsertSpan = document.createElement('span');
  138. autoInsertSpan.textContent = 'Do this automatically';
  139.  
  140. // Append checkbox and span to auto-insert div
  141. autoInsertDiv.appendChild(autoInsertCheckbox);
  142. autoInsertDiv.appendChild(autoInsertSpan);
  143.  
  144. // Append auto-insert div to show-all-download-buttons container
  145. insertAllContainer.appendChild(autoInsertDiv);
  146.  
  147. // Check if the checkbox is checked
  148. if (autoInsertCheckbox.checked) {
  149. clickAllGrabButtons();
  150. }
  151. }
  152.  
  153.  
  154. // Check if the current URL includes freesound.org/search
  155. if (window.location.href.includes("freesound.org/search")) {
  156. console.log('URL matched: freesound.org/search');
  157.  
  158. // Find all instances of '.bw-search__result' within the 'main' element
  159. const searchResults = document.querySelectorAll('main .bw-search__result');
  160.  
  161. // Loop through each search result
  162. searchResults.forEach(result => {
  163. // Find the first <a> element within the search result
  164. const firstLink = result.querySelector('a');
  165.  
  166. // Check if a <a> element is found
  167. if (firstLink) {
  168. // Print the href attribute of the first <a> element to the console
  169. console.log('First link href:', firstLink.href);
  170.  
  171. // If grabMode is true, insert a new div at the end of the search result
  172. createGrabButtons(result, true, '');
  173. } else {
  174. console.log('No link found in search result');
  175. }
  176. });
  177.  
  178. // Find the very first search result element
  179. const firstSearchResult = document.querySelector('main .bw-search__result:first-child');
  180.  
  181. // Check if the first search result element is found
  182. if (firstSearchResult) {
  183. createInsertAllButton(firstSearchResult);
  184. } else {
  185. console.log('No search results found');
  186. }
  187. } else {
  188. console.log('URL did not match: freesound.org/search');
  189.  
  190. // Check if the page includes one or more instances of .bw-player
  191. const bwPlayers = document.querySelectorAll('.bw-player');
  192. if (bwPlayers.length >= 1) {
  193. console.log('One or more instances of .bw-player were found:', bwPlayers.length);
  194.  
  195. // Initialize an array to store common ancestors
  196. var commonAncestors = [];
  197.  
  198. // Loop through each .bw-player instance
  199. bwPlayers.forEach(player => {
  200. // Initialize an array to store ancestors of the current .bw-player instance
  201. var ancestors = [];
  202.  
  203. // Find the parent element of the .bw-player instance
  204. const parentElement = player.parentNode;
  205. // Create and append grab buttons
  206. createGrabButtons(parentElement, true, 'in-row');
  207.  
  208. // Traverse up the DOM tree and store ancestors until reaching the document body
  209. var ancestor = player.parentNode;
  210. while (ancestor !== document.body) {
  211. ancestors.push(ancestor);
  212. ancestor = ancestor.parentNode;
  213. }
  214.  
  215. // Add ancestors of the current .bw-player instance to the common ancestors array
  216. commonAncestors = commonAncestors.length === 0 ? ancestors : commonAncestors.filter(ancestor => ancestors.includes(ancestor));
  217. });
  218.  
  219. // Find the lowest-level common ancestor
  220. console.log("commonAncestors: ");
  221. commonAncestors.forEach(ancestor => {
  222. console.log(ancestor);
  223. });
  224.  
  225. function findLowestLevelCommonAncestor(commonAncestors) {
  226. var lowestCommonAncestor = commonAncestors[0]; // Initialize with the first element
  227.  
  228. // Iterate through the common ancestors array
  229. for (let i = 1; i < commonAncestors.length; i++) {
  230. const currentAncestor = commonAncestors[i];
  231. // Check if the current ancestor is deeper in the hierarchy than the current lowest common ancestor
  232. if (lowestCommonAncestor.contains(currentAncestor)) {
  233. lowestCommonAncestor = currentAncestor;
  234. }
  235. }
  236.  
  237. return lowestCommonAncestor;
  238. }
  239.  
  240. const lowestCommonAncestor = findLowestLevelCommonAncestor(commonAncestors);
  241. console.log('Lowest level common ancestor:', lowestCommonAncestor);
  242.  
  243. // Pass the lowest-level common ancestor to the createInsertAllButton function
  244. createInsertAllButton(lowestCommonAncestor);
  245. } else {
  246. console.log('No instances of .bw-player were found');
  247. }
  248. }
  249.  
  250. // Function to handle auto-insert checkbox change
  251. function handleAutoInsertChange() {
  252. // Get the auto-insert checkbox
  253. const autoInsertCheckbox = document.querySelector('.auto-insert-div input[type="checkbox"]');
  254.  
  255. // Update the local storage value
  256. localStorage.setItem('autoInsertCheckbox', autoInsertCheckbox.checked);
  257.  
  258. // Check if the checkbox is checked
  259. if (autoInsertCheckbox.checked) {
  260. clickAllGrabButtons();
  261. }
  262. }
  263.  
  264. // Function to click all grab buttons
  265. function clickAllGrabButtons() {
  266. const grabButtons = document.querySelectorAll('.grab-button');
  267. grabButtons.forEach(button => {
  268. grabHtmlAndReplaceButton(button);
  269. });
  270. }
  271.  
  272. // Add stylesheet to the page head
  273. const style = document.createElement('style');
  274. style.id = 'new-script-css';
  275. style.innerHTML = `
  276. .grabber {
  277. width: 100%;
  278. margin: 0 0 12px;
  279. text-align: center;
  280. }
  281.  
  282. .grabber .grab-button {
  283. background: #CCC;
  284. padding: 4px 8px;
  285. border: 1px solid black;
  286. border-radius: 1em;
  287. }
  288.  
  289. .grabber.in-row {
  290. margin-top: 12px;
  291. }
  292.  
  293. .grabber.in-row .grab-button {
  294. font-size: .75em;
  295. }
  296.  
  297. .grabber.in-row .btn-primary {
  298. padding: 9px 21px;
  299. }
  300.  
  301. .show-all-download-buttons {
  302. margin-bottom: 32px;
  303. text-align: center;
  304. --sadb-border-style: 1px solid hsla(0, 0%, 0%, .5);
  305. border-bottom: var(--sadb-border-style);
  306. border-top: var(--sadb-border-style);
  307. }
  308.  
  309. .insert-all-download-buttons {
  310. margin: 12px 0 6px;
  311. }
  312.  
  313. .auto-insert-div {
  314. margin-bottom: 12px;
  315. justify-content: center;
  316. align-items: center;
  317. display: flex;
  318. }
  319.  
  320. .auto-insert-div * {
  321. margin: 0 .35em;
  322. }
  323. `;
  324. document.head.appendChild(style);