From df3b59c32838d5bad15836ba9b4965ca33aecf1f Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:52:57 +0700 Subject: [PATCH 01/11] move /uptime and /whoami to client --- backend/websockets/chat.js | 31 ----------------- backend/websockets/uptime.js | 7 ++++ backend/websockets/whoami.js | 21 ++++++++++++ frontend/static/yw/javascript/chat.js | 16 +++++++++ frontend/static/yw/javascript/helpers.js | 31 +++++++++++++++++ frontend/static/yw/javascript/owot.js | 42 +++++++++++++++++++++++- runserver.js | 4 ++- 7 files changed, 119 insertions(+), 33 deletions(-) create mode 100644 backend/websockets/uptime.js create mode 100644 backend/websockets/whoami.js diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 7b88b242..784630da 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -74,7 +74,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var broadcastMonitorEvent = server.broadcastMonitorEvent; var loadPlugin = server.loadPlugin; var getServerSetting = server.getServerSetting; - var getServerUptime = server.getServerUptime; var add_to_chatlog = chat_mgr.add_to_chatlog; var remove_from_chatlog = chat_mgr.remove_from_chatlog; @@ -207,9 +206,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { // [rank, name, args, description, example] var command_list = [ - // operator - [3, "uptime", null, "get uptime of server", null], - // superuser [2, "worlds", null, "list all worlds", null], @@ -228,7 +224,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { [0, "clearmutes", null, "unmute all clients"], // check for permission [0, "delete", ["id", "timestamp"], "delete a chat message", "1220 1693147307895"], // check for permission [0, "tell", ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"], - [0, "whoami", null, "display your identity"], [0, "test", null, "preview your appearance"] // hidden by default @@ -454,9 +449,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } serverChatResponse("Cleared all blocks", location); }, - uptime: function() { - serverChatResponse("Server uptime: " + calculateTimeDiff(getServerUptime()), location); - }, tell: function(id, message) { id += ""; message += ""; @@ -653,23 +645,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } return serverChatResponse("Unmuted " + cnt + " user(s)", location); }, - whoami: function() { - var idstr = "Who Am I:\n"; - var user_login = "(anonymous)"; - var user_disp = "(anonymous)"; - if(user.authenticated) { - user_disp = username_to_display; - if(accountSystem == "uvias") { - user_login = user.username; - } else { - user_login = user_disp; - } - } - idstr += "Login username: " + user_login + "\n"; - idstr += "Display username: " + user_disp + "\n"; - idstr += "Chat ID: " + clientId; - return serverChatResponse(idstr, location); - }, delete: async function(id, timestamp) { if(!is_owner && !user.staff) return; id = san_nbr(id); @@ -751,9 +726,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "help": com.help(); return; - case "uptime": - com.uptime(); - return; case "block": com.block(commandArgs[1]); return; @@ -781,9 +753,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "clearmutes": com.clearmutes(); return; - case "whoami": - com.whoami(); - return; case "delete": com.delete(commandArgs[1], commandArgs[2]); return; diff --git a/backend/websockets/uptime.js b/backend/websockets/uptime.js new file mode 100644 index 00000000..963b8a87 --- /dev/null +++ b/backend/websockets/uptime.js @@ -0,0 +1,7 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var getServerUptime = server.getServerUptime; + + send({ + uptime: getServerUptime() + }); +} diff --git a/backend/websockets/whoami.js b/backend/websockets/whoami.js new file mode 100644 index 00000000..95220199 --- /dev/null +++ b/backend/websockets/whoami.js @@ -0,0 +1,21 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var user = ctx.user; + var accountSystem = server.accountSystem; + + var res = { + user_login: null, + user_disp: null + }; + + if(user.authenticated) { + res.user_login = user.username; + + if(accountSystem == "uvias") { + res.user_disp = user.display_username; + } else { + res.user_disp = user.username; + } + } + + send(res); +} diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 338329aa..eab8079a 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -273,6 +273,22 @@ register_chat_command("stats", function() { }); }, null, "view stats of a world", null); +register_chat_command("uptime", function() { + network.uptime(function(data) { + clientChatResponse("Server uptime: " + calculateTimeDiff(data.uptime)); + }); +}, null, "get uptime of server", null); + +register_chat_command("whoami", function() { + network.whoami(function(data) { + var idstr = "Who Am I:\n"; + idstr += "Login username: " + (data.user_login ?? "(anonymous)") + "\n"; + idstr += "Display username: " + (data.user_disp ?? "(anonymous)") + "\n"; + idstr += "Chat ID: " + w.clientId; + clientChatResponse(idstr); + }); +}, null, "display your identity", null); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/helpers.js b/frontend/static/yw/javascript/helpers.js index d1078406..b45f5c8e 100644 --- a/frontend/static/yw/javascript/helpers.js +++ b/frontend/static/yw/javascript/helpers.js @@ -780,6 +780,37 @@ function filterAdvancedChars(array, noSurrogates, noCombining) { return array; } +function calculateTimeDiff(difference) { + var str = ""; + var days = Math.floor(difference / 86400000); + difference -= days * 86400000; + var hours = Math.floor(difference / 3600000); + difference -= hours * 3600000; + var minutes = Math.floor(difference / 60000); + difference -= minutes * 60000; + var seconds = Math.floor(difference / 1000); + difference -= seconds * 1000; + + if(days > 0) { + if(str) str += ", "; + str += days + " day" + (days != 1 ? "s" : ""); + } + if(hours > 0) { + if(str) str += ", "; + str += hours + " hour" + (hours != 1 ? "s" : ""); + } + if(minutes > 0) { + if(str) str += ", "; + str += minutes + " minute" + (minutes != 1 ? "s" : ""); + } + if(seconds > 0) { + if(str) str += ", "; + str += seconds + " second" + (seconds != 1 ? "s" : ""); + } + + return str; +} + if(!HTMLElement.prototype.append) { HTMLElement.prototype.append = function(string) { this.appendChild(document.createTextNode(string)); diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index 67cbce13..dc20524d 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6549,6 +6549,28 @@ var network = { kind: "stats", id: cb_id // optional: number }); + }, + uptime: function(callback) { + var cb_id = void 0; + if(callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "uptime", + request: cb_id + }); + }, + whoami: function(callback) { + var cb_id = void 0; + if(callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "whoami", + request: cb_id + }); } }; @@ -8071,7 +8093,25 @@ var ws_functions = { cb(data); } } - } + }, + uptime: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } + }, + whoami: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } + }, }; function begin() { diff --git a/runserver.js b/runserver.js index 3eaac042..23b0e5a5 100644 --- a/runserver.js +++ b/runserver.js @@ -605,7 +605,9 @@ var websockets = { write: require("./backend/websockets/write.js"), config: require("./backend/websockets/config.js"), boundary: require("./backend/websockets/boundary.js"), - stats: require("./backend/websockets/stats.js") + stats: require("./backend/websockets/stats.js"), + uptime: require("./backend/websockets/uptime.js"), + whoami: require("./backend/websockets/whoami.js") }; var modules = { From fe92521947b5b25690c6c9c5071b7b09d50c766e Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:48:42 +0700 Subject: [PATCH 02/11] move /test to client --- backend/websockets/chat.js | 15 +--------- backend/websockets/chat_test.js | 43 +++++++++++++++++++++++++++ frontend/static/yw/javascript/chat.js | 14 +++++++++ frontend/static/yw/javascript/owot.js | 8 +++++ runserver.js | 5 ++-- 5 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 backend/websockets/chat_test.js diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 784630da..3cf7ec6c 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -139,7 +139,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } var isMuted = false; - var isTestMessage = false; var muteInfo = null; var worldChatMutes = blocked_ips_by_world_id[world.id]; if(location == "global") { @@ -223,8 +222,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { [0, "mute", ["id", "seconds", "[h/d/w/m/y]"], "mute a user completely", "1220 9999"], // check for permission [0, "clearmutes", null, "unmute all clients"], // check for permission [0, "delete", ["id", "timestamp"], "delete a chat message", "1220 1693147307895"], // check for permission - [0, "tell", ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"], - [0, "test", null, "preview your appearance"] + [0, "tell", ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"] // hidden by default // "/search Phrase" (client) -> searches for Phrase within a 25 tile radius @@ -673,9 +671,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } else if(mode == "off") { ws.sdata.passiveCmd = false; } - }, - test: function() { - isTestMessage = true; } } @@ -759,9 +754,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "passive": com.passive(commandArgs[1]); return; - case "test": - com.test(); - break; default: serverChatResponse("Invalid command: " + msg); } @@ -850,11 +842,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { username: user.authenticated ? username_to_display.toUpperCase() : null }; - if(isTestMessage) { - websocketChatData.message = "This message is visible to only you."; - send(websocketChatData); - } - if(!isCommand) { if(clientIpObj && location == "global") { clientIpObj[3] = Date.now(); diff --git a/backend/websockets/chat_test.js b/backend/websockets/chat_test.js new file mode 100644 index 00000000..3a802e32 --- /dev/null +++ b/backend/websockets/chat_test.js @@ -0,0 +1,43 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var user = ctx.user; + var clientId = ws.sdata.clientId; + var ranks_cache = server.ranks_cache; + var accountSystem = server.accountSystem; + + var nick = ""; + if(data.nickname) { + nick = data.nickname + ""; + } + if(!user.staff) { + nick = nick.slice(0, 40); + } else { + nick = nick.slice(0, 3030); + } + + var username_to_display = user.username; + if(accountSystem == "uvias") { + username_to_display = user.display_username; + } + + var chatData = { + kind: "chat", + nickname: nick, + realUsername: username_to_display, + id: clientId, + message: "This message is visible to only you.", + registered: user.authenticated, + location: data.location, + op: user.operator, + admin: user.superuser, + staff: user.staff, + color: data.color + }; + + if(user.authenticated && user.id in ranks_cache.users) { + var rank = ranks_cache[ranks_cache.users[user.id]]; + chatData.rankName = rank.name; + chatData.rankColor = rank.chat_color; + } + + send(chatData); +} diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index eab8079a..8f574f1b 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -289,6 +289,20 @@ register_chat_command("whoami", function() { }); }, null, "display your identity", null); +register_chat_command("test", function() { + var location = selectedChatTab == 0 ? "page" : "global"; + var nickname = YourWorld.Nickname || state.userModel.username; + + var color; + if(!YourWorld.Color) { + color = assignColor(nickname); + } else { + color = "#" + ("00000" + YourWorld.Color.toString(16)).slice(-6); + } + + network.chat_test(location, nickname, color); +}, null, "preview your appearance", null); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index dc20524d..e073ea85 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6571,6 +6571,14 @@ var network = { kind: "whoami", request: cb_id }); + }, + chat_test: function(location, nickname, color) { + network.transmit({ + kind: "chat_test", + location, + nickname, + color + }); } }; diff --git a/runserver.js b/runserver.js index 23b0e5a5..a6ff808a 100644 --- a/runserver.js +++ b/runserver.js @@ -607,7 +607,8 @@ var websockets = { boundary: require("./backend/websockets/boundary.js"), stats: require("./backend/websockets/stats.js"), uptime: require("./backend/websockets/uptime.js"), - whoami: require("./backend/websockets/whoami.js") + whoami: require("./backend/websockets/whoami.js"), + chat_test: require("./backend/websockets/chat_test.js") }; var modules = { @@ -2232,7 +2233,7 @@ async function manageWebsocketConnection(ws, req) { } if(!can_process_req_kind(kindLimits, kind)) return; function send(msg) { - msg.kind = kind; + if(!msg.kind) msg.kind = kind; if(requestID !== null) msg.request = requestID; send_ws(JSON.stringify(msg)); } From 02a18f47aae274855bb037ad3400ae3a925728ce Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:13:02 +0700 Subject: [PATCH 03/11] move /delete to client --- backend/websockets/chat.js | 29 +------------- backend/websockets/chat_delete_req.js | 55 +++++++++++++++++++++++++++ frontend/static/yw/javascript/chat.js | 20 ++++++++++ frontend/static/yw/javascript/owot.js | 23 +++++++++++ runserver.js | 3 +- 5 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 backend/websockets/chat_delete_req.js diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 3cf7ec6c..0c6795f7 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -76,7 +76,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var getServerSetting = server.getServerSetting; var add_to_chatlog = chat_mgr.add_to_chatlog; - var remove_from_chatlog = chat_mgr.remove_from_chatlog; var ipHeaderAddr = ws.sdata.ipAddress; var clientId = ws.sdata.clientId; @@ -221,7 +220,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { [0, "unblockall", null, "unblock all users", null], [0, "mute", ["id", "seconds", "[h/d/w/m/y]"], "mute a user completely", "1220 9999"], // check for permission [0, "clearmutes", null, "unmute all clients"], // check for permission - [0, "delete", ["id", "timestamp"], "delete a chat message", "1220 1693147307895"], // check for permission [0, "tell", ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"] // hidden by default @@ -254,7 +252,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var desc = row[3]; var example = row[4]; - if(command == "mute" || command == "clearmutes" || command == "delete") { + if(command == "mute" || command == "clearmutes") { if(!user.staff && !is_owner) { continue; } @@ -643,28 +641,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } return serverChatResponse("Unmuted " + cnt + " user(s)", location); }, - delete: async function(id, timestamp) { - if(!is_owner && !user.staff) return; - id = san_nbr(id); - timestamp = san_nbr(timestamp); - var wid = world.id; - if(location == "global") { - if(!user.staff) { - return serverChatResponse("You do not have permission to delete messages on global", location); - } - wid = 0; - } - var res = await remove_from_chatlog(wid, id, timestamp); - if(res == 0) { - return serverChatResponse("No messages deleted", location); - } - broadcast({ - kind: "chatdelete", - id: id, - time: timestamp - }); - return serverChatResponse("Deleted " + res + " message(s)", location); - }, passive: function(mode) { if(mode == "on") { ws.sdata.passiveCmd = true; @@ -748,9 +724,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "clearmutes": com.clearmutes(); return; - case "delete": - com.delete(commandArgs[1], commandArgs[2]); - return; case "passive": com.passive(commandArgs[1]); return; diff --git a/backend/websockets/chat_delete_req.js b/backend/websockets/chat_delete_req.js new file mode 100644 index 00000000..6484477f --- /dev/null +++ b/backend/websockets/chat_delete_req.js @@ -0,0 +1,55 @@ +var utils = require("../utils/utils.js"); +var san_nbr = utils.san_nbr; + +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var user = ctx.user; + var world = ctx.world; + var is_owner = world.ownerId == user.id; + var chat_mgr = server.chat_mgr; + var remove_from_chatlog = chat_mgr.remove_from_chatlog; + + if(!is_owner && !user.staff) { + send({ + success: false, + error: "no_perm" + }); + return; + } + + var id = san_nbr(data.id); + var timestamp = san_nbr(data.timestamp); + var wid = world.id; + + var location = ""; + if(!(data.location == "global" || data.location == "page")) data.location = "page"; + location = data.location; + + if(location == "global") { + if(!user.staff) { + send({ + success: false, + error: "no_perm" + }); + return; + } + + wid = 0; + } + + var res = await remove_from_chatlog(wid, id, timestamp); + + send({ + success: true, + count: res + }); + + if(res == 0) { + return; + } + + broadcast({ + kind: "chatdelete", + id: id, + time: timestamp + }); +} diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 8f574f1b..f0046405 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -303,6 +303,26 @@ register_chat_command("test", function() { network.chat_test(location, nickname, color); }, null, "preview your appearance", null); +register_chat_command("delete", function(args) { + var id = Number(args[0]); + var timestamp = Number(args[1]); + var location = selectedChatTab == 0 ? "page" : "global"; + + network.chat_delete_req(id, timestamp, location, function(data) { + if(data.success) { + if(data.count == 0) { + clientChatResponse("No messages deleted"); + } else { + clientChatResponse("Deleted " + data.count + " message(s)"); + } + } else { + if(data.error == "no_perm") { + clientChatResponse("You do not have permission to delete messages here"); + } + } + }); +}, ["id", "timestamp"], "delete a chat message", "1220 1693147307895"); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index e073ea85..f9145b79 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6579,6 +6579,20 @@ var network = { nickname, color }); + }, + chat_delete_req: function(id, timestamp, location, callback) { + var cb_id = void 0; + if(callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "chat_delete_req", + id, + timestamp, + location, + request: cb_id + }); } }; @@ -8120,6 +8134,15 @@ var ws_functions = { } } }, + chat_delete_req: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } + } }; function begin() { diff --git a/runserver.js b/runserver.js index a6ff808a..a701506e 100644 --- a/runserver.js +++ b/runserver.js @@ -608,7 +608,8 @@ var websockets = { stats: require("./backend/websockets/stats.js"), uptime: require("./backend/websockets/uptime.js"), whoami: require("./backend/websockets/whoami.js"), - chat_test: require("./backend/websockets/chat_test.js") + chat_test: require("./backend/websockets/chat_test.js"), + chat_delete_req: require("./backend/websockets/chat_delete_req.js") }; var modules = { From ad7a8d21c4c9aa1c59ca360300e044efdbbb658f Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:11:35 +0700 Subject: [PATCH 04/11] move /worlds to client --- backend/websockets/chat.js | 24 ------------------------ backend/websockets/worlds.js | 14 ++++++++++++++ frontend/static/yw/javascript/chat.js | 17 +++++++++++++++++ frontend/static/yw/javascript/owot.js | 20 ++++++++++++++++++++ runserver.js | 3 ++- 5 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 backend/websockets/worlds.js diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 0c6795f7..d6229d24 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -65,7 +65,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var db_chat = server.db_chat; var ws_broadcast = server.ws_broadcast; // site-wide broadcast var chat_mgr = server.chat_mgr; - var topActiveWorlds = server.topActiveWorlds; var wss = server.wss; var ranks_cache = server.ranks_cache; var accountSystem = server.accountSystem; @@ -204,9 +203,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { // [rank, name, args, description, example] var command_list = [ - // superuser - [2, "worlds", null, "list all worlds", null], - // staff [1, "channel", null, "get info about a chat channel"], @@ -299,23 +295,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } var com = { - worlds: function() { - var topCount = 1000; - var lst = topActiveWorlds(topCount); - var worldList = ""; - for(var i = 0; i < lst.length; i++) { - var row = lst[i]; - if(row[1] == "") { - row[1] = "(main)" - } else { - row[1] = "/" + row[1]; - } - worldList += "-> " + row[1] + " [" + row[0] + "]"; - if(i != lst.length - 1) worldList += "\n"; - } - serverChatResponse("Currently loaded worlds (top " + topCount + "):\n" + worldList, location); - return; - }, help: function(modifier) { return serverChatResponse(generate_command_list(), location); }, @@ -691,9 +670,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var staff = user.staff; switch(commandType) { - case "worlds": - if(superuser) com.worlds(); - return; case "help": com.help(); return; diff --git a/backend/websockets/worlds.js b/backend/websockets/worlds.js new file mode 100644 index 00000000..6abce9f1 --- /dev/null +++ b/backend/websockets/worlds.js @@ -0,0 +1,14 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var user = ctx.user; + var topActiveWorlds = server.topActiveWorlds; + + if (!user.superuser) return; + + var topCount = 1000; + var list = topActiveWorlds(topCount); + + send({ + topCount, + list + }); +} diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index f0046405..0e42f451 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -323,6 +323,23 @@ register_chat_command("delete", function(args) { }); }, ["id", "timestamp"], "delete a chat message", "1220 1693147307895"); +register_chat_command("worlds", function() { + network.worlds(function(data) { + var worldList = ""; + for(var i = 0; i < data.list.length; i++) { + var row = data.list[i]; + if(row[1] == "") { + row[1] = "(main)" + } else { + row[1] = "/" + row[1]; + } + worldList += "-> " + row[1] + " [" + row[0] + "]"; + if(i != data.list.length - 1) worldList += "\n"; + } + clientChatResponse("Currently loaded worlds (top " + data.topCount + "):\n" + worldList); + }); +}, null, "list all worlds", null); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index f9145b79..2d7415bf 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6593,6 +6593,17 @@ var network = { location, request: cb_id }); + }, + worlds: function(callback) { + var cb_id = void 0; + if(callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "worlds", + request: cb_id + }); } }; @@ -8142,6 +8153,15 @@ var ws_functions = { cb(data); } } + }, + worlds: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } } }; diff --git a/runserver.js b/runserver.js index a701506e..f683559e 100644 --- a/runserver.js +++ b/runserver.js @@ -609,7 +609,8 @@ var websockets = { uptime: require("./backend/websockets/uptime.js"), whoami: require("./backend/websockets/whoami.js"), chat_test: require("./backend/websockets/chat_test.js"), - chat_delete_req: require("./backend/websockets/chat_delete_req.js") + chat_delete_req: require("./backend/websockets/chat_delete_req.js"), + worlds: require("./backend/websockets/worlds.js") }; var modules = { From 7fd2cf3cb732e1a98a31bf89ab705cb15ae77f02 Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:35:08 +0700 Subject: [PATCH 05/11] move /channel to client --- backend/websockets/chat.js | 33 ----------------------- backend/websockets/chat_channel.js | 39 +++++++++++++++++++++++++++ frontend/static/yw/javascript/chat.js | 18 +++++++++++++ frontend/static/yw/javascript/owot.js | 21 +++++++++++++++ runserver.js | 3 ++- 5 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 backend/websockets/chat_channel.js diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index d6229d24..fb40926c 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -62,7 +62,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var world = ctx.world; var db = server.db; - var db_chat = server.db_chat; var ws_broadcast = server.ws_broadcast; // site-wide broadcast var chat_mgr = server.chat_mgr; var wss = server.wss; @@ -203,9 +202,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { // [rank, name, args, description, example] var command_list = [ - // staff - [1, "channel", null, "get info about a chat channel"], - // general [0, "help", null, "list all commands", null], @@ -533,32 +529,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } broadcastMonitorEvent("TellSpam", "Tell from " + clientId + " (" + ipHeaderAddr + ") to " + id + ", first 4 chars: [" + message.slice(0, 4) + "]"); }, - channel: async function() { - if(!user.staff) return; - var worldId = world.id; - if(location == "global") worldId = 0; - var channels = await db_chat.all("SELECT * FROM channels WHERE world_id=?", worldId); - var count = channels.length; - var infoLog = "Found " + count + " channel(s) for this world:\n"; - for(var i = 0; i < count; i++) { - var ch = channels[i]; - var name = ch.name; - var desc = ch.description; - var date = ch.date_created; - infoLog += "Name: " + name + "\n"; - infoLog += "Desc: " + desc + "\n"; - infoLog += "Created: " + create_date(date) + "\n"; - infoLog += "----------------\n"; - } - var def = await db_chat.get("SELECT * FROM default_channels WHERE world_id=?", worldId); - if(def && def.channel_id) { - def = def.channel_id; - } else { - def = ""; - } - infoLog += "Default channel id: " + def; - return serverChatResponse(infoLog, location); - }, mute: function(id, time, flag) { if(!is_owner && !user.staff) return; id = san_nbr(id); @@ -691,9 +661,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "tell": com.tell(commandArgs[1], commandArgs.slice(2).join(" ")); return; - case "channel": - com.channel(); - return; case "mute": com.mute(commandArgs[1], commandArgs[2], commandArgs[3]); return; diff --git a/backend/websockets/chat_channel.js b/backend/websockets/chat_channel.js new file mode 100644 index 00000000..eeb1ff0b --- /dev/null +++ b/backend/websockets/chat_channel.js @@ -0,0 +1,39 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var user = ctx.user; + var world = ctx.world; + var db_chat = server.db_chat; + + var location = ""; + if(!(data.location == "global" || data.location == "page")) data.location = "page"; + location = data.location; + + if(!user.staff) return; + + var worldId = world.id; + if(location == "global") worldId = 0; + + var res = { + channels: [], + default_channel: null + }; + + var channels = await db_chat.all("SELECT * FROM channels WHERE world_id=?", worldId); + var count = channels.length; + + for(var i = 0; i < count; i++) { + var ch = channels[i]; + + res.channels.push({ + name: ch.name, + description: ch.description, + date_created: ch.date_created + }); + } + + var def = await db_chat.get("SELECT * FROM default_channels WHERE world_id=?", worldId); + if(def && def.channel_id) { + res.default_channel = def.channel_id; + } + + send(res); +} diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 0e42f451..3296f4ae 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -340,6 +340,24 @@ register_chat_command("worlds", function() { }); }, null, "list all worlds", null); +register_chat_command("channel", function() { + var location = selectedChatTab == 0 ? "page" : "global"; + + network.chat_channel(location, function(data) { + var infoLog = "Found " + data.channels.length + " channel(s) for this world:\n"; + for(var i = 0; i < data.channels.length; i++) { + var ch = data.channels[i]; + infoLog += "Name: " + ch.name + "\n"; + infoLog += "Desc: " + ch.description + "\n"; + infoLog += "Created: " + convertToDate(ch.date_created) + "\n"; + infoLog += "----------------\n"; + } + + infoLog += "Default channel id: " + (data.default_channel ?? ""); + clientChatResponse(infoLog); + }); +}, null, "get info about a chat channel", null); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index 2d7415bf..444244be 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6604,6 +6604,18 @@ var network = { kind: "worlds", request: cb_id }); + }, + chat_channel: function(location, callback) { + var cb_id = void 0; + if(callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "chat_channel", + location, + request: cb_id + }); } }; @@ -8162,6 +8174,15 @@ var ws_functions = { cb(data); } } + }, + chat_channel: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } } }; diff --git a/runserver.js b/runserver.js index f683559e..feafbecb 100644 --- a/runserver.js +++ b/runserver.js @@ -610,7 +610,8 @@ var websockets = { whoami: require("./backend/websockets/whoami.js"), chat_test: require("./backend/websockets/chat_test.js"), chat_delete_req: require("./backend/websockets/chat_delete_req.js"), - worlds: require("./backend/websockets/worlds.js") + worlds: require("./backend/websockets/worlds.js"), + chat_channel: require("./backend/websockets/chat_channel.js") }; var modules = { From aa0c5770dda1f5a85d86e671f0d357483bb3e8af Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:30:21 +0700 Subject: [PATCH 06/11] move /mute and /clearmutes to client --- backend/subsystems/chat_mgr.js | 41 ++++++++- backend/utils/utils.js | 34 ++++++- backend/websockets/chat.js | 125 ++------------------------ backend/websockets/clear_mutes.js | 31 +++++++ backend/websockets/mute.js | 52 +++++++++++ frontend/static/yw/javascript/chat.js | 50 +++++++++++ frontend/static/yw/javascript/owot.js | 44 +++++++++ runserver.js | 4 +- 8 files changed, 259 insertions(+), 122 deletions(-) create mode 100644 backend/websockets/clear_mutes.js create mode 100644 backend/websockets/mute.js diff --git a/backend/subsystems/chat_mgr.js b/backend/subsystems/chat_mgr.js index 19caecd0..7a4eef36 100644 --- a/backend/subsystems/chat_mgr.js +++ b/backend/subsystems/chat_mgr.js @@ -314,7 +314,46 @@ async function chatDatabaseClock(serverExit) { } } +var muted_ips_by_world_id = {}; // id 0 = global +function getMuteInfo(world_id, ip) { + var worldChatMutes = muted_ips_by_world_id[world_id]; + + if(worldChatMutes) { + return worldChatMutes[ip]; + } + + return null; +} + +function mute(world_id, ip, date) { + if(!muted_ips_by_world_id[world_id]) muted_ips_by_world_id[world_id] = {}; + muted_ips_by_world_id[world_id][ip] = [date]; +} + +function clearMutes(world_id) { + var cnt = 0; + + if(muted_ips_by_world_id[world_id]) { + cnt = Object.keys(muted_ips_by_world_id[world_id]).length; + delete muted_ips_by_world_id[world_id]; + } + + return cnt; +} + +function unmute(world_id, ip) { + var worldChatMutes = muted_ips_by_world_id[world_id]; + + if(worldChatMutes) { + delete worldChatMutes[ip]; + } +} + module.exports.retrieveChatHistory = retrieveChatHistory; module.exports.add_to_chatlog = add_to_chatlog; module.exports.remove_from_chatlog = remove_from_chatlog; -module.exports.clearChatlog = clearChatlog; \ No newline at end of file +module.exports.clearChatlog = clearChatlog; +module.exports.getMuteInfo = getMuteInfo; +module.exports.mute = mute; +module.exports.clearMutes = clearMutes; +module.exports.unmute = unmute; diff --git a/backend/utils/utils.js b/backend/utils/utils.js index 7a80bb7d..500a78af 100644 --- a/backend/utils/utils.js +++ b/backend/utils/utils.js @@ -820,6 +820,37 @@ function checkURLParam(mask, url) { return values; } +function getClientIPByChatID(server, world_id, id, isGlobal) { + var client_ips = server.client_ips; + + if(isGlobal) { + // since this is global, there is the potential for duplicate IDs. + // pick the one that has chatted the most recently. + var latestGCli = null; + var latestGCliTime = -1; + for(var cw in client_ips) { + var worldClients = client_ips[cw]; + if(worldClients[id]) { + var gCli = worldClients[id]; + if(gCli[3] != -1 && gCli[3] >= latestGCliTime) { + latestGCliTime = gCli[3]; + latestGCli = gCli; + } + } + } + if(latestGCli) { + return latestGCli[0]; + } + } else { + if(client_ips[world_id]) { + if(client_ips[world_id][id]) { + return client_ips[world_id][id][0]; + } + } + } + return null; +} + module.exports = { trimHTML, create_date, @@ -851,5 +882,6 @@ module.exports = { trimSlash, checkDuplicateCookie, toHex64, - toInt64 + toInt64, + getClientIPByChatID }; \ No newline at end of file diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index fb40926c..2116ea5b 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -3,6 +3,7 @@ var html_tag_esc = utils.html_tag_esc; var san_nbr = utils.san_nbr; var calculateTimeDiff = utils.calculateTimeDiff; var create_date = utils.create_date; +var getClientIPByChatID = utils.getClientIPByChatID; function sanitizeColor(col) { var masks = ["#XXXXXX", "#XXX"]; @@ -54,7 +55,6 @@ function sanitizeCustomMeta(meta) { var chat_ip_limits = {}; var tell_blocks = {}; -var blocked_ips_by_world_id = {}; // id 0 = global module.exports = async function(ws, data, send, broadcast, server, ctx) { var channel = ctx.channel; @@ -135,24 +135,15 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { return; } + var muteInfo = chat_mgr.getMuteInfo(location == "global" ? 0 : world.id, ipHeaderAddr); var isMuted = false; - var muteInfo = null; - var worldChatMutes = blocked_ips_by_world_id[world.id]; - if(location == "global") { - worldChatMutes = blocked_ips_by_world_id[0]; - } - if(worldChatMutes) { - muteInfo = worldChatMutes[ipHeaderAddr]; - if(muteInfo) { - isMuted = true; - } - } + if (muteInfo) isMuted = true; if(isMuted) { var expTime = muteInfo[0]; if(!expTime || typeof expTime != "number" || Date.now() >= expTime) { isMuted = false; - delete worldChatMutes[ipHeaderAddr]; + chat_mgr.unmute(location == "global" ? 0 : world.id, ipHeaderAddr); } } @@ -210,8 +201,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { [0, "unblock", ["id"], "unblock someone by id", "1220"], [0, "unblockuser", ["username"], "unblock someone by username", "JohnDoe"], [0, "unblockall", null, "unblock all users", null], - [0, "mute", ["id", "seconds", "[h/d/w/m/y]"], "mute a user completely", "1220 9999"], // check for permission - [0, "clearmutes", null, "unmute all clients"], // check for permission [0, "tell", ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"] // hidden by default @@ -244,12 +233,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var desc = row[3]; var example = row[4]; - if(command == "mute" || command == "clearmutes") { - if(!user.staff && !is_owner) { - continue; - } - } - var rawArgs = ""; var rawExample = ""; if(example) rawExample = " (/" + command + " " + example + ")"; @@ -261,35 +244,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { return html; } - function getClientIPByChatID(id, isGlobal) { - if(isGlobal) { - // since this is global, there is the potential for duplicate IDs. - // pick the one that has chatted the most recently. - var latestGCli = null; - var latestGCliTime = -1; - for(var cw in client_ips) { - var worldClients = client_ips[cw]; - if(worldClients[id]) { - var gCli = worldClients[id]; - if(gCli[3] != -1 && gCli[3] >= latestGCliTime) { - latestGCliTime = gCli[3]; - latestGCli = gCli; - } - } - } - if(latestGCli) { - return latestGCli[0]; - } - } else { - if(client_ips[world.id]) { - if(client_ips[world.id][id]) { - return client_ips[world.id][id][0]; - } - } - } - return null; - } - var com = { help: function(modifier) { return serverChatResponse(generate_command_list(), location); @@ -320,7 +274,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { blocks.id.push(id); } - var blocked_ip = getClientIPByChatID(id, location == "global"); + var blocked_ip = getClientIPByChatID(server, world.id, id, location == "global"); if(blocked_ip) { var blist = tell_blocks[ipHeaderAddr]; if(!blist) { @@ -378,7 +332,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { blocks.id.splice(idx, 1); } - var unblocked_ip = getClientIPByChatID(id, location == "global"); + var unblocked_ip = getClientIPByChatID(server, world.id, id, location == "global"); if(unblocked_ip) { var blist = tell_blocks[ipHeaderAddr]; if(blist) { @@ -529,67 +483,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } broadcastMonitorEvent("TellSpam", "Tell from " + clientId + " (" + ipHeaderAddr + ") to " + id + ", first 4 chars: [" + message.slice(0, 4) + "]"); }, - mute: function(id, time, flag) { - if(!is_owner && !user.staff) return; - id = san_nbr(id); - time = san_nbr(time); // in seconds - - var timeSuffixMap = { - "h": 3600, - "d": 86400, - "w": 86400*7, - "m": 86400*30, - "y": 31556925.216 //average year length - }; - - if(flag in timeSuffixMap) { - time *= timeSuffixMap[flag]; - } else { - if(flag) { //invalid flag - return serverChatResponse("Invalid flag used for muting, must be h, d, w, m, or y.") - } - } - - if(location == "global" && !user.staff) { - return serverChatResponse("You do not have permission to mute on global", location); - } - - var muted_ip = getClientIPByChatID(id, location == "global"); - - if(muted_ip) { - var muteDate = Date.now() + (time * 1000); - var mute_wid = null; - if(location == "global") { - mute_wid = 0; - } else if(location == "page") { - mute_wid = world.id; - } - if(mute_wid == null) { - return serverChatResponse("Invalid location", location); - } - if(!blocked_ips_by_world_id[mute_wid]) blocked_ips_by_world_id[mute_wid] = {}; - blocked_ips_by_world_id[mute_wid][muted_ip] = [muteDate]; - return serverChatResponse("Muted client until " + create_date(muteDate), location); - } else { - return serverChatResponse("Client not found", location); - } - }, - clearmutes: function() { - if(!is_owner && !user.staff) return; - var cnt = 0; - if(location == "global" && user.staff) { - if(blocked_ips_by_world_id["0"]) { - cnt = Object.keys(blocked_ips_by_world_id["0"]).length; - delete blocked_ips_by_world_id["0"]; - } - } else { - if(blocked_ips_by_world_id[world.id]) { - cnt = Object.keys(blocked_ips_by_world_id[world.id]).length; - delete blocked_ips_by_world_id[world.id]; - } - } - return serverChatResponse("Unmuted " + cnt + " user(s)", location); - }, passive: function(mode) { if(mode == "on") { ws.sdata.passiveCmd = true; @@ -661,12 +554,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "tell": com.tell(commandArgs[1], commandArgs.slice(2).join(" ")); return; - case "mute": - com.mute(commandArgs[1], commandArgs[2], commandArgs[3]); - return; - case "clearmutes": - com.clearmutes(); - return; case "passive": com.passive(commandArgs[1]); return; diff --git a/backend/websockets/clear_mutes.js b/backend/websockets/clear_mutes.js new file mode 100644 index 00000000..28e2c907 --- /dev/null +++ b/backend/websockets/clear_mutes.js @@ -0,0 +1,31 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var user = ctx.user; + var world = ctx.world; + var chat_mgr = server.chat_mgr; + var is_owner = world.ownerId == user.id; + + if(!is_owner && !user.staff) { + send({ + success: false, + error: "no_perm" + }); + return; + } + + var location = ""; + if(!(data.location == "global" || data.location == "page")) data.location = "page"; + location = data.location; + + if(location == "global" && !user.staff) { + send({ + success: false, + error: "no_perm" + }); + return; + } + + send({ + success: true, + count: chat_mgr.clearMutes(location == "global" ? 0 : world.id) + }); +} diff --git a/backend/websockets/mute.js b/backend/websockets/mute.js new file mode 100644 index 00000000..89683bda --- /dev/null +++ b/backend/websockets/mute.js @@ -0,0 +1,52 @@ +var utils = require("../utils/utils.js"); +var san_nbr = utils.san_nbr; +var getClientIPByChatID = utils.getClientIPByChatID; + +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var user = ctx.user; + var world = ctx.world; + var chat_mgr = server.chat_mgr; + var is_owner = world.ownerId == user.id; + + if(!is_owner && !user.staff) { + send({ + success: false, + error: "no_perm" + }); + return; + } + + var id = san_nbr(data.id); + var time = san_nbr(data.time); // in seconds + + var location = ""; + if(!(data.location == "global" || data.location == "page")) data.location = "page"; + location = data.location; + + if(location == "global" && !user.staff) { + send({ + success: false, + error: "no_perm" + }); + return; + } + + var muted_ip = getClientIPByChatID(server, world.id, id, location == "global"); + + if(muted_ip) { + var muteDate = Date.now() + (time * 1000); + var mute_wid = location == "global" ? 0 : world.id; + + chat_mgr.mute(mute_wid, muted_ip, muteDate); + + send({ + success: true, + until: muteDate + }); + } else { + send({ + success: false, + error: "not_found" + }); + } +} diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 3296f4ae..83c22067 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -358,6 +358,56 @@ register_chat_command("channel", function() { }); }, null, "get info about a chat channel", null); +register_chat_command("mute", function(args) { + var id = Number(args[0]); + var seconds = Number(args[1]); + var flag = args[2]; + var location = selectedChatTab == 0 ? "page" : "global"; + + var timeSuffixMap = { + "h": 3600, + "d": 86400, + "w": 86400*7, + "m": 86400*30, + "y": 31556925.216 //average year length + }; + + if(flag in timeSuffixMap) { + seconds *= timeSuffixMap[flag]; + } else { + if(flag) { //invalid flag + clientChatResponse("Invalid flag used for muting, must be h, d, w, m, or y."); + return; + } + } + + network.mute(id, seconds, location, function(data) { + if(data.success) { + clientChatResponse("Muted client until " + convertToDate(data.until)); + } else { + if(data.error == "no_perm") { + clientChatResponse("You do not have permission to mute here"); + } else if(data.error == "not_found") { + clientChatResponse("Client not found"); + } + } + }); +}, ["id", "seconds", "[h/d/w/m/y]"], "mute a user completely", "1220 9999"); + +register_chat_command("clearmutes", function() { + var location = selectedChatTab == 0 ? "page" : "global"; + + network.clear_mutes(location, function(data) { + if(data.success) { + clientChatResponse("Unmuted " + data.count + " user(s)"); + } else { + if(data.error == "no_perm") { + clientChatResponse("You do not have permission to unmute here"); + } + } + }); +}, null, "unmute all clients", null); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index 444244be..ab410b37 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6616,6 +6616,32 @@ var network = { location, request: cb_id }); + }, + mute: function(id, time, location, callback) { + var cb_id = void 0; + if(callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "mute", + id, + time, + location, + request: cb_id + }); + }, + clear_mutes: function(location, callback) { + var cb_id = void 0; + if(callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "clear_mutes", + location, + request: cb_id + }); } }; @@ -8183,6 +8209,24 @@ var ws_functions = { cb(data); } } + }, + mute: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } + }, + clear_mutes: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } } }; diff --git a/runserver.js b/runserver.js index feafbecb..f65726bc 100644 --- a/runserver.js +++ b/runserver.js @@ -611,7 +611,9 @@ var websockets = { chat_test: require("./backend/websockets/chat_test.js"), chat_delete_req: require("./backend/websockets/chat_delete_req.js"), worlds: require("./backend/websockets/worlds.js"), - chat_channel: require("./backend/websockets/chat_channel.js") + chat_channel: require("./backend/websockets/chat_channel.js"), + mute: require("./backend/websockets/mute.js"), + clear_mutes: require("./backend/websockets/clear_mutes.js") }; var modules = { From 82214bc125bf2c767ef247b7d13efd21503bfbbc Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:14:54 +0700 Subject: [PATCH 07/11] move /tell to client --- backend/websockets/chat.js | 213 ++++++++++++-------------- frontend/static/yw/javascript/chat.js | 17 +- frontend/static/yw/javascript/owot.js | 5 +- 3 files changed, 117 insertions(+), 118 deletions(-) diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 2116ea5b..7bfc7c8b 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -200,8 +200,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { [0, "blockuser", ["username"], "block someone by username", "JohnDoe"], [0, "unblock", ["id"], "unblock someone by id", "1220"], [0, "unblockuser", ["username"], "unblock someone by username", "JohnDoe"], - [0, "unblockall", null, "unblock all users", null], - [0, "tell", ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"] + [0, "unblockall", null, "unblock all users", null] // hidden by default // "/search Phrase" (client) -> searches for Phrase within a 25 tile radius @@ -374,115 +373,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } serverChatResponse("Cleared all blocks", location); }, - tell: function(id, message) { - id += ""; - message += ""; - message = message.trim(); - var noClient = false; - if(!id) { - return serverChatResponse("No id given", location); - } - if(!message) { - return serverChatResponse("No message given", location); - } - id = parseInt(id, 10); - if(isNaN(id)) { - return serverChatResponse("Invalid ID format", location); - } - id = san_nbr(id); - - var client = null; - var latestGlobalClientTime = -1; - wss.clients.forEach(function(ws) { - if(!ws.sdata) return; - if(!ws.sdata.userClient) return; - var dstClientId = ws.sdata.clientId; - var clientWorld = ws.sdata.world; - if(dstClientId != id) return; - if(location == "page") { - if(clientWorld.id == world.id) { - client = ws; - } - } else if(location == "global") { - var cliObj = client_ips[clientWorld.id][dstClientId]; - var cliTime = cliObj[3]; - var disconnected = cliObj[2]; - if((!disconnected && cliTime != -1 && cliTime >= latestGlobalClientTime) || (dstClientId == clientId)) { // allow self-dm - latestGlobalClientTime = cliTime; - client = ws; - } - } - }); - - if(!client) { - noClient = true; - } - - hasPrivateMsged = true; - - if(isMuted) return; - - if(noClient) { - return serverChatResponse("User not found", location); - } - - var privateMessage = { - nickname: nick, - realUsername: username_to_display, - id: clientId, // client id of sender - message: message, - registered: user.authenticated, - location: location, - op: user.operator, - admin: user.superuser, - staff: user.staff, - color: data.color, - kind: "chat", - privateMessage: "to_me", - customMeta: data.customMeta - }; - - if(user.authenticated && user.id in ranks_cache.users) { - var rank = ranks_cache[ranks_cache.users[user.id]]; - privateMessage.rankName = rank.name; - privateMessage.rankColor = rank.chat_color; - } - send({ - nickname: "", - realUsername: "", - id: id, // client id of receiver - message: message, - registered: false, - location: location, - op: false, - admin: false, - staff: false, - color: "#000000", - kind: "chat", - privateMessage: "from_me", - customMeta: data.customMeta - }); - // if user has blocked TELLs, don't let the /tell-er know - if(client.sdata.chat_blocks.block_all) return; - if(client.sdata.chat_blocks.no_tell) return; - if(client.sdata.chat_blocks.no_anon && !user.authenticated) return; - if(client.sdata.chat_blocks.no_reg && user.authenticated) return; - if(user.authenticated && client.sdata.chat_blocks.user.includes(username_to_display.toUpperCase())) { - return; // sender username is blocked by destination user - } - - // user has blocked the TELLer by IP - var tellblock = tell_blocks[client.sdata.ipAddress]; - if(tellblock && tellblock[ipHeaderAddr]) { - return; - } - - wsSend(client, JSON.stringify(privateMessage)); - if(clientIpObj && location == "global") { - clientIpObj[3] = Date.now(); - } - broadcastMonitorEvent("TellSpam", "Tell from " + clientId + " (" + ipHeaderAddr + ") to " + id + ", first 4 chars: [" + message.slice(0, 4) + "]"); - }, passive: function(mode) { if(mode == "on") { ws.sdata.passiveCmd = true; @@ -507,7 +397,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { if(is_member) chatsEverySecond = 8; if(is_owner) chatsEverySecond = 512; } - if(isCommand && commandType != "tell") chatsEverySecond = 512; + if(isCommand) chatsEverySecond = 512; if(!chat_ip_limits[ipHeaderAddr]) { chat_ip_limits[ipHeaderAddr] = {}; @@ -551,9 +441,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "unblockall": com.unblockall(); return; - case "tell": - com.tell(commandArgs[1], commandArgs.slice(2).join(" ")); - return; case "passive": com.passive(commandArgs[1]); return; @@ -562,6 +449,102 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } } + if(data.privateMessageTo) { + var noClient = false; + var id = san_nbr(data.privateMessageTo); + + var client = null; + var latestGlobalClientTime = -1; + wss.clients.forEach(function(ws) { + if(!ws.sdata) return; + if(!ws.sdata.userClient) return; + var dstClientId = ws.sdata.clientId; + var clientWorld = ws.sdata.world; + if(dstClientId != id) return; + if(location == "page") { + if(clientWorld.id == world.id) { + client = ws; + } + } else if(location == "global") { + var cliObj = client_ips[clientWorld.id][dstClientId]; + var cliTime = cliObj[3]; + var disconnected = cliObj[2]; + if((!disconnected && cliTime != -1 && cliTime >= latestGlobalClientTime) || (dstClientId == clientId)) { // allow self-dm + latestGlobalClientTime = cliTime; + client = ws; + } + } + }); + + if(!client) { + noClient = true; + } + + if(isMuted) return; + + if(noClient) { + return serverChatResponse("User not found", location); + } + + var privateMessage = { + nickname: nick, + realUsername: username_to_display, + id: clientId, // client id of sender + message: msg, + registered: user.authenticated, + location: location, + op: user.operator, + admin: user.superuser, + staff: user.staff, + color: data.color, + kind: "chat", + privateMessage: "to_me", + customMeta: data.customMeta + }; + + if(user.authenticated && user.id in ranks_cache.users) { + var rank = ranks_cache[ranks_cache.users[user.id]]; + privateMessage.rankName = rank.name; + privateMessage.rankColor = rank.chat_color; + } + send({ + nickname: "", + realUsername: "", + id: id, // client id of receiver + message: msg, + registered: false, + location: location, + op: false, + admin: false, + staff: false, + color: "#000000", + kind: "chat", + privateMessage: "from_me", + customMeta: data.customMeta + }); + // if user has blocked TELLs, don't let the /tell-er know + if(client.sdata.chat_blocks.block_all) return; + if(client.sdata.chat_blocks.no_tell) return; + if(client.sdata.chat_blocks.no_anon && !user.authenticated) return; + if(client.sdata.chat_blocks.no_reg && user.authenticated) return; + if(user.authenticated && client.sdata.chat_blocks.user.includes(username_to_display.toUpperCase())) { + return; // sender username is blocked by destination user + } + + // user has blocked the TELLer by IP + var tellblock = tell_blocks[client.sdata.ipAddress]; + if(tellblock && tellblock[ipHeaderAddr]) { + return; + } + + wsSend(client, JSON.stringify(privateMessage)); + if(clientIpObj && location == "global") { + clientIpObj[3] = Date.now(); + } + broadcastMonitorEvent("TellSpam", "Tell from " + clientId + " (" + ipHeaderAddr + ") to " + id + ", first 4 chars: [" + msg.slice(0, 4) + "]"); + return; + } + var chatData = { nickname: nick, realUsername: username_to_display, diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 83c22067..278f42d1 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -131,7 +131,7 @@ function api_chat_send(message, opts) { } } - network.chat(message, location, nick, chatColor, customMeta); + network.chat(message, location, nick, chatColor, customMeta, opts.privateMessageTo); } function clientChatResponse(message) { @@ -408,6 +408,21 @@ register_chat_command("clearmutes", function() { }); }, null, "unmute all clients", null); +register_chat_command("tell", function(args) { + var id = args[0]; + var msg = args.slice(1).join(" "); + + var opts = { + privateMessageTo: id + }; + + if(defaultChatColor != null) { + opts.color = "#" + ("00000" + defaultChatColor.toString(16)).slice(-6); + } + + api_chat_send(msg, opts); +}, ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index ab410b37..184aae04 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6464,14 +6464,15 @@ var network = { } network.transmit(fetchReq); }, - chat: function(message, location, nickname, color, customMeta) { + chat: function(message, location, nickname, color, customMeta, pmTo) { network.transmit({ kind: "chat", nickname: nickname, message: message, location: location, color: color, - customMeta: customMeta + customMeta: customMeta, + privateMessageTo: pmTo }); }, ping: function(callback) { From 2536ff74308bd9e70c17546883c031dd004a20a2 Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:47:53 +0700 Subject: [PATCH 08/11] move /block(user) & /unblock(user/all) to client --- backend/subsystems/chat_mgr.js | 3 + backend/websockets/block_id.js | 69 ++++++++++++ backend/websockets/block_special.js | 8 ++ backend/websockets/block_user.js | 41 +++++++ backend/websockets/chat.js | 153 +------------------------- backend/websockets/unblock_all.js | 19 ++++ frontend/static/yw/javascript/chat.js | 94 ++++++++++++++++ frontend/static/yw/javascript/owot.js | 59 ++++++++++ runserver.js | 6 +- 9 files changed, 300 insertions(+), 152 deletions(-) create mode 100644 backend/websockets/block_id.js create mode 100644 backend/websockets/block_special.js create mode 100644 backend/websockets/block_user.js create mode 100644 backend/websockets/unblock_all.js diff --git a/backend/subsystems/chat_mgr.js b/backend/subsystems/chat_mgr.js index 7a4eef36..a3b537eb 100644 --- a/backend/subsystems/chat_mgr.js +++ b/backend/subsystems/chat_mgr.js @@ -349,6 +349,8 @@ function unmute(world_id, ip) { } } +var tell_blocks = {}; + module.exports.retrieveChatHistory = retrieveChatHistory; module.exports.add_to_chatlog = add_to_chatlog; module.exports.remove_from_chatlog = remove_from_chatlog; @@ -357,3 +359,4 @@ module.exports.getMuteInfo = getMuteInfo; module.exports.mute = mute; module.exports.clearMutes = clearMutes; module.exports.unmute = unmute; +module.exports.tell_blocks = tell_blocks; diff --git a/backend/websockets/block_id.js b/backend/websockets/block_id.js new file mode 100644 index 00000000..98ced278 --- /dev/null +++ b/backend/websockets/block_id.js @@ -0,0 +1,69 @@ +var utils = require("../utils/utils.js"); +var san_nbr = utils.san_nbr; +var getClientIPByChatID = utils.getClientIPByChatID; + +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var world = ctx.world + var chat_mgr = server.chat_mgr; + var tell_blocks = chat_mgr.tell_blocks; + + var ipHeaderAddr = ws.sdata.ipAddress; + var blocks = ws.sdata.chat_blocks; + + var id = san_nbr(data.id); + if(id < 0) { + if(data.block) { + send({ + success: false, + error: "bad_id" + }); + } + + return; + } + + var location = ""; + if(!(data.location == "global" || data.location == "page")) data.location = "page"; + location = data.location; + + if(data.block) { + if ((blocks.id.length + blocks.user.length) >= 1280) { + send({ + success: false, + error: "too_many" + }); + return; + } + + if (blocks.id.indexOf(id) > -1) return; + blocks.id.push(id); + + var blocked_ip = getClientIPByChatID(server, world.id, id, location == "global"); + if(blocked_ip) { + var blist = tell_blocks[ipHeaderAddr]; + if(!blist) { + blist = {}; + tell_blocks[ipHeaderAddr] = blist; + } + if(!blist[blocked_ip]) { + blist[blocked_ip] = Date.now(); + } + } + + send({ + success: true + }); + } else { + var idx = blocks.id.indexOf(id); + if(idx == -1) return; + blocks.id.splice(idx, 1); + + var unblocked_ip = getClientIPByChatID(server, world.id, id, location == "global"); + if(unblocked_ip) { + var blist = tell_blocks[ipHeaderAddr]; + if(blist) { + delete blist[unblocked_ip]; + } + } + } +} diff --git a/backend/websockets/block_special.js b/backend/websockets/block_special.js new file mode 100644 index 00000000..8fa9505e --- /dev/null +++ b/backend/websockets/block_special.js @@ -0,0 +1,8 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var blocks = ws.sdata.chat_blocks; + + if (typeof data.all == "boolean") blocks.block_all = data.all; + if (typeof data.tell == "boolean") blocks.no_tell = data.tell; + if (typeof data.anon == "boolean") blocks.no_anon = data.anon; + if (typeof data.reg == "boolean") blocks.no_reg = data.reg; +} diff --git a/backend/websockets/block_user.js b/backend/websockets/block_user.js new file mode 100644 index 00000000..3790b7f5 --- /dev/null +++ b/backend/websockets/block_user.js @@ -0,0 +1,41 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var blocks = ws.sdata.chat_blocks; + + var username = data.username; + if(typeof username != "string" || !username) { + send({ + success: false, + error: "bad_username" + }); + return; + } + + if(data.block) { + if (!/^[^\s\x00-\x20]+$/.test(username)) return; + + // The case-insensitive value to be stored in chat_blocks. + var username_value = username.toUpperCase(); + + // Ensure maximum block count not exceeded, and check if it already exists. + if ((blocks.id.length + blocks.user.length) >= 1280) { + send({ + success: false, + error: "too_many" + }); + return; + } + + if (blocks.user.indexOf(username_value) > -1) return; + blocks.user.push(username_value); + + send({ + success: true + }); + } else { + var username_value = username.toUpperCase(); + + var idx = blocks.user.indexOf(username_value); + if(idx == -1) return; + blocks.user.splice(idx, 1); + } +} diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 7bfc7c8b..20d338f7 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -54,7 +54,6 @@ function sanitizeCustomMeta(meta) { } var chat_ip_limits = {}; -var tell_blocks = {}; module.exports = async function(ws, data, send, broadcast, server, ctx) { var channel = ctx.channel; @@ -64,6 +63,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var db = server.db; var ws_broadcast = server.ws_broadcast; // site-wide broadcast var chat_mgr = server.chat_mgr; + var tell_blocks = chat_mgr.tell_blocks; var wss = server.wss; var ranks_cache = server.ranks_cache; var accountSystem = server.accountSystem; @@ -189,18 +189,10 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { username_to_display = user.display_username; } - var chatBlockLimit = 1280; - // [rank, name, args, description, example] var command_list = [ // general - [0, "help", null, "list all commands", null], - - [0, "block", ["id"], "block someone by id", "1220"], - [0, "blockuser", ["username"], "block someone by username", "JohnDoe"], - [0, "unblock", ["id"], "unblock someone by id", "1220"], - [0, "unblockuser", ["username"], "unblock someone by username", "JohnDoe"], - [0, "unblockall", null, "unblock all users", null] + [0, "help", null, "list all commands", null] // hidden by default // "/search Phrase" (client) -> searches for Phrase within a 25 tile radius @@ -247,132 +239,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { help: function(modifier) { return serverChatResponse(generate_command_list(), location); }, - block: function(id) { - var blocks = ws.sdata.chat_blocks; - - switch (id) { - case "*": - blocks.block_all = true; - break; - case "tell": - blocks.no_tell = true; - break; - case "anon": - blocks.no_anon = true; - break; - case "reg": - blocks.no_reg = true; - break; - default: - id = san_nbr(id); - if (id < 0) return; - - if ((blocks.id.length + blocks.user.length) >= chatBlockLimit) - return serverChatResponse("Too many blocked IDs/users", location); - if (blocks.id.indexOf(id) > -1) return; - blocks.id.push(id); - } - - var blocked_ip = getClientIPByChatID(server, world.id, id, location == "global"); - if(blocked_ip) { - var blist = tell_blocks[ipHeaderAddr]; - if(!blist) { - blist = {}; - tell_blocks[ipHeaderAddr] = blist; - } - if(!blist[blocked_ip]) { - blist[blocked_ip] = Date.now(); - } - } - - serverChatResponse("Blocked chats from ID: " + id, location); - }, - blockuser: function(username) { - var blocks = ws.sdata.chat_blocks; - if(typeof username != "string" || !username) { - serverChatResponse("Invalid username", location); - return; - } - - if (!/^[^\s\x00-\x20]+$/.test(username)) return; - - // The case-insensitive value to be stored in chat_blocks. - var username_value = username.toUpperCase(); - - // Ensure maximum block count not exceeded, and check if it already exists. - if ((blocks.id.length + blocks.user.length) >= chatBlockLimit) - return serverChatResponse("Too many blocked IDs/users", location); - if (blocks.user.indexOf(username_value) > -1) return; - blocks.user.push(username_value); - - serverChatResponse("Blocked chats from user: " + username, location); - }, - unblock: function(id) { - var blocks = ws.sdata.chat_blocks; - - switch (id) { - case "*": - blocks.block_all = false; - break; - case "tell": - blocks.no_tell = false; - break; - case "anon": - blocks.no_anon = false; - break; - case "reg": - blocks.no_reg = false; - default: - id = san_nbr(id); - if(id < 0) return; - - var idx = blocks.id.indexOf(id); - if(idx == -1) return; - blocks.id.splice(idx, 1); - } - - var unblocked_ip = getClientIPByChatID(server, world.id, id, location == "global"); - if(unblocked_ip) { - var blist = tell_blocks[ipHeaderAddr]; - if(blist) { - delete blist[unblocked_ip]; - } - } - - serverChatResponse("Unblocked chats from ID: " + id, location); - }, - unblockuser: function(username) { - var blocks = ws.sdata.chat_blocks; - if(typeof username != "string" || !username) { - serverChatResponse("Invalid username", location); - return; - } - - // The case-insensitive value to be stored in chat_blocks. - var username_value = username.toUpperCase(); - - var idx = blocks.user.indexOf(username_value); - if(idx == -1) return; - blocks.user.splice(idx, 1); - - serverChatResponse("Unblocked chats from user: " + username, location); - }, - unblockall: function() { - ws.sdata.chat_blocks.id.splice(0); - ws.sdata.chat_blocks.user.splice(0); - ws.sdata.chat_blocks.block_all = false; - ws.sdata.chat_blocks.no_tell = false; - ws.sdata.chat_blocks.no_anon = false; - ws.sdata.chat_blocks.no_reg = false; - - var tblocks = tell_blocks[ipHeaderAddr]; - if(tblocks) { - for(var b in tblocks) { - delete tblocks[b]; - } - } - serverChatResponse("Cleared all blocks", location); - }, passive: function(mode) { if(mode == "on") { ws.sdata.passiveCmd = true; @@ -426,21 +292,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { case "help": com.help(); return; - case "block": - com.block(commandArgs[1]); - return; - case "blockuser": - com.blockuser(commandArgs[1]); - return; - case "unblock": - com.unblock(commandArgs[1]); - return; - case "unblockuser": - com.unblockuser(commandArgs[1]); - return; - case "unblockall": - com.unblockall(); - return; case "passive": com.passive(commandArgs[1]); return; diff --git a/backend/websockets/unblock_all.js b/backend/websockets/unblock_all.js new file mode 100644 index 00000000..df6c3045 --- /dev/null +++ b/backend/websockets/unblock_all.js @@ -0,0 +1,19 @@ +module.exports = async function(ws, data, send, broadcast, server, ctx) { + var chat_mgr = server.chat_mgr; + var ipHeaderAddr = ws.sdata.ipAddress; + var tell_blocks = chat_mgr.tell_blocks; + + ws.sdata.chat_blocks.id.splice(0); + ws.sdata.chat_blocks.user.splice(0); + ws.sdata.chat_blocks.block_all = false; + ws.sdata.chat_blocks.no_tell = false; + ws.sdata.chat_blocks.no_anon = false; + ws.sdata.chat_blocks.no_reg = false; + + var tblocks = tell_blocks[ipHeaderAddr]; + if(tblocks) { + for(var b in tblocks) { + delete tblocks[b]; + } + } +} diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 278f42d1..4c27a12b 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -423,6 +423,100 @@ register_chat_command("tell", function(args) { api_chat_send(msg, opts); }, ["id", "message"], "tell someone a secret message", "1220 The coordinates are (392, 392)"); +register_chat_command("block", function(args) { + var id = args[0]; + var location = selectedChatTab == 0 ? "page" : "global"; + + switch (id) { + case "*": + network.block_special(true, null, null, null); + clientChatResponse("Blocked all messages"); + break; + case "tell": + network.block_special(null, true, null, null); + clientChatResponse("Blocked private messages"); + break; + case "anon": + network.block_special(null, null, true, null); + clientChatResponse("Blocked messages from anonymous users"); + break; + case "reg": + network.block_special(null, null, null, true); + clientChatResponse("Blocked messages from registered users"); + break; + default: + id = Number(id); + network.block_id(id, location, true, function(data) { + if(data.success) { + clientChatResponse("Blocked chats from ID: " + id); + } else { + if(data.error == "too_many") { + clientChatResponse("Too many blocked IDs/users"); + } else if(data.error == "bad_id") { + clientChatResponse("Invalid ID"); + } + } + }); + } +}, ["id"], "block someone by id", "1220"); + +register_chat_command("unblock", function(args) { + var id = args[0]; + var location = selectedChatTab == 0 ? "page" : "global"; + + switch (id) { + case "*": + network.block_special(false, null, null, null); + clientChatResponse("Unblocked all messages"); + break; + case "tell": + network.block_special(null, false, null, null); + clientChatResponse("Unblocked private messages"); + break; + case "anon": + network.block_special(null, null, false, null); + clientChatResponse("Unblocked messages from anonymous users"); + break; + case "reg": + network.block_special(null, null, null, false); + clientChatResponse("Unblocked messages from registered users"); + break; + default: + id = Number(id); + network.block_id(id, location, false); + clientChatResponse("Unblocked chats from ID: " + id); + } + +}, ["id"], "unblock someone by id", "1220"); + +register_chat_command("blockuser", function(args) { + var username = args[0]; + + network.block_user(username, true, function(data) { + if(data.success) { + clientChatResponse("Blocked chats from user: " + username); + } else { + if(data.error == "too_many") { + clientChatResponse("Too many blocked IDs/users"); + } else if(data.error == "bad_username") { + clientChatResponse("Invalid username"); + } + } + }); +}, ["username"], "block someone by username", "JohnDoe"); + +register_chat_command("unblockuser", function(args) { + var username = args[0]; + + network.block_user(username, false); + clientChatResponse("Unblocked chats from user: " + username); +}, ["username"], "unblock someone by username", "JohnDoe"); + +register_chat_command("unblockall", function(args) { + network.unblock_all(); + clientChatResponse("Cleared all blocks"); +}, null, "unblock all users", null); + function sendChat() { var chatText = elm.chatbar.value; elm.chatbar.value = ""; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index 184aae04..6dc65aab 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -6643,6 +6643,47 @@ var network = { location, request: cb_id }); + }, + block_id: function(id, location, block, callback) { + var cb_id = void 0; + if(block && callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "block_id", + id, + location, + block, + request: cb_id + }); + }, + block_user: function(username, block, callback) { + var cb_id = void 0; + if(block && callback) { + cb_id = network.latestID++; + network.callbacks[cb_id] = callback; + } + network.transmit({ + kind: "block_user", + username, + block, + request: cb_id + }); + }, + block_special: function(all, tell, anon, reg) { + network.transmit({ + kind: "block_special", + all, + tell, + anon, + reg + }); + }, + unblock_all: function() { + network.transmit({ + kind: "unblock_all" + }); } }; @@ -8228,6 +8269,24 @@ var ws_functions = { cb(data); } } + }, + block_id: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } + }, + block_user: function(data) { + if(data.request) { + if(network.callbacks[data.request]) { + var cb = network.callbacks[data.request]; + delete network.callbacks[data.request]; + cb(data); + } + } } }; diff --git a/runserver.js b/runserver.js index f65726bc..1cf1b79e 100644 --- a/runserver.js +++ b/runserver.js @@ -613,7 +613,11 @@ var websockets = { worlds: require("./backend/websockets/worlds.js"), chat_channel: require("./backend/websockets/chat_channel.js"), mute: require("./backend/websockets/mute.js"), - clear_mutes: require("./backend/websockets/clear_mutes.js") + clear_mutes: require("./backend/websockets/clear_mutes.js"), + block_special: require("./backend/websockets/block_special.js"), + block_id: require("./backend/websockets/block_id.js"), + block_user: require("./backend/websockets/block_user.js"), + unblock_all: require("./backend/websockets/unblock_all.js") }; var modules = { From bffd0ebb58da095c623bc36644d2dee74aec2585 Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:58:18 +0700 Subject: [PATCH 09/11] move /help to client --- backend/websockets/chat.js | 52 -------- frontend/static/yw/javascript/chat.js | 166 ++++++++++---------------- 2 files changed, 63 insertions(+), 155 deletions(-) diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 20d338f7..6ddf5360 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -189,56 +189,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { username_to_display = user.display_username; } - // [rank, name, args, description, example] - var command_list = [ - // general - [0, "help", null, "list all commands", null] - - // hidden by default - // "/search Phrase" (client) -> searches for Phrase within a 25 tile radius - // "/passive on/off" -> disable or enable server responses to commands (e.g. /block) - ]; - - function generate_command_list() { - var list = []; - for(var i = 0; i < command_list.length; i++) { - var command = command_list[i]; - var rank = command[0]; - if(rank == 3 && user.operator) list.push(command); - if(rank == 2 && user.superuser) list.push(command); - if(rank == 1 && user.staff) list.push(command); - if(rank == 0) list.push(command); - } - - // sort the command list - list.sort(function(v1, v2) { - return v1[1].localeCompare(v2[1], "en", { sensitivity: "base" }); - }); - - var html = ""; - html += "Command list:\n"; - for(var i = 0; i < list.length; i++) { - var row = list[i]; - var command = row[1]; - var args = row[2]; - var desc = row[3]; - var example = row[4]; - - var rawArgs = ""; - var rawExample = ""; - if(example) rawExample = " (/" + command + " " + example + ")"; - if(args) rawArgs = " <" + args.join(",") + ">"; - - html += `/${command}${rawArgs} -> ${desc}${rawExample}\n`; - - } - return html; - } - var com = { - help: function(modifier) { - return serverChatResponse(generate_command_list(), location); - }, passive: function(mode) { if(mode == "on") { ws.sdata.passiveCmd = true; @@ -289,9 +240,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { var staff = user.staff; switch(commandType) { - case "help": - com.help(); - return; case "passive": com.passive(commandArgs[1]); return; diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 4c27a12b..11e1680f 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -150,6 +150,69 @@ function register_chat_command(command, callback, params, desc, example) { client_commands[command.toLowerCase()] = callback; } +register_chat_command("help", function() { + var cmdList = []; + var htmlResp = "
"; + var cmdIdx = 0; + + for(var cmd in chatCommandRegistry) { + var cliCmd = chatCommandRegistry[cmd]; + cmdList.push({ + command: cmd, + params: cliCmd.params, + desc: cliCmd.desc, + example: cliCmd.example + }); + } + + cmdList.sort(function(a, b) { + return a.command.localeCompare(b.command); + }); + + for(var i = 0; i < cmdList.length; i++) { + var info = cmdList[i]; + var command = info.command; + var params = info.params; + var example = info.example; + var desc = info.desc; + + // display command parameters + var param_desc = ""; + if(params) { + param_desc += html_tag_esc("<"); + for(var v = 0; v < params.length; v++) { + var arg = params[v]; + param_desc += "" + html_tag_esc(arg) + ""; + if(v != params.length - 1) { + param_desc += ", "; + } + } + param_desc += html_tag_esc(">"); + } + + var exampleElm = ""; + if(example && params) { + example = "/" + command + " " + example; + exampleElm = "title=\"" + html_tag_esc("Example: " + example) +"\""; + } + + command = "" + html_tag_esc(command) + ""; + + var help_row = html_tag_esc("-> /") + command + " " + param_desc + " :: " + html_tag_esc(desc); + + // alternating stripes + if(cmdIdx % 2 == 1) { + help_row = "
" + help_row + "
"; + } + + htmlResp += help_row; + cmdIdx++; + } + htmlResp += "
"; + + addChat(null, 0, "user", "[ Client ]", htmlResp, "Client", true, true, true, null, getDate()); +}, null, "list all commands", null); + register_chat_command("nick", function (args) { var newDisplayName = args.join(" "); if(!newDisplayName) { @@ -887,109 +950,6 @@ var emoteList = { "fppinchaaa": [128, 96] }; -w.on("chatMod", function(e) { - if(e.id !== 0) return; - if(e.realUsername != "[ Server ]") return; - if(e.message.startsWith("Command")) { - var cmdList = []; - var htmlResp = ""; - var remoteCmdList = e.message.split("\n"); - var head = remoteCmdList[0]; - - htmlResp += head + "
"; - htmlResp += "
"; - - var cmdIdx = 0; - for(var i = 1; i < remoteCmdList.length; i++) { - var line = remoteCmdList[i]; - if(!line.startsWith("/")) continue; - line = line.split(" -> "); - var cmdRaw = line[0].split(" "); - var params = cmdRaw[1]; - var command = cmdRaw[0].slice(1); - if(params) { - params = params.slice(1, -1).split(","); - } - var descRaw = line[1]; - var exampleStartIdx = descRaw.indexOf("("); - var example = ""; - if(exampleStartIdx > -1) { - example = descRaw.slice(exampleStartIdx + 1, -1); // remove parentheses - descRaw = descRaw.slice(0, exampleStartIdx - 1); - example = example.split(" ").slice(1).join(" "); - } - - cmdList.push({ - command: command, - params: params, - desc: descRaw, - example: example - }); - } - - for(var cmd in chatCommandRegistry) { - var cliCmd = chatCommandRegistry[cmd]; - cmdList.push({ - command: cmd, - params: cliCmd.params, - desc: cliCmd.desc, - example: cliCmd.example - }); - } - - cmdList.sort(function(a, b) { - return a.command.localeCompare(b.command); - }); - - for(var i = 0; i < cmdList.length; i++) { - var info = cmdList[i]; - var command = info.command; - var params = info.params; - var example = info.example; - var desc = info.desc; - - // display command parameters - var param_desc = ""; - if(params) { - param_desc += html_tag_esc("<"); - for(var v = 0; v < params.length; v++) { - var arg = params[v]; - param_desc += "" + html_tag_esc(arg) + ""; - if(v != params.length - 1) { - param_desc += ", "; - } - } - param_desc += html_tag_esc(">"); - } - - var exampleElm = ""; - if(example && params) { - example = "/" + command + " " + example; - exampleElm = "title=\"" + html_tag_esc("Example: " + example) +"\""; - } - - command = "" + html_tag_esc(command) + ""; - - var help_row = html_tag_esc("-> /") + command + " " + param_desc + " :: " + html_tag_esc(desc); - - // alternating stripes - if(cmdIdx % 2 == 1) { - help_row = "
" + help_row + "
"; - } - - htmlResp += help_row; - cmdIdx++; - } - htmlResp += "
"; - - e.message = htmlResp; - // upgrade permissions to allow display of HTML - e.op = true; - e.admin = true; - e.staff = true; - } -}); - /* [type]: * "user" :: registered non-renamed nick From 6a3edb245d8929734ccbaa7cd4c01b554e8e91a9 Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:05:12 +0700 Subject: [PATCH 10/11] remove remaining server command logic --- backend/websockets/chat.js | 57 +++++---------------------- frontend/static/yw/javascript/chat.js | 4 +- runserver.js | 1 - 3 files changed, 13 insertions(+), 49 deletions(-) diff --git a/backend/websockets/chat.js b/backend/websockets/chat.js index 6ddf5360..ba1aabab 100644 --- a/backend/websockets/chat.js +++ b/backend/websockets/chat.js @@ -2,8 +2,6 @@ var utils = require("../utils/utils.js"); var html_tag_esc = utils.html_tag_esc; var san_nbr = utils.san_nbr; var calculateTimeDiff = utils.calculateTimeDiff; -var create_date = utils.create_date; -var getClientIPByChatID = utils.getClientIPByChatID; function sanitizeColor(col) { var masks = ["#XXXXXX", "#XXX"]; @@ -98,7 +96,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { // sends `[ Server ]: ` in chat. function serverChatResponse(message, location) { - if(ws.sdata.passiveCmd) return; send({ nickname: "[ Server ]", realUsername: "[ Server ]", @@ -189,23 +186,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { username_to_display = user.display_username; } - var com = { - passive: function(mode) { - if(mode == "on") { - ws.sdata.passiveCmd = true; - } else if(mode == "off") { - ws.sdata.passiveCmd = false; - } - } - } - - var isCommand = (msg[0] == "/"); - var commandArgs, commandType; - if(isCommand) { - commandArgs = msg.slice(1).split(" "); - commandType = commandArgs[0].toLowerCase(); - } - // chat limiter var msNow = Date.now(); var second = Math.floor(msNow / 1000); @@ -214,7 +194,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { if(is_member) chatsEverySecond = 8; if(is_owner) chatsEverySecond = 512; } - if(isCommand) chatsEverySecond = 512; if(!chat_ip_limits[ipHeaderAddr]) { chat_ip_limits[ipHeaderAddr] = {}; @@ -234,20 +213,6 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } } - if(isCommand) { - var operator = user.operator; - var superuser = user.superuser; - var staff = user.staff; - - switch(commandType) { - case "passive": - com.passive(commandArgs[1]); - return; - default: - serverChatResponse("Invalid command: " + msg); - } - } - if(data.privateMessageTo) { var noClient = false; var id = san_nbr(data.privateMessageTo); @@ -374,7 +339,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { user: user, world: world, message: { - isCommand, + isCommand: false, isMuted, isOwner: is_owner, isMember: is_member, @@ -397,7 +362,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } } - if(!isCommand && !isMuted) { + if(!isMuted) { if(location == "page") { await add_to_chatlog(chatData, world.id); } else if(location == "global") { @@ -405,7 +370,7 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { } } - if(!isCommand && user.operator && !safeOrigin) { + if(user.operator && !safeOrigin) { msg = html_tag_esc(msg); chatData.message = msg; } @@ -427,14 +392,12 @@ module.exports = async function(ws, data, send, broadcast, server, ctx) { username: user.authenticated ? username_to_display.toUpperCase() : null }; - if(!isCommand) { - if(clientIpObj && location == "global") { - clientIpObj[3] = Date.now(); - } - if(location == "page") { - broadcast(websocketChatData, chatOpts); - } else if(location == "global") { - ws_broadcast(websocketChatData, void 0, chatOpts); - } + if(clientIpObj && location == "global") { + clientIpObj[3] = Date.now(); + } + if(location == "page") { + broadcast(websocketChatData, chatOpts); + } else if(location == "global") { + ws_broadcast(websocketChatData, void 0, chatOpts); } } diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 11e1680f..4a30dea9 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -127,8 +127,10 @@ function api_chat_send(message, opts) { args.shift(); if(client_commands.hasOwnProperty(command)) { client_commands[command](args); - return; + } else { + clientChatResponse("Invalid command: " + message); } + return; } network.chat(message, location, nick, chatColor, customMeta, opts.privateMessageTo); diff --git a/runserver.js b/runserver.js index 1cf1b79e..5449cb56 100644 --- a/runserver.js +++ b/runserver.js @@ -1915,7 +1915,6 @@ async function manageWebsocketConnection(ws, req) { messageBackpressure: 0, receiveContentUpdates: true, descriptiveCmd: false, - passiveCmd: false, handleCmdSockets: false, cmdsSentInSecond: 0, lastCmdSecond: 0, From 6a699fbed3c899288630cfb71395988576b9e1da Mon Sep 17 00:00:00 2001 From: KKosty4ka <49340075+KKosty4ka@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:32:38 +0700 Subject: [PATCH 11/11] fix /tell chat history bug --- frontend/static/yw/javascript/chat.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/static/yw/javascript/chat.js b/frontend/static/yw/javascript/chat.js index 4a30dea9..f96a2ec4 100644 --- a/frontend/static/yw/javascript/chat.js +++ b/frontend/static/yw/javascript/chat.js @@ -103,12 +103,15 @@ function api_chat_send(message, opts) { message = message.trim(); if(!message.length) return; message = message.slice(0, msgLim); - chatWriteHistory.push(message); - if(chatWriteHistory.length > chatWriteHistoryMax) { - chatWriteHistory.shift(); + + if (!opts.privateMessageTo) { + chatWriteHistory.push(message); + if(chatWriteHistory.length > chatWriteHistoryMax) { + chatWriteHistory.shift(); + } + chatWriteHistoryIdx = -1; + chatWriteTmpBuffer = ""; } - chatWriteHistoryIdx = -1; - chatWriteTmpBuffer = ""; var chatColor; if(!opts.color) {