2025-08-02 00:48:21 +02:00
|
|
|
from typing import Any
|
|
|
|
|
2025-08-01 20:59:09 +02:00
|
|
|
import dotenv
|
2025-08-01 18:46:38 +02:00
|
|
|
from flask import Flask, Response, jsonify, request
|
2025-08-01 18:07:11 +02:00
|
|
|
from flask_cors import CORS
|
2025-08-01 22:07:39 +02:00
|
|
|
from flask_socketio import SocketIO, emit
|
2025-08-02 00:59:38 +02:00
|
|
|
import uuid
|
2025-08-01 19:16:20 +02:00
|
|
|
|
2025-08-02 01:00:32 +02:00
|
|
|
from .state import State
|
2025-08-01 23:29:40 +02:00
|
|
|
from .connect import get_connection
|
2025-08-01 19:16:20 +02:00
|
|
|
from .room import Room
|
2025-08-01 23:23:00 +02:00
|
|
|
from .song import Song, init_db, get_song_by_title_artist, add_song_in_db
|
|
|
|
from .song_fetch import lastfm_query_search, download_song_mp3
|
2025-08-02 00:44:35 +02:00
|
|
|
from .qrcode_gen import generate_qr
|
2025-08-01 18:07:11 +02:00
|
|
|
|
2025-08-02 00:19:07 +02:00
|
|
|
from typing import Any
|
|
|
|
|
2025-08-01 19:13:12 +02:00
|
|
|
dotenv.load_dotenv()
|
2025-08-01 19:16:20 +02:00
|
|
|
|
2025-08-01 18:23:27 +02:00
|
|
|
|
2025-08-01 18:07:11 +02:00
|
|
|
app = Flask(__name__)
|
2025-08-01 22:07:39 +02:00
|
|
|
app.config["SECRET_KEY"] = "your_secret_key"
|
|
|
|
socketio = SocketIO(app)
|
2025-08-01 18:07:11 +02:00
|
|
|
CORS(app)
|
|
|
|
|
2025-08-01 23:07:00 +02:00
|
|
|
db_conn = get_connection()
|
2025-08-02 00:48:21 +02:00
|
|
|
state = State(app, socketio, db_conn.cursor())
|
2025-08-01 23:07:00 +02:00
|
|
|
init_db(state.db)
|
2025-08-01 18:23:27 +02:00
|
|
|
|
2025-08-02 00:19:07 +02:00
|
|
|
state.rooms[1000] = Room(
|
|
|
|
id=1000,
|
|
|
|
coord=(1.0, 5.5),
|
|
|
|
name="Test Room",
|
|
|
|
pin=None,
|
|
|
|
tags=set(),
|
2025-08-02 01:14:58 +02:00
|
|
|
range_size=100,
|
|
|
|
songs={"b": (Song(uuid="b", title="title", artist="art", tags=["a", "B"], image_id="img", youtube_id="yt"), 1)},
|
2025-08-02 00:19:07 +02:00
|
|
|
history=[],
|
|
|
|
playing=[Song(uuid="<uuid>", title="<title>", artist="<artist>", tags=[], image_id="<img>", youtube_id="<yt>")],
|
|
|
|
playing_idx=0,
|
|
|
|
)
|
|
|
|
|
2025-08-01 18:23:27 +02:00
|
|
|
|
|
|
|
def error(msg: str, status: int = 400) -> Response:
|
|
|
|
res = jsonify({"success": False, "error": msg})
|
|
|
|
res.status_code = status
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
2025-08-02 00:19:07 +02:00
|
|
|
def ws_broadcast(event: str, *, room_id: int | None = None, data: Any) -> None:
|
|
|
|
final = {}
|
|
|
|
|
|
|
|
if room_id is not None:
|
|
|
|
final["room_id"] = room_id
|
|
|
|
|
|
|
|
final["data"] = data
|
|
|
|
|
|
|
|
emit(event, final, broadcast=True, namespace="/")
|
|
|
|
|
|
|
|
|
2025-08-01 21:39:46 +02:00
|
|
|
@app.get("/api/join")
|
2025-08-01 18:23:27 +02:00
|
|
|
def join():
|
|
|
|
room_id = request.args.get("room")
|
|
|
|
code = request.args.get("code")
|
|
|
|
|
|
|
|
if room_id is None:
|
|
|
|
return error("Missing room id")
|
|
|
|
|
2025-08-01 23:07:00 +02:00
|
|
|
if (room := state.rooms.get(int(room_id))) is None:
|
2025-08-01 18:23:27 +02:00
|
|
|
return error("Invalid room")
|
|
|
|
|
|
|
|
if room.pin is not None and room.pin != code:
|
|
|
|
return error("Invalid code")
|
|
|
|
|
|
|
|
return {"success": True, "ws": f"/ws/{room_id}"}
|
|
|
|
|
|
|
|
|
2025-08-01 21:39:46 +02:00
|
|
|
@app.get("/api/queue")
|
|
|
|
def queue():
|
|
|
|
if (room_id := request.args.get("room")) is None:
|
|
|
|
return error("Missing room id")
|
|
|
|
|
2025-08-01 23:07:00 +02:00
|
|
|
if (room := state.rooms.get(int(room_id))) is None:
|
2025-08-01 21:39:46 +02:00
|
|
|
return error("Invalid room")
|
|
|
|
|
2025-08-01 23:32:48 +02:00
|
|
|
return {"success": True, "queue": room.playing, "index": room.playing_idx}
|
2025-08-01 21:39:46 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/queue/next")
|
2025-08-01 22:07:39 +02:00
|
|
|
def queue_next():
|
|
|
|
if (room_id := request.args.get("room")) is None:
|
|
|
|
return error("Missing room id")
|
|
|
|
|
2025-08-01 23:07:00 +02:00
|
|
|
if (room := state.rooms.get(int(room_id))) is None:
|
2025-08-01 22:07:39 +02:00
|
|
|
return error("Invalid room")
|
|
|
|
|
|
|
|
room.playing_idx += 1
|
|
|
|
|
|
|
|
if room.playing_idx >= len(room.playing):
|
|
|
|
## queue ended
|
|
|
|
|
|
|
|
# room.renew_queue()
|
|
|
|
emit("update_songs", {"songs": [1, 2, 3]}, broadcast=True, namespace="/")
|
2025-08-01 21:39:46 +02:00
|
|
|
|
2025-08-01 22:32:44 +02:00
|
|
|
return {"success": True, "ended": True, "index": room.playing_idx, "queue": room.playing}
|
2025-08-01 21:39:46 +02:00
|
|
|
|
2025-08-01 22:07:39 +02:00
|
|
|
return {"success": True, "ended": False, "index": room.playing_idx}
|
2025-08-01 21:39:46 +02:00
|
|
|
|
|
|
|
|
2025-08-01 22:32:44 +02:00
|
|
|
@app.post("/api/room/new")
|
|
|
|
def room_new():
|
|
|
|
if (room_name := request.args.get("name")) is None:
|
|
|
|
return error("Missing room name")
|
|
|
|
|
|
|
|
if (room_cords := request.args.get("coords")) is None:
|
|
|
|
return error("Missing room coords")
|
|
|
|
|
2025-08-02 01:14:58 +02:00
|
|
|
if (room_range := request.args.get("range")) is None:
|
|
|
|
return error("Missing room range")
|
|
|
|
|
2025-08-01 22:32:44 +02:00
|
|
|
if room_pin := request.args.get("pin"):
|
|
|
|
room_pin = int(room_pin)
|
|
|
|
else:
|
|
|
|
room_pin = None
|
|
|
|
|
|
|
|
lat, lon = room_cords.split(",")
|
|
|
|
|
|
|
|
room = Room(
|
2025-08-01 23:07:00 +02:00
|
|
|
id=max(state.rooms or [0]) + 1, #
|
2025-08-01 22:32:44 +02:00
|
|
|
coord=(float(lat), float(lon)),
|
2025-08-02 01:14:58 +02:00
|
|
|
range_size=int(room_range),
|
2025-08-01 22:32:44 +02:00
|
|
|
name=room_name,
|
|
|
|
pin=room_pin,
|
|
|
|
tags=set([tag for tag in request.args.get("tags", "").split(",") if tag]),
|
|
|
|
songs={},
|
|
|
|
history=[],
|
|
|
|
playing=[],
|
|
|
|
playing_idx=-1,
|
|
|
|
)
|
|
|
|
|
2025-08-01 23:07:00 +02:00
|
|
|
state.rooms[room.id] = room
|
2025-08-01 22:32:44 +02:00
|
|
|
|
|
|
|
return {"success": True, "room_id": room.id}
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/room")
|
|
|
|
def room():
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
"id": room.id,
|
|
|
|
"name": room.name,
|
|
|
|
"private": room.pin is not None,
|
|
|
|
"coords": room.coord,
|
|
|
|
}
|
2025-08-01 23:07:00 +02:00
|
|
|
for room in state.rooms.values()
|
2025-08-01 22:32:44 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2025-08-01 23:23:00 +02:00
|
|
|
@app.post("/api/addsong")
|
|
|
|
def add_song():
|
|
|
|
if (room_id := request.args.get("room")) is None:
|
|
|
|
return error("Missing room id")
|
|
|
|
|
2025-08-01 23:25:28 +02:00
|
|
|
if (room := state.rooms.get(int(room_id))) is None:
|
2025-08-01 23:23:00 +02:00
|
|
|
return error("Invalid room")
|
|
|
|
|
|
|
|
if (query := request.args.get("query")) is None:
|
|
|
|
return error("Missing query")
|
|
|
|
|
|
|
|
info = lastfm_query_search(query)
|
|
|
|
|
|
|
|
if (song := get_song_by_title_artist(info.title, info.artist)) is None:
|
|
|
|
## song not found, downolad from YT
|
2025-08-02 00:59:38 +02:00
|
|
|
if (res := download_song_mp3(info.title, info.artist)) is None:
|
|
|
|
return error("Cannot get info from YT")
|
|
|
|
|
2025-08-01 23:23:00 +02:00
|
|
|
## add in DB
|
2025-08-02 00:59:38 +02:00
|
|
|
song = Song(
|
|
|
|
uuid=str(uuid.uuid4()),
|
|
|
|
title=info.title,
|
|
|
|
artist=info.artist,
|
|
|
|
tags=info.tags,
|
|
|
|
image_id=info.img_id,
|
|
|
|
youtube_id=res[0],
|
|
|
|
)
|
|
|
|
|
|
|
|
add_song_in_db(song)
|
|
|
|
|
2025-08-02 01:02:43 +02:00
|
|
|
## add the song in the room if does not exists
|
|
|
|
if song.uuid not in room.songs:
|
|
|
|
room.songs[song.uuid] = (song, 1) # start with one vote
|
2025-08-02 00:59:38 +02:00
|
|
|
|
2025-08-02 01:02:43 +02:00
|
|
|
return {"success": True, "song": song}
|
2025-08-01 23:23:00 +02:00
|
|
|
|
|
|
|
|
2025-08-02 00:59:38 +02:00
|
|
|
@app.get("/api/room/suggestions")
|
|
|
|
def get_room_suggestion():
|
|
|
|
if (room_id := request.args.get("room")) is None:
|
|
|
|
return error("Missing room id")
|
|
|
|
|
|
|
|
if (room := state.rooms.get(int(room_id))) is None:
|
|
|
|
return error("Invalid room id")
|
|
|
|
|
|
|
|
return {"success": True, "songs": room.songs}
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/song/voting")
|
|
|
|
def post_song_vote():
|
|
|
|
if (room_id := request.args.get("room")) is None:
|
|
|
|
return error("Missing room id")
|
|
|
|
|
|
|
|
if (room := state.rooms.get(int(room_id))) is None:
|
|
|
|
return error("Invalid room id")
|
|
|
|
|
|
|
|
if (song_id := request.args.get("song")) is None:
|
|
|
|
return error("Missing song id")
|
|
|
|
|
|
|
|
if (song_info := room.songs.get(song_id)) is None:
|
|
|
|
return error("Invalid song id")
|
|
|
|
|
|
|
|
## update the song
|
|
|
|
room.songs[song_id] = (song_info[0], song_info[1] + int(request.args["increment"]))
|
|
|
|
|
|
|
|
return {"success": True}
|
|
|
|
|
|
|
|
|
2025-08-02 00:44:35 +02:00
|
|
|
@app.get("/api/room/qrcode")
|
|
|
|
def room_qrcode():
|
|
|
|
if (room_id := request.args.get("room")) is None:
|
|
|
|
return error("Missing room id")
|
|
|
|
|
|
|
|
if (pin := request.args.get("pin")) is not None:
|
|
|
|
pin = int(pin)
|
|
|
|
|
|
|
|
stream = generate_qr(
|
|
|
|
base_uri="https://chillbox.leoinvents.com",
|
|
|
|
room_id=int(room_id),
|
|
|
|
pin=pin,
|
|
|
|
)
|
|
|
|
|
|
|
|
return Response(stream, content_type="image/jpeg")
|
|
|
|
|
|
|
|
|
2025-08-01 18:07:11 +02:00
|
|
|
if __name__ == "__main__":
|
2025-08-01 22:07:39 +02:00
|
|
|
socketio.run(app, debug=True)
|