added hashing, updated parts of readme to explain env file
also added some tabby stuff, but its not done yet
This commit is contained in:
parent
37945ccced
commit
4f39417852
5 changed files with 65 additions and 38 deletions
|
|
@ -59,7 +59,6 @@ changes visibility with JS-->
|
||||||
<p class="italic">Opposite of light mode</p>
|
<p class="italic">Opposite of light mode</p>
|
||||||
<button title="darkmode-button" id="darkmode-button">Off</button>
|
<button title="darkmode-button" id="darkmode-button">Off</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h2 for="iptextbox">Server IP:</h2>
|
<h2 for="iptextbox">Server IP:</h2>
|
||||||
<p class="italic">IP of the device running the song server</p>
|
<p class="italic">IP of the device running the song server</p>
|
||||||
|
|
@ -107,14 +106,13 @@ changes visibility with JS-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--All the buttons are down here but settings is just doing its own thing-->
|
<!--All the buttons are down here but settings is just doing its own thing-->
|
||||||
<img class="settings-button control-button" id="settings-button" src="./images/settings.png" alt="settings"></img>
|
|
||||||
<div id="controls" class="controls">
|
<div id="controls" class="controls">
|
||||||
<img class="control-button" id="playlist-button" src="./images/playlist.png" alt="Playlist"></img>
|
<img tabindex=0 class="control-button" id="playlist-button" src="./images/playlist.png" alt="Playlist"></img>
|
||||||
<img class="control-button" id="play-pause-button" src="./images/play-pause.png" alt="Play pause"></img>
|
<img tabindex=0 class="control-button" id="play-pause-button" src="./images/play-pause.png" alt="Play pause"></img>
|
||||||
<img class="control-button" id="skip-button" src="./images/skip.png" alt="Skip"></img>
|
<img tabindex=0 class="control-button" id="skip-button" src="./images/skip.png" alt="Skip"></img>
|
||||||
<img class="control-button" id="search-button" src="./images/search.png" alt="Search"></img>
|
<img tabindex=0 class="control-button" id="search-button" src="./images/search.png" alt="Search"></img>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<img tabindex=0 class="settings-button control-button" id="settings-button" src="./images/settings.png" alt="settings"></img>
|
||||||
<script src="/ext/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="/ext/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="scripts.js"></script>
|
<script src="scripts.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,12 @@ let adminPass = "";
|
||||||
const ERR_NO_ADMIN = "401"; // gonna use this later to refactor
|
const ERR_NO_ADMIN = "401"; // gonna use this later to refactor
|
||||||
const VALID_FILE_EXT = ["mp3","flac","wav"];
|
const VALID_FILE_EXT = ["mp3","flac","wav"];
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
|
||||||
let darkmodetemp = getCookie("darkmode");
|
let darkmodetemp = getCookie("darkmode");
|
||||||
|
darkmodetemp = params.get("darkmode")
|
||||||
|
if(darkmodetemp === "") {
|
||||||
|
}
|
||||||
if (darkmodetemp === "true") {
|
if (darkmodetemp === "true") {
|
||||||
// i know this is gonna cause weird blinking
|
// i know this is gonna cause weird blinking
|
||||||
// maybe the dark mode function should be loaded before any content, would that work?
|
// maybe the dark mode function should be loaded before any content, would that work?
|
||||||
|
|
@ -29,6 +34,7 @@ async function getFromServer(bodyInfo, source="",password=adminPass) {
|
||||||
// the currently set password is always included in every request
|
// the currently set password is always included in every request
|
||||||
bodyInfo["password"] = password;
|
bodyInfo["password"] = password;
|
||||||
}
|
}
|
||||||
|
// console.log(bodyInfo);
|
||||||
const response = await fetch("http://"+ip+"/"+source, {
|
const response = await fetch("http://"+ip+"/"+source, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(bodyInfo),
|
body: JSON.stringify(bodyInfo),
|
||||||
|
|
@ -327,6 +333,7 @@ async function submitSong(songid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function checkWhatSongWasClicked(e) {
|
function checkWhatSongWasClicked(e) {
|
||||||
|
if(e.type == "click" || e.key == "Enter") {
|
||||||
itemId = e.srcElement.id;
|
itemId = e.srcElement.id;
|
||||||
if ((itemId.length-itemId.lastIndexOf("image") == 5) && itemId.lastIndexOf("image")!=-1) {
|
if ((itemId.length-itemId.lastIndexOf("image") == 5) && itemId.lastIndexOf("image")!=-1) {
|
||||||
itemId = itemId.slice(0,-6)
|
itemId = itemId.slice(0,-6)
|
||||||
|
|
@ -340,6 +347,7 @@ function checkWhatSongWasClicked(e) {
|
||||||
submitSong(itemId);
|
submitSong(itemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleDark(e) {
|
function toggleDark(e) {
|
||||||
let x = document.getElementById("test-body").classList
|
let x = document.getElementById("test-body").classList
|
||||||
|
|
@ -355,11 +363,31 @@ function toggleDark(e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function adminPassEnter(e) {
|
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") {
|
if (e.key == "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
adminPass=document.getElementById("adminpasswordbox").value
|
let enteredpass = document.getElementById("adminpasswordbox").value;
|
||||||
alertText("Admin Password Updated")
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,6 +452,7 @@ document.getElementById("adminpasswordbox").addEventListener('keydown',function(
|
||||||
document.getElementById("admincheckholder").addEventListener('click',function(e){submitPerms(e)});
|
document.getElementById("admincheckholder").addEventListener('click',function(e){submitPerms(e)});
|
||||||
document.getElementById("partymode-button").addEventListener('click',function(){controlButton("pm")})
|
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
|
//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)});
|
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
|
//makes the controls look mostly normal on all screens, best solution i could find, idk man
|
||||||
|
|
@ -435,7 +464,6 @@ document.getElementById("darkmode-button").addEventListener('click',function(){t
|
||||||
//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
|
//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)
|
//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
|
//the port must be set manually using this method, but only has to be done once for the url that ends up being shared
|
||||||
let params = new URLSearchParams(location.search);
|
|
||||||
|
|
||||||
//tries the url first, then the cookie, then the default
|
//tries the url first, then the cookie, then the default
|
||||||
ip = params.get("ip")
|
ip = params.get("ip")
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from flask import Flask
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
import sqlite3 as sql
|
import sqlite3 as sql
|
||||||
import vlc,threading,time,random,argparse,dotenv,os
|
import vlc,threading,time,random,argparse,dotenv,os,hashlib
|
||||||
# Argparse Stuff
|
# Argparse Stuff
|
||||||
parser=argparse.ArgumentParser(description="Options for the Webby Bits")
|
parser=argparse.ArgumentParser(description="Options for the Webby Bits")
|
||||||
# parser.add_argument('-p','--port',help="Port to host on, not the same as the web (client) port",default='19054')
|
# parser.add_argument('-p','--port',help="Port to host on, not the same as the web (client) port",default='19054')
|
||||||
|
|
@ -14,7 +14,7 @@ portTheUserPicked=os.getenv("SERVER_PORT")
|
||||||
# This is not great design, and the whole "returning string codes" thing is something to add to the todo list
|
# This is not great design, and the whole "returning string codes" thing is something to add to the todo list
|
||||||
# I mean returning 200 when no return is necesary i think is fine but we'll see
|
# I mean returning 200 when no return is necesary i think is fine but we'll see
|
||||||
ERR_NO_ADMIN = "401"
|
ERR_NO_ADMIN = "401"
|
||||||
ADMIN_PASS = args.admin
|
ADMIN_PASS = hashlib.sha256(bytes(args.admin,'utf-8')).hexdigest()
|
||||||
if not(ADMIN_PASS):
|
if not(ADMIN_PASS):
|
||||||
ADMIN_PASS = None
|
ADMIN_PASS = None
|
||||||
# True = everyone, False = admin only. Change in client while in use.
|
# True = everyone, False = admin only. Change in client while in use.
|
||||||
|
|
|
||||||
25
readme.md
25
readme.md
|
|
@ -10,6 +10,7 @@ The main advantage compared to doing something similar using Spotify is that you
|
||||||
### Client Setup:
|
### Client Setup:
|
||||||
The client is a web application that can be hosted on any server, it need not be the same device running the music player.
|
The client is a web application that can be hosted on any server, it need not be the same device running the music player.
|
||||||
* If the app is being setup for a large group, you can distribute the url (via QR code, for example) with `?ip=YOURSERVERHOSTNAME:19054` set as an attribute after the url.
|
* If the app is being setup for a large group, you can distribute the url (via QR code, for example) with `?ip=YOURSERVERHOSTNAME:19054` set as an attribute after the url.
|
||||||
|
* You can also add `?darkmode=(true/false)` to set the default colour scheme, but this will be overwritten by the users saved choice in the cookie if they change it themselves
|
||||||
### Server Setup:
|
### Server Setup:
|
||||||
**Pre-setup:** If you want the songs to have art associated with them, it is all hosted on and retrieved from LastFM, and you will need to sign up for a developer app, and put your key in the database generator \
|
**Pre-setup:** If you want the songs to have art associated with them, it is all hosted on and retrieved from LastFM, and you will need to sign up for a developer app, and put your key in the database generator \
|
||||||
\
|
\
|
||||||
|
|
@ -22,14 +23,17 @@ webbyBits.py
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Place mp3 files in the `sound/` folder
|
1. Place mp3 files in the `sound/` folder
|
||||||
2. Open `databaseGenerator.py` and put your LastFM API key in at the top or at runtime using `-k APIKey` (*optional*)
|
2. Rename `example.env` to `.env` and...
|
||||||
3. Run `databaseGenerator.py`
|
- Set the location where your audio files are (Default: `./sound/`)
|
||||||
|
- Set the LastFM API key (Optional)
|
||||||
|
- Change the port of the webbybits server ("Default )
|
||||||
|
3. Run `databaseGenerator.py` (Will try to use LastFM API key)
|
||||||
* *The `databaseGenerator.py` will index all mp3 files, and save the information to `songDatabase.db`*
|
* *The `databaseGenerator.py` will index all mp3 files, and save the information to `songDatabase.db`*
|
||||||
* *If getting images, this process may take a long time with a large amount of mp3 files*
|
* *If getting images, this process may take a long time with a large amount of mp3 files*
|
||||||
4. Run `webbyBits.py`
|
4. Run `webbyBits.py`
|
||||||
* *The port can be customized at runtime using* `-p portNumber` *as an atribute*
|
* *The port can be customized by editing the `.env` file*
|
||||||
* *You can add an admin password at runtime with* `-a AdminPass` *as an atribute*
|
* *You can add an admin password at runtime with* `-a AdminPass` *as an atribute*
|
||||||
* ***NOTE: Do not reuse ANY password for this, it is 100% unsecure. The best option is just a random string you write down once***
|
* ***NOTE: Do not reuse ANY password for this, it is hashed but 100% unsecure. The best option is just a random string you write down once***
|
||||||
* This is intended for protecting certain features for small closed events, not for public security
|
* This is intended for protecting certain features for small closed events, not for public security
|
||||||
|
|
||||||
You can now connect with the client and use the app as normal. \
|
You can now connect with the client and use the app as normal. \
|
||||||
|
|
@ -42,25 +46,21 @@ These are specific details on each section of the app, and how to use them
|
||||||
- `sound/` contains all mp3 files by default
|
- `sound/` contains all mp3 files by default
|
||||||
- `databaseGenerator.py` scans through mp3 files and gets information about them
|
- `databaseGenerator.py` scans through mp3 files and gets information about them
|
||||||
- `Filename, Title, Artist, Art, Length` are all saved
|
- `Filename, Title, Artist, Art, Length` are all saved
|
||||||
- *If the title and artist are not in the mp3 metadata, it looks for a format of* `TITLE_ARTIST.mp3` *then of* `ARTIST - TITLE.mp3` *and otherwise defaults to the file name as the title, and no artist*
|
- *If the title and artist are not in the file metadata, it looks for a format of* `TITLE_ARTIST.mp3` *then of* `ARTIST - TITLE.mp3` *and otherwise defaults to the file name as the title, and no artist*
|
||||||
- Art is retrieved from LastFM
|
- Art is retrieved from LastFM
|
||||||
- Running with `--mode (update/new)` either updates the current database and adds new songs/removes deleted songs, or recreates the entire database (update is default, and is faster in art mode)
|
- Running with `--mode (update/new)` either updates the current database and adds new songs/removes deleted songs, or recreates the entire database (update is default, and is faster in art mode)
|
||||||
- Running with `--art (True/False)` retrieves art from LastFM or doesn't (True is default)
|
- Running with `--art (True/False)` retrieves art from LastFM or doesn't (True is default)
|
||||||
- *Can only generate one song / 0.25 seconds, to avoid pinging the LastFM server too much*
|
- *Can only generate one song / 0.25 seconds, to avoid pinging the LastFM server too much*
|
||||||
- Running with `--apikey (KEYhere)` sets the LastFM key for that run
|
- Directory to index for music files can be set in the `.env` file
|
||||||
- If this is set to an empty string (Default) the app runs in non-art mode
|
|
||||||
- Running with `--directory (directoryOfmp3s)` allows for sound files to be in a different place
|
|
||||||
- Default `"./sound/"`
|
|
||||||
- _This setting might be kinda iffy on Linux. You're on Linux just go and edit it if you have issues_
|
|
||||||
- `songDatabase.db` stores all the information about each song in a SQLite database with tables `songs` and `meta`
|
- `songDatabase.db` stores all the information about each song in a SQLite database with tables `songs` and `meta`
|
||||||
- `webbyBits.py` imports the database, runs all music playing, and accepts all commands from clients
|
- `webbyBits.py` imports the database, runs all music playing, and accepts all commands from clients
|
||||||
- Searches return matching songs
|
- Searches return matching songs
|
||||||
- Accepts Play-Pause and Skip commands
|
- Accepts Play-Pause and Skip commands
|
||||||
- Uses port 19054 by default
|
- Uses port 19054 by default
|
||||||
- `--port (port)` changes the port for that run
|
- Can be changed in the `.env` file
|
||||||
- The default port can be changed in the file
|
- The default port can be changed in the file
|
||||||
- Running with `--admin (admin password)` sets an admin password for moderation on the client
|
- Running with `--admin (admin password)` sets an admin password for moderation on the client
|
||||||
- ***Note: Do not reuse a password, consider this like making whatever this string is public, no security is guaranteed***
|
- ***Note: Do not reuse a password, the password is hashed before being sent over the network, but I still wouldn't bet my house on it, no security is guaranteed***
|
||||||
- Anyone who knows the admin password can enter it on the client and change the abilities of any non-admin users (for example to limit skipping)
|
- Anyone who knows the admin password can enter it on the client and change the abilities of any non-admin users (for example to limit skipping)
|
||||||
- The total set of features that can be restricted is
|
- The total set of features that can be restricted is
|
||||||
- Skip track
|
- Skip track
|
||||||
|
|
@ -80,6 +80,7 @@ From left to right:
|
||||||
- *No "previous" button is a design decision (It's a feature not a bug)*
|
- *No "previous" button is a design decision (It's a feature not a bug)*
|
||||||
- The search button opens the search screen (pictured)
|
- The search button opens the search screen (pictured)
|
||||||
- The settings button (top right) opens the settings menu
|
- The settings button (top right) opens the settings menu
|
||||||
|
- Dark mode sets a dark mode and stores a cookie to keep you in dark mode after refreshing
|
||||||
- Server IP allows you to change the ip that the site connects to
|
- Server IP allows you to change the ip that the site connects to
|
||||||
- Alert time changes how long error/confirmation messages are shown for (Default 2s)
|
- Alert time changes how long error/confirmation messages are shown for (Default 2s)
|
||||||
- Party Mode adds new songs to the queue when the queue has only 1 song in it
|
- Party Mode adds new songs to the queue when the queue has only 1 song in it
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
- [ ] Verify all if-else sequences are correct and not redundant
|
- [ ] Verify all if-else sequences are correct and not redundant
|
||||||
- [ ] Security Updates
|
- [ ] Security Updates
|
||||||
- [x] `.env` file for the api keys and other runtime info to be set, rather than in the `.py` files
|
- [x] `.env` file for the api keys and other runtime info to be set, rather than in the `.py` files
|
||||||
- [ ] Hashing rather than plaintext sending passwords (that way at least the password text itself isn't transmitted over the network)
|
- [x] Hashing rather than plaintext sending passwords (that way at least the password text itself isn't transmitted over the network)
|
||||||
- [ ] Actually use SSL, for posting (CORS seems like an issue)
|
- [ ] Actually use SSL, for posting (CORS seems like an issue)
|
||||||
- [ ] Accessibility
|
- [ ] Accessibility
|
||||||
- [ ] Better use of semantic HTML tags
|
- [ ] Better use of semantic HTML tags
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue