Skip to content

Instantly share code, notes, and snippets.

@Nezteb
Last active October 31, 2023 22:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nezteb/5ba0025d89bc743581e9fceccdde9dc1 to your computer and use it in GitHub Desktop.
Save Nezteb/5ba0025d89bc743581e9fceccdde9dc1 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Download songs
// @description Download songs from a specific website
// @namespace Violentmonkey Scripts
// @match REDACTED
// @version 0.1
// @author Noah Betzen
// @grant none
// ==/UserScript==
const STORAGE_KEY = `seen`
(function() {
`use strict`;
const seenString = localStorage.getItem(STORAGE_KEY);
let seen = seenString ? JSON.parse(seenString) : {};
const count = Object.keys(seen).length;
if (count > 0) {
console.log(`Seen URLs: ${count}`, seen);
}
const originalFetch = fetch;
const queue = new DownloadQueue(originalFetch, 5);
window.fetch = async function() {
let response = await originalFetch.apply(this, arguments);
// This is async but we don't await it on purpose so that the response returns quickly
processNewData(response.clone(), seen, queue);
return response;
};
})();
async function processNewData(resp, seen, queue) {
const responseData = await resp.json();
responseData?.result?.servings?.forEach(async (serving) => {
let originalDownloadUrl = serving.trackVariation?.tokenedUrl;
if (originalDownloadUrl) {
// Key url is stripped of query/hash params
const url = new URL(originalDownloadUrl);
const keyUrl = keyUrl.origin + keyUrl.pathname;
if (!seen[keyUrl]) {
console.log(`Unseen URL: ${keyUrl}`)
const proxyDownloadUrl = `https://corsproxy.io/?${encodeURIComponent(originalDownloadUrl)}`;
await queue.enqueue(originalDownloadUrl, proxyDownloadUrl, keyUrl);
seen[keyUrl] = true;
localStorage.setItem(STORAGE_KEY, JSON.stringify(seen));
}
}
});
}
class DownloadQueue {
constructor(originalFetch, concurrency) {
this.queue = [];
this.running = 0;
this.originalFetch = originalFetch;
this.concurrency = concurrency || 5;
}
// Add a file to the queue
async enqueue(originalDownloadUrl, proxyDownloadUrl, keyUrl) {
this.queue.push({
originalDownloadUrl,
proxyDownloadUrl,
keyUrl
});
await this.run();
}
// Process the queue
async run() {
if (this.running >= this.concurrency || this.queue.length === 0) {
return;
}
const {
originalDownloadUrl,
proxyDownloadUrl,
keyUrl
} = this.queue.shift();
this.running++;
try {
await downloadFile(this.originalFetch, originalDownloadUrl, proxyDownloadUrl, keyUrl)
this.running--;
this.run();
} catch (error) {
console.error(`Error occurred in download queue`, {
originalDownloadUrl,
proxyDownloadUrl,
keyUrl,
error
});
this.running--;
this.run();
}
}
}
async function downloadFile(originalFetch, originalDownloadUrl, proxyDownloadUrl, keyUrl) {
const filename = new URL(keyUrl).pathname.split(`/`).pop();
if (filename) {
const response = await originalFetch(proxyDownloadUrl);
if (!response.ok) {
console.error(`Failed to fetch (response not ok) ${keyUrl}`, response);
return;
}
const blob = await response.blob();
// Create an object URL from the Blob
const blobURL = URL.createObjectURL(blob);
// Use an anchor element to trigger the download
const a = document.createElement(`a`);
a.style.display = `none`;
document.body.appendChild(a);
a.href = blobURL;
a.download = filename;
a.click();
// Cleanup
document.body.removeChild(a);
URL.revokeObjectURL(blobURL);
console.log(`Successfully downloaded file`, {
filename,
proxyDownloadUrl
});
} else {
console.error(`Couldn not get filename for url ${keyUrl}`);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment