diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-07-02 22:17:17 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-07-02 22:17:17 -0700 |
| commit | 04eb44d685ba677a444b73c7cfc2427d2740497f (patch) | |
| tree | 94b35f31d59b2268e40a054304191be38848a7f6 /src/server/Main.hx | |
| parent | ece9cf2749ac592299a34a2cf0f24672eedbd19e (diff) | |
implement better leader, queue, and upload management
- Majority vote skip management (Admins and the original video queuer
overrides this behavior)
- Leader request now requires the current leader to give up the role
(via `/giveup <new-leader>`)
- Overhauled default CSS and improved mobile view
- Caching and uploading files is limited to Admin users only
Signed-off-by: Pinapelz <yukais@pinapelz.com>
Diffstat (limited to 'src/server/Main.hx')
| -rw-r--r-- | src/server/Main.hx | 113 |
1 files changed, 108 insertions, 5 deletions
diff --git a/src/server/Main.hx b/src/server/Main.hx index 8b9ade1..12a3bfa 100644 --- a/src/server/Main.hx +++ b/src/server/Main.hx @@ -82,6 +82,8 @@ class Main { **/ var emptyRoomCallbackTimer:Null<Timer>; var isServerPause = false; + var activeSkipVoteUrl:Null<String>; + final skipVotes:Map<Int, Bool> = []; static function main():Void { new Main({ @@ -426,6 +428,11 @@ class Main { return req.socket.remoteAddress; } + function isLoopbackIp(ip:Null<String>):Bool { + if (ip == null) return false; + return ip == "127.0.0.1" || ip == "::1" || ip == "::ffff:127.0.0.1"; + } + public function addAdmin(name:String, password:String):Void { password += config.salt; final hash = Sha256.encode(password); @@ -504,7 +511,8 @@ class Main { final id = freeIds.length > 0 ? freeIds.shift() : clients.length; final name = 'Guest ${id + 1}'; trace(Date.now().toString(), '$name connected ($ip)'); - final isAdmin = config.localAdmins && req.socket.localAddress == ip; + final remoteIp = req.socket.remoteAddress; + final isAdmin = config.localAdmins && isLoopbackIp(remoteIp); final client = new Client(ws, req, id, name, 0); client.uuid = uuid; client.isAdmin = isAdmin; @@ -760,6 +768,10 @@ class Main { // Initial timer start if VideoLoaded is not happen if (videoList.length == 1) restartWaitTimer(); } + if (item.doCache && !client.isAdmin) { + item.doCache = false; + serverMessage(client, "Only admins may cache videos."); + } if (!item.doCache) { addVideo(); } else { @@ -789,6 +801,7 @@ class Main { case RemoveVideo: if (videoList.length == 0) return; + if (activeSkipVoteUrl == data.removeVideo.url) resetSkipVote(); if (isPlaylistLockedFor(client)) return; if (!checkPermission(client, RemoveVideoPerm)) return; final url = data.removeVideo.url; @@ -805,7 +818,7 @@ class Main { case SkipVideo: if (!checkPermission(client, RemoveVideoPerm)) return; - skipVideo(data); + handleSkipVote(client, data); case Pause: if (videoList.length == 0) return; @@ -910,11 +923,26 @@ class Main { }); case SetLeader: - final clientName = data.setLeader.clientName; + var clientName = data.setLeader.clientName; if (client.name == clientName) { if (!checkPermission(client, RequestLeaderPerm)) return; - } else if (!client.isLeader && clientName != "") { - if (!checkPermission(client, SetLeaderPerm)) return; + final currentLeader = clients.find(c -> c.isLeader); + final hasOtherLeader = currentLeader != null && currentLeader != client; + if (!client.isAdmin && hasOtherLeader) { + serverMessage(client, + 'Leader request sent to ${currentLeader.name}. Waiting for /giveup ${client.name}.'); + serverMessage(currentLeader, + '${client.name} requested leader. Use /giveup ${client.name} to transfer leadership.'); + return; + } + } else if (clientName == "") { + if (!client.isLeader && !checkPermission(client, SetLeaderPerm)) return; + } else if (!client.isLeader && !checkPermission(client, SetLeaderPerm)) { + return; + } + if (clientName != "" && clients.getByName(clientName) == null) { + serverMessage(client, 'Client "$clientName" not found.'); + return; } isServerPause = false; clients.setLeader(clientName); @@ -938,6 +966,7 @@ class Main { case PlayItem: if (!checkPermission(client, ChangeOrderPerm)) return; + resetSkipVote(); final pos = data.playItem.pos; if (!videoList.hasItem(pos)) return; if (videoTimer.getTime() > FLASHBACK_DIST) { @@ -972,6 +1001,7 @@ class Main { case ClearPlaylist: if (isPlaylistLockedFor(client)) return; + resetSkipVote(); if (!checkPermission(client, RemoveVideoPerm)) return; if (videoList.length != 0) { if (videoTimer.getTime() > FLASHBACK_DIST) { @@ -1113,8 +1143,81 @@ class Main { return value; } + function resetSkipVote():Void { + activeSkipVoteUrl = null; + skipVotes.clear(); + } + + function getSkipVoteEligibleClients():Array<Client> { + return [ + for (c in clients) + if (!c.isBanned && c.hasPermission(RemoveVideoPerm, config.permissions)) c + ]; + } + + function getSkipVoteCount():Int { + var count = 0; + for (c in getSkipVoteEligibleClients()) { + if (skipVotes[c.id]) count++; + } + return count; + } + + function getSkipVoteRequiredCount():Int { + final total = getSkipVoteEligibleClients().length; + if (total <= 1) return 1; + return Std.int(Math.floor(total / 2)) + 1; + } + + function handleSkipVote(client:Client, data:WsEvent):Void { + if (videoList.length == 0) return; + final item = videoList.currentItem; + if (item.url != data.skipVideo.url) return; + + final isOwnItemAuthor = item.author == client.name; + if (client.isAdmin || isOwnItemAuthor) { + if (activeSkipVoteUrl != null) { + final reason = client.isAdmin ? "forced skip" : "skipped own queued video"; + broadcast({ + type: ServerMessage, + serverMessage: { + textId: '${client.name} ${reason}.' + } + }); + } + resetSkipVote(); + skipVideo(data); + return; + } + + if (activeSkipVoteUrl != item.url) { + activeSkipVoteUrl = item.url; + skipVotes.clear(); + } + if (skipVotes[client.id]) { + serverMessage(client, "You already voted to skip this video."); + return; + } + skipVotes[client.id] = true; + + final votes = getSkipVoteCount(); + final required = getSkipVoteRequiredCount(); + broadcast({ + type: ServerMessage, + serverMessage: { + textId: '${client.name} voted to skip (${votes}/${required}).' + } + }); + + if (votes >= required) { + resetSkipVote(); + skipVideo(data); + } + } + function skipVideo(data:WsEvent):Void { if (videoList.length == 0) return; + resetSkipVote(); final item = videoList.currentItem; if (item.url != data.skipVideo.url) return; final dur = videoList.currentItem.duration; |
