// set all the global stuff let ip; let alertTime = 2; let adminPass = ""; const ERR_NO_ADMIN = "401"; // gonna use this later to refactor const VALID_FILE_EXT = ["mp3","flac","wav"]; const params = new URLSearchParams(location.search); let darkmodetemp = getCookie("darkmode"); if(darkmodetemp === "") { darkmodetemp = params.get("darkmode") } if (darkmodetemp === "true") { // i know this is gonna cause weird blinking // maybe the dark mode function should be loaded before any content, would that work? // NEW JS FILE ????? exciting stuff // im thinking a few new js files // you know like good design separating stuff // yeah but i need the getCookie function in both the darkmode.js and this one, so im gonna make a // getcookie.js toggleDark("None"); } async function alertText(text="Song Added!") { alertbox = document.getElementById("alert"); alertbox.innerHTML = text; await new Promise(r => setTimeout(r, alertTime*1000)); if (alertbox.innerHTML == text) { alertbox.innerHTML = "" } } // a lot of this is kinda waffly because i was trying to get // it to return the right stuff and javascript is asyrcronouse (boo) async function getFromServer(bodyInfo, source="",password=adminPass) { try{ if (bodyInfo != null) { // the currently set password is always included in every request bodyInfo["password"] = password; } // console.log(bodyInfo); const response = await fetch("http://"+ip+"/"+source, { method: "POST", body: JSON.stringify(bodyInfo), headers: { "Content-type": "application/json; charset=UTF-8" } }); const data = await response.json(); if (data == ERR_NO_ADMIN) { // im suprised i didn't comment on this already but this is kinda lame desing // its not wrong but you know // it is easy which i like // and it overrides any other non-async alerts which is nice alertText("Error: Admin restricted action") } return await data; } catch(e) { if (e == "TypeError: Failed to fetch"){ alertText("Error: Can't Connect to Server (is the ip set?)") } else if(e == "") { } else { alertText("Error: " + e); } const response=null; return response; } } //cookie reader is taken from internet because cookies ae too complicated for me //i still understand how it works though promise just i see no reason to write this from scratch function getCookie(cname) { let name = cname + "="; let decodedCookie = decodeURIComponent(document.cookie); let ca = decodedCookie.split(';'); for(let i = 0; i "; document.getElementById("playlist-mode").style.display = "block"; document.getElementById("songlist-mode").style.display = "none"; document.getElementById("settings-mode").style.display = "none"; generateVisualPlaylist(); } else if (buttonType == "se") { //SearchMode button document.getElementById("songlist").innerHTML = "

Search to find songs!

"; document.getElementById("playlist").innerHTML = ""; document.getElementById("playlist-mode").style.display = "none"; document.getElementById("songlist-mode").style.display = "block"; document.getElementById("settings-mode").style.display = "none"; } else if (buttonType == "st") { //Settings button document.getElementById("songlist").innerHTML = ""; document.getElementById("playlist").innerHTML = ""; document.getElementById("playlist-mode").style.display = "none"; document.getElementById("songlist-mode").style.display = "none"; document.getElementById("settings-mode").style.display = "block"; checkSettings() } else if (buttonType = "pm") { //Partymode toggle (in settings) await getFromServer({setting: "partymode-toggle"}, "settings") checkSettings(true) } } function searchSongsEnter(e) { if (e.keyCode == 13) { searchSongs(document.getElementById("songsearch").value) } } async function searchSongs(searchTerm){ document.getElementById("songlist").innerHTML = "" searchResults = await getFromServer({search:searchTerm},"search").then() //generate the visual song list for(var fileName in searchResults) { let currentSongInJSON = searchResults[fileName] let newItem = document.createElement("div"); newItem.className = "item"; newItem.id = fileName; newItem.tabIndex = 0; let image = document.createElement("img"); try { if (currentSongInJSON["art"] == null) { throw "no image lolz" } image.src = currentSongInJSON["art"]; } catch(err){ image.src = "./images/placeholder.png"; } image.id = String(fileName)+" image"; let head3 = document.createElement("h3"); head3.innerText = currentSongInJSON["title"]; let head4 = document.createElement("h4"); head4.innerText = currentSongInJSON["artist"]; newItem.appendChild(image); newItem.appendChild(head3); newItem.appendChild(head4); document.getElementById("songlist").appendChild(newItem); } if (JSON.stringify(searchResults)==JSON.stringify({})) { //display error if no results document.getElementById("songlist").innerHTML = "

We might not have that one...

"; } } function alertTimeEnter(e){ if (e.key == "Enter") { e.preventDefault(); alertTimeSet(document.getElementById("alerttimetextbox").value); } } function alertTimeSet(time) { alertTime = time; document.cookie = "alertTime="+alertTime+"; path=/;" alertText("Alerts stay on screen for " + alertTime.toString() + " seconds") } function ipSetEnter(e){ if (e.key == "Enter") { e.preventDefault(); ipSetter(document.getElementById("iptextbox").value) } } function ipSetter(){ ipBox = document.getElementById("iptextbox").value if (ipBox == "") { alertText("Your IP is set to "+ip) } else { if (ipBox.includes(":")) { port = ipBox.slice(ipBox.indexOf(":")+1) ip = ipBox; document.cookie = "ip="+ip+"; path=/;" alertText("Your IP is now set to "+ip.slice(0, ipBox.indexOf(":"))+" at port "+port) } else { ip = ipBox + ":19054" document.cookie = "ip="+ip+"; path=/;" alertText("Your IP is now set to "+ipBox+" at port 19054 (Default)") } } // anytime the server ip changes the qrcode should change to use it qrCodeGenerate() } function qrCodeGenerate() { let tempURL = "http://" + document.location.href.split("/")[2] + "/?ip=" + ip; document.getElementById("qrcode").innerHTML = "" new QRCode(document.getElementById("qrcode"), { text: tempURL, width: 256, height: 256, colorDark : "#000000", colorLight : "#eeeeee", correctLevel : QRCode.CorrectLevel.H }); } async function checkSettings(skipServer=false) { //check client stuff first so if the server doesn't exist it can still be changed and seen if (ip.slice(-5)=="19054") { // don't show the port if it is the default document.getElementById("iptextbox").value = ip.slice(0,-6) } else { document.getElementById("iptextbox").value = ip; } qrCodeGenerate() document.getElementById("alerttimetextbox").value = alertTime partyButtonState = document.getElementById("partymode-button").innerHTML; let nodeList = document.getElementById("admincheckholder").children // temporary for (let i=0; i"; playlist = await getFromServer(null, "playlist"); playlist = Object.values(playlist).map(obj => { const filename = Object.keys(obj)[0]; // Get the filename const songData = obj[filename]; // Get the song metadata return { filename, ...songData }; // Merge filename with song data }); if (playlist.length==0){ document.getElementById("playlist-alert").innerHTML = "Nothing's Queued..." } else { if (conditions=="skip-button") { playlist.shift() if (playlist.length==0){ document.getElementById("playlist-alert").innerHTML = "Nothing's Queued..." } } for (let i in playlist) { let fileName = playlist[i]["filename"] let newItem = document.createElement("div"); newItem.className = "item"; newItem.id = fileName; newItem.tabIndex = 0; let image = document.createElement("img"); try { if (playlist[i]["art"] == null) { throw "no image lolz" } image.src = playlist[i]["art"]; } catch(err){ image.src = "./images/placeholder.png"; } image.id = String(fileName)+" image"; let head3 = document.createElement("h3"); head3.innerText = playlist[i]["title"]; let head4 = document.createElement("h4"); head4.innerText=playlist[i]["artist"]; let head5 = document.createElement("h5"); let timeLeft =document.createElement("h5"); timeLeft.style.fontWeight = 100; try { if (i == 0) { head5.innerHTML="Playing"; if ((conditions != "skip-button")) { let mins = Math.floor(playlist[i]["time"]/60); let secs = Math.floor(playlist[i]["time"]%60); let durMins = Math.floor(playlist[i]["length"]/60); let durSecs = Math.floor(playlist[i]["length"]%60); timeLeft.innerHTML = mins.toString() +":"+ secs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}) + "/"+ durMins.toString()+":"+durSecs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}); } } }catch(err){ // i dont know why there's a try catch here but i'm leaving it i dont want to break something console.log(err) } let textdiv = document.createElement("div") textdiv.className="text" newItem.appendChild(image); textdiv.appendChild(head3); textdiv.appendChild(head4); textdiv.appendChild(timeLeft); textdiv.appendChild(head5); newItem.appendChild(textdiv); document.getElementById("playlist").appendChild(newItem); } } } async function submitSong(songid) { let returncode = await getFromServer({song: songid}, "songadd"); if(returncode == ERR_NO_ADMIN) { // right now the error is alerted in getFromServer, maybe will change that } else { alertText("Added to Queue"); } } function checkWhatSongWasClicked(e) { if(e.type == "click" || e.key == "Enter") { itemId = e.srcElement.id; if ((itemId.length-itemId.lastIndexOf("image") == 5) && itemId.lastIndexOf("image")!=-1) { itemId = itemId.slice(0,-6) } //i feel like later kristy won't apreciate this //one of my files was "file.MP3" so it didn't work //windows be like let filenameSep = itemId.split('.') if (VALID_FILE_EXT.includes(filenameSep[filenameSep.length-1].toLowerCase())) { submitSong(itemId); } } } function toggleDark(e) { let x = document.getElementById("test-body").classList if (!(x.contains("dark-mode"))) { document.cookie = "darkmode=true; path=/;"; document.getElementById("darkmode-button").innerHTML = "On"; x.add("dark-mode"); } else { document.cookie = "darkmode=false; path=/;"; document.getElementById("darkmode-button").innerHTML = "Off"; x.remove("dark-mode"); } } async function sha256(message) { // Encode the message as UTF-8 const msgBuffer = new TextEncoder().encode(message); // Hash the message const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); // Convert ArrayBuffer to hex string const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; } async function adminPassEnter(e) { if (e.key == "Enter") { e.preventDefault(); let enteredpass = document.getElementById("adminpasswordbox").value; if(enteredpass === "") { adminPass = ""; // an empty pass is technically meant to represent not having one // this isn't stritly necesarry but i dont wanna break anything that might depend on this being true } else { adminPass= await sha256(document.getElementById("adminpasswordbox").value); } alertText("Admin Password Updated"); } } async function submitPerms(e) { let tempData = {} tempData["PP"] = document.getElementById("playpausesettingcheckbox").checked; tempData["SK"] = document.getElementById("skipsongsettingcheckbox").checked; tempData["AS"] = document.getElementById("addsongsettingcheckbox").checked; tempData["PM"] = document.getElementById("partymodesettingcheckbox").checked; tempData["VOL"] = document.getElementById("volumechangesettingcheckbox").checked; let returncode = await getFromServer({"setting":"perms","admin":tempData},"settings"); if (returncode == ERR_NO_ADMIN || returncode == null) { // if you aren't allowed to check the box then toggle it again // its not perfect if you spam click, but it gets the point across to the user let clickedBox = e.srcElement; clickedBox.checked = !clickedBox.checked; } } let optionslist = [] //sets all de stuff for buttons document.addEventListener('keydown', function(e){ if (e.key == "/"){ document.getElementById("title").scrollIntoView(); document.getElementById("songsearch").select(); e.preventDefault() }}) document.getElementById("playlist-mode").style.display = "none"; document.getElementById("settings-mode").style.display = "none"; document.getElementById("volumerange").onchange = async function() { // there is no reason for this not to be a defined function // FIX THIS let returnValue = await getFromServer({setting:"volume",level:this.value}, "settings") if (returnValue == ERR_NO_ADMIN) { alertText("Error: Admin restricted action"); } else if (returnValue["volumePassed"] !=0) { // i forgot about this, i had to do this because it confused the crap out of me one time // vlc doesn't let you change the volume of nothing, which makes sense if you think about it alertText("Nothing is playing") document.getElementById("volumerange").value = -1 } else if (this.value == 0) { alertText("The volume is now set to 0 (Pause?)") } else { alertText("The volume is now set to " + this.value.toString()) } } //bit of a cheat code for clearing the alerts when they don't clear normally document.getElementById("title").addEventListener('click',function(){document.getElementById("alert").innerHTML = ""}) document.getElementById("settings-button").addEventListener('click',function(){controlButton("st")}); document.getElementById("play-pause-button").addEventListener('click', function(){controlButton("pp")}); document.getElementById("playlist-button").addEventListener('click', function(){controlButton("pl")}); document.getElementById("search-button").addEventListener('click', function(){controlButton("se")}); document.getElementById("skip-button").addEventListener('click',function(){controlButton("sk")}); document.getElementById("go-search").addEventListener('click', function(){searchSongs(document.getElementById("songsearch").value)}) document.getElementById("songsearch").addEventListener('keydown', function(e){searchSongsEnter(e)}); document.getElementById("iptextbox").addEventListener('keydown', function(e){ipSetEnter(e)}); document.getElementById("alerttimetextbox").addEventListener('keydown', function(e){alertTimeEnter(e)}); document.getElementById("adminpasswordbox").addEventListener('keydown',function(e){adminPassEnter(e)}); document.getElementById("admincheckholder").addEventListener('click',function(e){submitPerms(e)}); document.getElementById("partymode-button").addEventListener('click',function(){controlButton("pm")}) //sets the fact that clicking a song needs to return its id to the function to find it document.getElementById("songlist").addEventListener('keydown', function(e){checkWhatSongWasClicked(e)}); document.getElementById("songlist").addEventListener('click', function(e){checkWhatSongWasClicked(e)}); //makes the controls look mostly normal on all screens, best solution i could find, idk man let tempWidth = document.getElementById('controls').clientWidth; document.getElementById("controls").style.marginLeft = "-"+String(parseInt(tempWidth/2))+"px"; document.getElementById("darkmode-button").addEventListener('click',function(){toggleDark()}) //for my use case (my immediate family), they dont know how to set an ip //using this allows the creator of the link for, a qr code for example, to set the ip before distributing the code, and it would all work smoothly //example (http://192.168.1.100:8000/?ip=192.168.1.100:19054 sets the ip to the same host at the default port) //the port must be set manually using this method, but only has to be done once for the url that ends up being shared //tries the url first, then the cookie, then the default ip = params.get("ip") if (ip == null || ip=="") { ip=getCookie("ip") } if (ip==null || ip==""){ ip = "" } // saving the cookies (don't tell the EU) document.cookie = "ip="+ip+"; path=/;" alertTime = getCookie("alertTime") document.getElementById("alerttimetextbox").value = alertTime if (alertTime == "") { alertTime = 2; document.cookie = "alertTime="+alertTime+"; path=/;" } // this is the code that makes the qr code at the very start qrCodeGenerate()