Last active
December 17, 2024 15:00
-
-
Save FranciscoG/1eb040ff2de25fba98fb6524cded27a7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (turntable) { | |
const modal = ` | |
<div class='playlistFeatures'> | |
<p>Deep-Cut to Queup exporter</p> | |
<button type="button" class='exportPlaylist'>Export Playlist: <span id="exporting-playlist"></span></button> | |
<hr /> | |
<p>Experimental: Export all playlists at once</p> | |
<p>Warning: this can be slow and will lock up the UI while processing, do not do this if you are DJ-ing</p> | |
<button type="button" class='exportPlaylistAll'>Export All Playlists</button> | |
<div id="pluginTT-loading"> | |
<div class="pluginTT-spinner"></div> | |
</div> | |
</div> | |
`; | |
const addStyles = () => { | |
const css = ` | |
.pluggedTT { | |
position: absolute; | |
z-index: 10; | |
bottom: 5em; | |
padding: 0.5em; | |
border-radius: 0.5em; | |
background-color: rgba(255, 255, 255, 0.1); | |
color: #eee; | |
right: 5em; | |
} | |
.pluggedTT p { | |
margin-bottom: 5px; | |
} | |
.miniplayer .pluggedTT { | |
z-index: 1000; | |
background-color: rgba(0, 0, 0, 0.5); | |
right: 1em; | |
} | |
.pluggedTT input { | |
padding: 0.1em; | |
} | |
#pluginTT-loading { | |
display: none; | |
justify-content: center; | |
align-items: center; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
background-color: rgba(255,255,255,.2); | |
} | |
#pluginTT-loading.show { | |
display: flex; | |
} | |
.pluginTT-spinner { | |
display: inline-block; | |
width: 25px; | |
height: 25px; | |
border: 3px solid rgba(255,255,255,.3); | |
border-radius: 50%; | |
border-top-color: #fff; | |
animation: spin 1s ease-in-out infinite; | |
-webkit-animation: spin 1s ease-in-out infinite; | |
} | |
`; | |
/** | |
* Insert css into <head> | |
* remove existing style tag to make it easier to develop | |
*/ | |
const head = document.head || document.getElementsByTagName("head")[0]; | |
const style = document.createElement("style"); | |
const id = "tt-export-playlist"; | |
style.id = id; | |
document.getElementById(id)?.remove(); | |
head.appendChild(style); | |
style.appendChild(document.createTextNode(css)); | |
}; | |
function sleep(ms) { | |
return new Promise((resolve) => setTimeout(resolve, ms)); | |
} | |
function loadScript(url) { | |
return new Promise((resolve, reject) => { | |
const script = document.createElement("script"); | |
script.onload = resolve; | |
script.onerror = reject; | |
script.src = url; | |
document.head.appendChild(script); | |
}); | |
} | |
function makeData(sc = false) { | |
const songs = turntable.playlist.fileids | |
.map((id) => turntable.playlist.songsByFid[id]) | |
.filter((song) => { | |
if (!song?.sourceid) return false; | |
const isYT = song.source === "yt" || song.metadata.ytid; | |
const isSC = song.source === "sc" || song.metadata.scid; | |
if (sc && isSC) return true; | |
if (!sc && isYT) return true; | |
return false; | |
}) | |
.map((song) => { | |
if (sc) { | |
return `https://api.soundcloud.com/tracks/${song.sourceid}`; | |
} | |
return `https://www.youtube.com/watch?v=${song.sourceid}`; | |
}); | |
return songs.join("\n").trim(); | |
} | |
function updateExportingPlaylist() { | |
document.querySelector("#exporting-playlist").innerHTML = turntable.playlist.activePlaylist; | |
} | |
function isPlayListElem(elem) { | |
return elem.classList.contains("option") && elem.classList.contains("playlist"); | |
} | |
/** | |
* | |
* @param {Event} e | |
*/ | |
function onPlaylistClick(e) { | |
if ( | |
isPlayListElem(e.target) || | |
isPlayListElem(e.target.parentElement) || | |
isPlayListElem(e.target.parentElement.parentElement) || | |
isPlayListElem(e.target.parentElement.parentElement.parentElement) | |
) { | |
setTimeout(() => { | |
updateExportingPlaylist(); | |
}, 1000); | |
} | |
} | |
function switchPlaylist(name) { | |
return new Promise((resolve) => { | |
turntable.playlist.switchPlaylist(name).done(() => { | |
turntable.playlist.setActivePlaylist(name); | |
turntable.playlist.loadList().done(resolve); | |
}); | |
}); | |
} | |
const runTurntableCode = () => { | |
let pluggedTTContainer = document.querySelector(".pluggedTT"); | |
// remove old existing modal if there is one | |
if (pluggedTTContainer?.parentElement) { | |
pluggedTTContainer.parentElement.removeChild(pluggedTTContainer); | |
} | |
// re-create the modal | |
pluggedTTContainer = document.createElement("div"); | |
pluggedTTContainer.classList.add("pluggedTT"); | |
pluggedTTContainer.innerHTML = modal; | |
document.querySelector(".room-container").appendChild(pluggedTTContainer); | |
// set the current playlist name in the UI and also listen for playlist changes | |
// so that we can update the UI to show current playlist name | |
updateExportingPlaylist(pluggedTTContainer); | |
document.getElementById("queue-view").addEventListener("click", onPlaylistClick); | |
const loading = document.querySelector("#pluginTT-loading"); | |
// export SINGLE playlist | |
// this will generate a zip file containing two text files | |
// one for soundcloud and one for youtube | |
const exportPlaylistButton = pluggedTTContainer.querySelector(".exportPlaylist"); | |
if (exportPlaylistButton) { | |
exportPlaylistButton.addEventListener("click", async () => { | |
loading.classList.add("show"); | |
const zip = new JSZip(); | |
const playlistName = turntable.playlist.activePlaylist; | |
const scData = makeData(true); | |
if (scData) zip.file(`playlist-${playlistName}-sc.txt`, scData); | |
const ytData = makeData(false); | |
if (ytData) zip.file(`playlist-${playlistName}-yt.txt`, ytData); | |
const content = await zip.generateAsync({ type: "blob" }); | |
saveAs(content, `deepcut-fm-playlist-${playlistName}.zip`); | |
loading.classList.remove("show"); | |
}); | |
} | |
// export ALL playlists | |
// this could be slow depending on how many playlists and how large they are. | |
// It will lock up the UI while processing so user should not be DJ-ing and | |
// won't be able to chat. I purposedly included a 1 second delay between each | |
// playlist to not overload the api | |
const exportPlaylistAll = pluggedTTContainer.querySelector(".exportPlaylistAll"); | |
if (exportPlaylistAll) { | |
exportPlaylistAll.addEventListener("click", async () => { | |
loading.classList.add("show"); | |
const zip = new JSZip(); | |
for (const playlistName of turntable.playlist.playlists) { | |
await switchPlaylist(playlistName); | |
const scData = makeData(true); | |
if (scData) zip.file(`playlist-${playlistName}-sc.txt`, scData); | |
const ytData = makeData(false); | |
if (ytData) zip.file(`playlist-${playlistName}-yt.txt`, ytData); | |
await sleep(1000); | |
} | |
const content = await zip.generateAsync({ type: "blob" }); | |
saveAs(content, "deepcut-fm-all-playlists.zip"); | |
loading.classList.remove("show"); | |
}); | |
} | |
}; | |
const init = async () => { | |
addStyles(); | |
const oldRequire = window.require; | |
const oldDefine = window.define | |
window.require = null; | |
window.define = null; | |
await loadScript("https://www.unpkg.com/[email protected]/dist/jszip.min.js"); | |
await loadScript("https://unpkg.com/[email protected]/dist/FileSaver.min.js"); | |
window.require = oldRequire; | |
window.define = oldDefine; | |
const waitForTurntable = () => { | |
if ("turntable" in window) { | |
runTurntableCode(); | |
} else { | |
console.log("waiting for window.turntable"); | |
setTimeout(waitForTurntable, 500); | |
} | |
}; | |
waitForTurntable(); | |
}; | |
init(); | |
})(window.turntable); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment