SampleFocus Direct API Downloader

Download samples from SampleFocus without using credits by accessing the API directly

  1. // ==UserScript==
  2. // @name SampleFocus Direct API Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Download samples from SampleFocus without using credits by accessing the API directly
  6. // @author You
  7. // @match https://samplefocus.com/samples/*
  8. // @grant GM_addStyle
  9. // @grant GM_xmlhttpRequest
  10. // @connect *
  11. // @run-at document-idle
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Add custom CSS for our download button
  18. GM_addStyle(`
  19. .api-download-btn {
  20. background-color: #4CAF50;
  21. color: white;
  22. padding: 10px 15px;
  23. border: none;
  24. border-radius: 4px;
  25. cursor: pointer;
  26. font-weight: bold;
  27. margin-right: 10px;
  28. display: inline-block;
  29. }
  30. .api-download-btn:hover {
  31. background-color: #45a049;
  32. }
  33. #download-status {
  34. margin-top: 10px;
  35. padding: 10px;
  36. border-radius: 4px;
  37. display: none;
  38. }
  39. .status-success {
  40. background-color: #dff0d8;
  41. color: #3c763d;
  42. }
  43. .status-error {
  44. background-color: #f2dede;
  45. color: #a94442;
  46. }
  47. .status-info {
  48. background-color: #d9edf7;
  49. color: #31708f;
  50. }
  51. #debug-info {
  52. margin-top: 10px;
  53. padding: 10px;
  54. background-color: #f5f5f5;
  55. border: 1px solid #ddd;
  56. border-radius: 4px;
  57. font-family: monospace;
  58. font-size: 12px;
  59. white-space: pre-wrap;
  60. display: none;
  61. }
  62. `);
  63.  
  64. // Function to extract sample ID from URL
  65. function getSampleIdFromURL() {
  66. const path = window.location.pathname;
  67. const match = path.match(/\/samples\/([^\/]+)/);
  68. return match ? match[1] : null;
  69. }
  70.  
  71. // Function to extract sample name from URL
  72. function getSampleNameFromURL() {
  73. const path = window.location.pathname;
  74. const parts = path.split('/');
  75. const sampleName = parts[parts.length - 1] || 'sample';
  76. return sampleName.replace(/-/g, '_');
  77. }
  78.  
  79. // Function to create a status message element
  80. function createStatusElement() {
  81. const statusElement = document.createElement('div');
  82. statusElement.id = 'download-status';
  83. return statusElement;
  84. }
  85.  
  86. // Function to create a debug info element
  87. function createDebugElement() {
  88. const debugElement = document.createElement('div');
  89. debugElement.id = 'debug-info';
  90. return debugElement;
  91. }
  92.  
  93. // Function to show status message
  94. function showStatus(message, type = 'success') {
  95. const statusElement = document.getElementById('download-status') || createStatusElement();
  96. statusElement.textContent = message;
  97.  
  98. // Remove all status classes
  99. statusElement.classList.remove('status-success', 'status-error', 'status-info');
  100.  
  101. // Add appropriate class
  102. switch(type) {
  103. case 'error':
  104. statusElement.classList.add('status-error');
  105. break;
  106. case 'info':
  107. statusElement.classList.add('status-info');
  108. break;
  109. default:
  110. statusElement.classList.add('status-success');
  111. }
  112.  
  113. statusElement.style.display = 'block';
  114.  
  115. // If not already in the DOM, add it
  116. if (!document.getElementById('download-status')) {
  117. const downloadButton = document.querySelector('.api-download-btn');
  118. if (downloadButton && downloadButton.parentNode) {
  119. downloadButton.parentNode.appendChild(statusElement);
  120. }
  121. }
  122. }
  123.  
  124. // Function to show debug info
  125. function showDebugInfo(info) {
  126. let debugElement = document.getElementById('debug-info');
  127. if (!debugElement) {
  128. debugElement = createDebugElement();
  129. const statusElement = document.getElementById('download-status');
  130. if (statusElement && statusElement.parentNode) {
  131. statusElement.parentNode.appendChild(debugElement);
  132. }
  133. }
  134.  
  135. debugElement.textContent = typeof info === 'object' ? JSON.stringify(info, null, 2) : info;
  136. debugElement.style.display = 'block';
  137. }
  138.  
  139. // Function to extract audio source from the page
  140. function extractAudioSource() {
  141. // Try to get the audio source from the audio element
  142. const audioElement = document.querySelector('audio');
  143. if (audioElement && audioElement.src) {
  144. return audioElement.src;
  145. }
  146.  
  147. // Try to find it in the page's HTML
  148. const pageHtml = document.documentElement.outerHTML;
  149.  
  150. // Look for audio URLs in script tags (often contains player configuration)
  151. const scriptTags = document.querySelectorAll('script');
  152. for (const script of scriptTags) {
  153. if (script.textContent) {
  154. const urlMatch = script.textContent.match(/"(https?:\/\/[^"]+\.(mp3|wav|ogg))"/i);
  155. if (urlMatch && urlMatch[1]) {
  156. return urlMatch[1];
  157. }
  158. }
  159. }
  160.  
  161. // Look for audio URLs in the page HTML
  162. const audioUrlMatch = pageHtml.match(/(https?:\/\/[^"']+\.(mp3|wav|ogg))/i);
  163. if (audioUrlMatch && audioUrlMatch[1]) {
  164. return audioUrlMatch[1];
  165. }
  166.  
  167. return null;
  168. }
  169.  
  170. // Function to get the download URL directly from the API
  171. function getDownloadUrlFromAPI() {
  172. const sampleId = getSampleIdFromURL();
  173. if (!sampleId) {
  174. showStatus('Could not determine the sample ID from the URL.', 'error');
  175. return Promise.reject('No sample ID found');
  176. }
  177.  
  178. showStatus('Fetching download URL from API...', 'info');
  179.  
  180. // Construct the API URL
  181. const apiUrl = `https://samplefocus.com/api/samples/${sampleId}/play`;
  182.  
  183. return new Promise((resolve, reject) => {
  184. GM_xmlhttpRequest({
  185. method: 'GET',
  186. url: apiUrl,
  187. headers: {
  188. 'Accept': 'application/json',
  189. 'X-Requested-With': 'XMLHttpRequest'
  190. },
  191. onload: function(response) {
  192. try {
  193. if (response.status === 200) {
  194. const data = JSON.parse(response.responseText);
  195. showDebugInfo(data);
  196.  
  197. if (data && data.url) {
  198. resolve(data.url);
  199. } else {
  200. showStatus('API response did not contain a download URL.', 'error');
  201. reject('No URL in API response');
  202. }
  203. } else {
  204. showStatus(`API request failed with status: ${response.status}`, 'error');
  205. reject(`API request failed: ${response.statusText}`);
  206. }
  207. } catch (error) {
  208. showStatus(`Error parsing API response: ${error.message}`, 'error');
  209. reject(`Parse error: ${error.message}`);
  210. }
  211. },
  212. onerror: function(error) {
  213. showStatus('Error making API request.', 'error');
  214. reject(`API request error: ${error.error}`);
  215. }
  216. });
  217. });
  218. }
  219.  
  220. // Function to download audio using GM_xmlhttpRequest
  221. function downloadAudio(url) {
  222. showStatus(`Downloading audio from: ${url}`, 'info');
  223.  
  224. GM_xmlhttpRequest({
  225. method: 'GET',
  226. url: url,
  227. responseType: 'blob',
  228. headers: {
  229. 'Referer': 'https://samplefocus.com/',
  230. 'Origin': 'https://samplefocus.com'
  231. },
  232. onload: function(response) {
  233. if (response.status === 200) {
  234. // Create a blob URL from the response
  235. const blob = response.response;
  236. const blobUrl = URL.createObjectURL(blob);
  237.  
  238. // Determine file extension from MIME type or URL
  239. let fileExtension = 'mp3';
  240. if (blob.type.includes('wav')) {
  241. fileExtension = 'wav';
  242. } else if (blob.type.includes('ogg')) {
  243. fileExtension = 'ogg';
  244. }
  245.  
  246. // Create a download link
  247. const a = document.createElement('a');
  248. a.href = blobUrl;
  249. a.download = getSampleNameFromURL() + '.' + fileExtension;
  250. document.body.appendChild(a);
  251. a.click();
  252.  
  253. // Clean up
  254. setTimeout(() => {
  255. document.body.removeChild(a);
  256. URL.revokeObjectURL(blobUrl);
  257. }, 100);
  258.  
  259. showStatus('Download completed! Check your downloads folder.');
  260. } else {
  261. showStatus(`Failed to download audio: ${response.status} ${response.statusText}`, 'error');
  262. showDebugInfo({
  263. status: response.status,
  264. statusText: response.statusText,
  265. headers: response.responseHeaders,
  266. url: url
  267. });
  268. }
  269. },
  270. onerror: function(error) {
  271. showStatus(`Error downloading audio: ${error.error || 'Unknown error'}`, 'error');
  272. showDebugInfo(error);
  273. }
  274. });
  275. }
  276.  
  277. // Function to handle the download button click
  278. async function handleDownloadClick() {
  279. try {
  280. // First try to get the download URL from the API
  281. const downloadUrl = await getDownloadUrlFromAPI();
  282. if (downloadUrl) {
  283. downloadAudio(downloadUrl);
  284. return;
  285. }
  286. } catch (apiError) {
  287. console.error('API method failed:', apiError);
  288. showStatus('API method failed, trying alternative methods...', 'info');
  289. }
  290.  
  291. // If API method fails, try to extract the audio source from the page
  292. const audioSrc = extractAudioSource();
  293. if (audioSrc) {
  294. downloadAudio(audioSrc);
  295. } else {
  296. showStatus('Could not find any audio source. Try playing the audio first.', 'error');
  297.  
  298. // Try playing the audio to make its source available
  299. const audioElement = document.querySelector('audio');
  300. if (audioElement) {
  301. audioElement.play();
  302.  
  303. // Check again after a short delay
  304. setTimeout(() => {
  305. const newAudioSrc = extractAudioSource();
  306. if (newAudioSrc) {
  307. downloadAudio(newAudioSrc);
  308. } else {
  309. showStatus('Still could not find the audio source. Please try again later.', 'error');
  310. }
  311. }, 2000);
  312. } else {
  313. showStatus('Could not find the audio player.', 'error');
  314. }
  315. }
  316. }
  317.  
  318. // Function to create our custom download button
  319. function createDownloadButton() {
  320. // Find the original download button to place our button next to it
  321. const originalDownloadButton = document.querySelector('a[href$="/download"]');
  322.  
  323. if (!originalDownloadButton) {
  324. console.error('Original download button not found');
  325. return;
  326. }
  327.  
  328. // Create our custom button
  329. const downloadBtn = document.createElement('a');
  330. downloadBtn.className = 'api-download-btn';
  331. downloadBtn.textContent = 'Download Without Credits';
  332. downloadBtn.href = '#';
  333. downloadBtn.addEventListener('click', function(e) {
  334. e.preventDefault();
  335. handleDownloadClick();
  336. });
  337.  
  338. // Insert our button before the original download button
  339. originalDownloadButton.parentNode.insertBefore(downloadBtn, originalDownloadButton);
  340.  
  341. // Create the status element
  342. const statusElement = createStatusElement();
  343. originalDownloadButton.parentNode.appendChild(statusElement);
  344. }
  345.  
  346. // Initialize the script
  347. function initialize() {
  348. console.log('SampleFocus Direct API Downloader initializing...');
  349.  
  350. // Create the download button
  351. createDownloadButton();
  352.  
  353. console.log('SampleFocus Direct API Downloader initialized');
  354. }
  355.  
  356. // Wait for the page to fully load before initializing
  357. window.addEventListener('load', function() {
  358. // Give a little extra time for any dynamic content to load
  359. setTimeout(initialize, 2000);
  360. });
  361.  
  362. })();