diff options
-rw-r--r-- | public/common/play.js | 836 | ||||
-rw-r--r-- | public/sort.js | 101 | ||||
-rw-r--r-- | server.js | 1938 | ||||
-rw-r--r-- | views/chat.pug | 62 | ||||
-rw-r--r-- | views/forum_post.pug | 6 | ||||
-rw-r--r-- | views/join.pug | 10 | ||||
-rw-r--r-- | views/message_outbox.pug | 4 | ||||
-rw-r--r-- | views/message_read.pug | 6 |
8 files changed, 1499 insertions, 1464 deletions
diff --git a/public/common/play.js b/public/common/play.js index 80ead70..1df4708 100644 --- a/public/common/play.js +++ b/public/common/play.js @@ -1,18 +1,18 @@ -"use strict"; +"use strict" // TODO: Remove when CSS Images Module Level 4 is widely supported, -(function () { - let avif = new Image(); - avif.src = "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUEAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABkAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgSAAAAAAABNjb2xybmNseAABAA0ABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACFtZGF0EgAKBzgABhAQ0GkyDBZABhhhhAAAeUzRyg=="; - avif.onload = () => document.documentElement.className = "avif"; - avif.onerror = () => document.documentElement.className = "jpeg"; -})(); +;(function () { + let avif = new Image() + avif.src = "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUEAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABkAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgSAAAAAAABNjb2xybmNseAABAA0ABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACFtZGF0EgAKBzgABhAQ0GkyDBZABhhhhAAAeUzRyg==" + avif.onload = () => document.documentElement.className = "avif" + avif.onerror = () => document.documentElement.className = "jpeg" +})() /* URL: /$title_id/(re)play:$game_id:$role */ if (!/\/[\w-]+\/(re)?play:\d+(:[\w-]+)?/.test(window.location.pathname)) { - document.getElementById("prompt").textContent = "Invalid game ID."; - throw Error("Invalid game ID."); + document.getElementById("prompt").textContent = "Invalid game ID." + throw Error("Invalid game ID.") } let params = { @@ -22,129 +22,129 @@ let params = { role: decodeURIComponent(window.location.pathname.split("/")[2]).split(":")[2] || "Observer", } -let roles = Array.from(document.querySelectorAll(".role")).map(x=>({id:x.id,role:x.id.replace(/^role_/,"").replace(/_/g," ")})); +let roles = Array.from(document.querySelectorAll(".role")).map(x=>({id:x.id,role:x.id.replace(/^role_/,"").replace(/_/g," ")})) -let view = null; -let player = "Observer"; -let socket = null; -let chat = null; +let view = null +let player = "Observer" +let socket = null +let chat = null function scroll_with_middle_mouse(panel_sel, multiplier) { - let panel = document.querySelector(panel_sel); - let down_x, down_y, scroll_x, scroll_y; + let panel = document.querySelector(panel_sel) + let down_x, down_y, scroll_x, scroll_y if (!multiplier) - multiplier = 1; + multiplier = 1 function md(e) { if (e.button === 1) { - down_x = e.clientX; - down_y = e.clientY; - scroll_x = panel.scrollLeft; - scroll_y = panel.scrollTop; - window.addEventListener("mousemove", mm); - window.addEventListener("mouseup", mu); - e.preventDefault(); + down_x = e.clientX + down_y = e.clientY + scroll_x = panel.scrollLeft + scroll_y = panel.scrollTop + window.addEventListener("mousemove", mm) + window.addEventListener("mouseup", mu) + e.preventDefault() } } function mm(e) { - let dx = down_x - e.clientX; - let dy = down_y - e.clientY; - panel.scrollLeft = scroll_x + dx * multiplier; - panel.scrollTop = scroll_y + dy * multiplier; - e.preventDefault(); + let dx = down_x - e.clientX + let dy = down_y - e.clientY + panel.scrollLeft = scroll_x + dx * multiplier + panel.scrollTop = scroll_y + dy * multiplier + e.preventDefault() } function mu(e) { if (e.button === 1) { - window.removeEventListener("mousemove", mm); - window.removeEventListener("mouseup", mu); - e.preventDefault(); + window.removeEventListener("mousemove", mm) + window.removeEventListener("mouseup", mu) + e.preventDefault() } } - panel.addEventListener("mousedown", md); + panel.addEventListener("mousedown", md) } function drag_element_with_mouse(element_sel, grabber_sel) { - let element = document.querySelector(element_sel); - let grabber = document.querySelector(grabber_sel) || element; - let save_x, save_y; + let element = document.querySelector(element_sel) + let grabber = document.querySelector(grabber_sel) || element + let save_x, save_y function md(e) { if (e.button === 0) { - save_x = e.clientX; - save_y = e.clientY; - window.addEventListener("mousemove", mm); - window.addEventListener("mouseup", mu); - e.preventDefault(); + save_x = e.clientX + save_y = e.clientY + window.addEventListener("mousemove", mm) + window.addEventListener("mouseup", mu) + e.preventDefault() } } function mm(e) { - let dx = save_x - e.clientX; - let dy = save_y - e.clientY; - save_x = e.clientX; - save_y = e.clientY; - element.style.left = (element.offsetLeft - dx) + "px"; - element.style.top = (element.offsetTop - dy) + "px"; - e.preventDefault(); + let dx = save_x - e.clientX + let dy = save_y - e.clientY + save_x = e.clientX + save_y = e.clientY + element.style.left = (element.offsetLeft - dx) + "px" + element.style.top = (element.offsetTop - dy) + "px" + e.preventDefault() } function mu(e) { if (e.button === 0) { - window.removeEventListener("mousemove", mm); - window.removeEventListener("mouseup", mu); - e.preventDefault(); + window.removeEventListener("mousemove", mm) + window.removeEventListener("mouseup", mu) + e.preventDefault() } } - grabber.addEventListener("mousedown", md); + grabber.addEventListener("mousedown", md) } /* TITLE BLINKER */ -let blink_title = document.title; -let blink_timer = 0; +let blink_title = document.title +let blink_timer = 0 function start_blinker(message) { - let tick = false; + let tick = false if (blink_timer) - stop_blinker(); + stop_blinker() if (!document.hasFocus()) { - document.title = message; + document.title = message blink_timer = setInterval(function () { - document.title = tick ? message : blink_title; - tick = !tick; - }, 1000); + document.title = tick ? message : blink_title + tick = !tick + }, 1000) } } function stop_blinker() { - document.title = blink_title; - clearInterval(blink_timer); - blink_timer = 0; + document.title = blink_title + clearInterval(blink_timer) + blink_timer = 0 } -window.addEventListener("focus", stop_blinker); +window.addEventListener("focus", stop_blinker) /* CHAT */ function init_chat() { // only fetch new messages when we reconnect! if (chat !== null) { - send_message("getchat", chat.log); - return; + send_message("getchat", chat.log) + return } - let chat_window = document.createElement("div"); - chat_window.id = "chat_window"; + let chat_window = document.createElement("div") + chat_window.id = "chat_window" chat_window.innerHTML = ` <div id="chat_x" onclick="toggle_chat()">\u274c</div> <div id="chat_header">Chat</div> <div id="chat_text"></div> <form id="chat_form" action=""><input id="chat_input" autocomplete="off"></form> - `; - document.querySelector("body").appendChild(chat_window); + ` + document.querySelector("body").appendChild(chat_window) - let chat_button = document.createElement("div"); - chat_button.id = "chat_button"; - chat_button.className = "icon_button"; - chat_button.innerHTML = '<img src="/images/chat-bubble.svg">'; - chat_button.addEventListener("click", toggle_chat); - document.querySelector("#toolbar").appendChild(chat_button); + let chat_button = document.createElement("div") + chat_button.id = "chat_button" + chat_button.className = "icon_button" + chat_button.innerHTML = '<img src="/images/chat-bubble.svg">' + chat_button.addEventListener("click", toggle_chat) + document.querySelector("#toolbar").appendChild(chat_button) chat = { is_visible: false, @@ -154,737 +154,737 @@ function init_chat() { log: 0 } - chat.seen = window.localStorage.getItem(chat.key) | 0; + chat.seen = window.localStorage.getItem(chat.key) | 0 - drag_element_with_mouse("#chat_window", "#chat_header"); + drag_element_with_mouse("#chat_window", "#chat_header") document.getElementById("chat_form").addEventListener("submit", e => { - let input = document.getElementById("chat_input"); - e.preventDefault(); + let input = document.getElementById("chat_input") + e.preventDefault() if (input.value) { - send_message("chat", input.value); - input.value = ""; + send_message("chat", input.value) + input.value = "" } else { - hide_chat(); + hide_chat() } - }); + }) document.querySelector("body").addEventListener("keydown", e => { if (e.key === "Escape") { if (chat.is_visible) { - e.preventDefault(); - hide_chat(); + e.preventDefault() + hide_chat() } } if (e.key === "Enter") { - let input = document.getElementById("chat_input"); + let input = document.getElementById("chat_input") if (document.activeElement !== input) { - e.preventDefault(); - show_chat(); + e.preventDefault() + show_chat() } } - }); + }) - send_message("getchat", 0); + send_message("getchat", 0) } function save_chat() { - window.localStorage.setItem(chat.key, chat.log); + window.localStorage.setItem(chat.key, chat.log) } function update_chat(chat_id, utc_date, user, message) { function format_time(date) { - let mm = date.getMinutes(); - let hh = date.getHours(); - if (mm < 10) mm = "0" + mm; - if (hh < 10) hh = "0" + hh; - return hh + ":" + mm; + let mm = date.getMinutes() + let hh = date.getHours() + if (mm < 10) mm = "0" + mm + if (hh < 10) hh = "0" + hh + return hh + ":" + mm } function add_date_line(date) { - let line = document.createElement("div"); - line.className = "date"; - line.textContent = "~ " + date + " ~"; - chat.text_element.appendChild(line); + let line = document.createElement("div") + line.className = "date" + line.textContent = "~ " + date + " ~" + chat.text_element.appendChild(line) } function add_chat_line(time, user, message) { - let line = document.createElement("div"); - line.textContent = "[" + time + "] " + user + " \xbb " + message; - chat.text_element.appendChild(line); - chat.text_element.scrollTop = chat.text_element.scrollHeight; + let line = document.createElement("div") + line.textContent = "[" + time + "] " + user + " \xbb " + message + chat.text_element.appendChild(line) + chat.text_element.scrollTop = chat.text_element.scrollHeight } if (chat_id > chat.log) { - chat.log = chat_id; - let date = new Date(utc_date + "Z"); - let day = date.toDateString(); + chat.log = chat_id + let date = new Date(utc_date + "Z") + let day = date.toDateString() if (day !== chat.last_day) { - add_date_line(day); - chat.last_day = day; + add_date_line(day) + chat.last_day = day } - add_chat_line(format_time(date), user, message); + add_chat_line(format_time(date), user, message) } if (chat_id > chat.seen) { - let button = document.getElementById("chat_button"); - start_blinker("NEW MESSAGE"); + let button = document.getElementById("chat_button") + start_blinker("NEW MESSAGE") if (!chat.is_visible) - button.classList.add("new"); + button.classList.add("new") else - save_chat(); + save_chat() } } function show_chat() { if (!chat.is_visible) { - document.getElementById("chat_button").classList.remove("new"); - document.getElementById("chat_window").classList.add("show"); - document.getElementById("chat_input").focus(); - chat.is_visible = true; - save_chat(); + document.getElementById("chat_button").classList.remove("new") + document.getElementById("chat_window").classList.add("show") + document.getElementById("chat_input").focus() + chat.is_visible = true + save_chat() } } function hide_chat() { if (chat.is_visible) { - document.getElementById("chat_window").classList.remove("show"); - document.getElementById("chat_input").blur(); - chat.is_visible = false; + document.getElementById("chat_window").classList.remove("show") + document.getElementById("chat_input").blur() + chat.is_visible = false } } function toggle_chat() { if (chat.is_visible) - hide_chat(); + hide_chat() else - show_chat(); + show_chat() } /* REMATCH BUTTON */ function remove_resign_menu() { - document.querySelectorAll(".resign").forEach(x => x.remove()); + document.querySelectorAll(".resign").forEach(x => x.remove()) } function goto_rematch() { - window.location = "/rematch/" + params.game_id + "/" + params.role; + window.location = "/rematch/" + params.game_id + "/" + params.role } function goto_replay() { - window.location = "/" + params.title_id + "/replay:" + params.game_id; + window.location = "/" + params.title_id + "/replay:" + params.game_id } function on_game_over() { function icon_button(id, img, title, fn) { if (!document.getElementById(id)) { - let button = document.createElement("div"); - button.id = id; - button.title = title; - button.className = "icon_button"; - button.innerHTML = '<img src="/images/' + img + '.svg">'; - button.addEventListener("click", fn); - document.querySelector("header").appendChild(button); + let button = document.createElement("div") + button.id = id + button.title = title + button.className = "icon_button" + button.innerHTML = '<img src="/images/' + img + '.svg">' + button.addEventListener("click", fn) + document.querySelector("header").appendChild(button) } } - icon_button("replay_button", "sherlock-holmes-mirror", "Watch replay", goto_replay); + icon_button("replay_button", "sherlock-holmes-mirror", "Watch replay", goto_replay) if (player !== "Observer") - icon_button("rematch_button", "cycle", "Propose a rematch!", goto_rematch); - remove_resign_menu(); + icon_button("rematch_button", "cycle", "Propose a rematch!", goto_rematch) + remove_resign_menu() } /* CONNECT TO GAME SERVER */ function init_player_names(players) { for (let i = 0; i < roles.length; ++i) { - let sel = "#" + roles[i].id + " .role_user"; - let p = players.find(p => p.role === roles[i].role); - document.querySelector(sel).textContent = p ? p.name : "NONE"; + let sel = "#" + roles[i].id + " .role_user" + let p = players.find(p => p.role === roles[i].role) + document.querySelector(sel).textContent = p ? p.name : "NONE" } } function send_message(cmd, arg) { - let data = JSON.stringify([cmd, arg]); - console.log("SEND %s %s", cmd, arg); - socket.send(data); + let data = JSON.stringify([cmd, arg]) + console.log("SEND %s %s", cmd, arg) + socket.send(data) } -let reconnect_count = 0; -let reconnect_max = 10; +let reconnect_count = 0 +let reconnect_max = 10 function connect_play() { if (reconnect_count >= reconnect_max) { - document.title = "DISCONNECTED"; - document.getElementById("prompt").textContent = "Disconnected."; - return; + document.title = "DISCONNECTED" + document.getElementById("prompt").textContent = "Disconnected." + return } - let protocol = (window.location.protocol === "http:") ? "ws" : "wss"; - let seen = document.getElementById("log").children.length; - let url = `${protocol}://${window.location.host}/play-socket?title=${params.title_id}&game=${params.game_id}&role=${params.role}&seen=${seen}`; + let protocol = (window.location.protocol === "http:") ? "ws" : "wss" + let seen = document.getElementById("log").children.length + let url = `${protocol}://${window.location.host}/play-socket?title=${params.title_id}&game=${params.game_id}&role=${params.role}&seen=${seen}` - console.log("CONNECTING", url); - document.getElementById("prompt").textContent = "Connecting... "; + console.log("CONNECTING", url) + document.getElementById("prompt").textContent = "Connecting... " - socket = new WebSocket(url); + socket = new WebSocket(url) window.addEventListener('beforeunload', function () { - socket.close(1000); - }); + socket.close(1000) + }) socket.onopen = function (evt) { - console.log("OPEN"); - document.querySelector("header").classList.remove("disconnected"); - reconnect_count = 0; + console.log("OPEN") + document.querySelector("header").classList.remove("disconnected") + reconnect_count = 0 } socket.onclose = function (evt) { - console.log("CLOSE %d", evt.code); + console.log("CLOSE %d", evt.code) if (evt.code === 1000 && evt.reason !== "") { - document.getElementById("prompt").textContent = "Disconnected: " + evt.reason; - document.title = "DISCONNECTED"; + document.getElementById("prompt").textContent = "Disconnected: " + evt.reason + document.title = "DISCONNECTED" } if (evt.code !== 1000) { - document.querySelector("header").classList.add("disconnected"); - document.getElementById("prompt").textContent = `Reconnecting soon... (${reconnect_count+1}/${reconnect_max})`; - let wait = 1000 * (Math.random() + 0.5) * Math.pow(2, reconnect_count++); - console.log("WAITING %.1f TO RECONNECT", wait/1000); - setTimeout(connect_play, wait); + document.querySelector("header").classList.add("disconnected") + document.getElementById("prompt").textContent = `Reconnecting soon... (${reconnect_count+1}/${reconnect_max})` + let wait = 1000 * (Math.random() + 0.5) * Math.pow(2, reconnect_count++) + console.log("WAITING %.1f TO RECONNECT", wait/1000) + setTimeout(connect_play, wait) } } socket.onmessage = function (evt) { - let [ cmd, arg ] = JSON.parse(evt.data); - console.log("MESSAGE %s", cmd); + let [ cmd, arg ] = JSON.parse(evt.data) + console.log("MESSAGE %s", cmd) switch (cmd) { case 'error': - document.getElementById("prompt").textContent = arg; - break; + document.getElementById("prompt").textContent = arg + break case 'chat': - update_chat(arg[0], arg[1], arg[2], arg[3]); - break; + update_chat(arg[0], arg[1], arg[2], arg[3]) + break case 'players': - player = arg[0]; - document.querySelector("body").classList.add(player.replace(/ /g, "_")); + player = arg[0] + document.querySelector("body").classList.add(player.replace(/ /g, "_")) if (player !== "Observer") - init_chat(); + init_chat() else - remove_resign_menu(); - init_player_names(arg[1]); - break; + remove_resign_menu() + init_player_names(arg[1]) + break case 'presence': for (let i = 0; i < roles.length; ++i) { - let elt = document.getElementById(roles[i].id); + let elt = document.getElementById(roles[i].id) if (roles[i].role in arg) - elt.classList.add("present"); + elt.classList.add("present") else - elt.classList.remove("present"); + elt.classList.remove("present") } - break; + break case 'state': - view = arg; - on_update_header(); + view = arg + on_update_header() if (typeof on_update === 'function') - on_update(); - on_update_log(); + on_update() + on_update_log() if (view.game_over) - on_game_over(); - break; + on_game_over() + break case 'reply': if (typeof on_reply === 'function') - on_reply(arg[0], arg[1]); - break; + on_reply(arg[0], arg[1]) + break case 'save': - window.localStorage[params.title_id + "/save"] = arg; - break; + window.localStorage[params.title_id + "/save"] = arg + break } } } /* HEADER */ -let is_your_turn = false; -let old_active = null; +let is_your_turn = false +let old_active = null function on_update_header() { - document.getElementById("prompt").textContent = view.prompt; + document.getElementById("prompt").textContent = view.prompt if (params.mode === "replay") - return; + return if (view.actions) { - document.querySelector("header").classList.add("your_turn"); + document.querySelector("header").classList.add("your_turn") if (!is_your_turn || old_active !== view.active) - start_blinker("YOUR TURN"); - is_your_turn = true; + start_blinker("YOUR TURN") + is_your_turn = true } else { - document.querySelector("header").classList.remove("your_turn"); - is_your_turn = false; + document.querySelector("header").classList.remove("your_turn") + is_your_turn = false } - old_active = view.active; + old_active = view.active } /* LOG */ function on_update_log() { - let div = document.getElementById("log"); - let to_delete = div.children.length - view.log_start; + let div = document.getElementById("log") + let to_delete = div.children.length - view.log_start while (to_delete-- > 0) - div.removeChild(div.lastChild); + div.removeChild(div.lastChild) for (let text of view.log) { if (typeof on_log === 'function') { - div.appendChild(on_log(text)); + div.appendChild(on_log(text)) } else { - let entry = document.createElement("div"); - entry.textContent = text; - div.appendChild(entry); + let entry = document.createElement("div") + entry.textContent = text + div.appendChild(entry) } } - scroll_log_to_end(); + scroll_log_to_end() } function scroll_log_to_end() { - let div = document.getElementById("log"); - div.scrollTop = div.scrollHeight; + let div = document.getElementById("log") + div.scrollTop = div.scrollHeight } try { - new ResizeObserver(scroll_log_to_end).observe(document.getElementById("log")); + new ResizeObserver(scroll_log_to_end).observe(document.getElementById("log")) } catch (err) { - window.addEventListener("resize", scroll_log_to_end); + window.addEventListener("resize", scroll_log_to_end) } /* MAP ZOOM */ function toggle_log() { - document.querySelector("aside").classList.toggle("hide"); - zoom_map(); + document.querySelector("aside").classList.toggle("hide") + zoom_map() } function toggle_zoom() { - let mapwrap = document.getElementById("mapwrap"); + let mapwrap = document.getElementById("mapwrap") if (mapwrap) { - mapwrap.classList.toggle("fit"); - zoom_map(); + mapwrap.classList.toggle("fit") + zoom_map() } } function zoom_map() { - let mapwrap = document.getElementById("mapwrap"); + let mapwrap = document.getElementById("mapwrap") if (mapwrap) { - let main = document.querySelector("main"); - let map = document.getElementById("map"); - map.style.transform = null; - mapwrap.style.width = null; - mapwrap.style.height = null; + let main = document.querySelector("main") + let map = document.getElementById("map") + map.style.transform = null + mapwrap.style.width = null + mapwrap.style.height = null if (mapwrap.classList.contains("fit")) { - let { width: gw, height: gh } = main.getBoundingClientRect(); - let { width: ww, height: wh } = mapwrap.getBoundingClientRect(); - let { width: cw, height: ch } = map.getBoundingClientRect(); - let scale = Math.min(ww / cw, gh / ch); + let { width: gw, height: gh } = main.getBoundingClientRect() + let { width: ww, height: wh } = mapwrap.getBoundingClientRect() + let { width: cw, height: ch } = map.getBoundingClientRect() + let scale = Math.min(ww / cw, gh / ch) if (scale < 1) { - map.style.transform = "scale(" + scale + ")"; - mapwrap.style.width = (cw * scale) + "px"; - mapwrap.style.height = (ch * scale) + "px"; + map.style.transform = "scale(" + scale + ")" + mapwrap.style.width = (cw * scale) + "px" + mapwrap.style.height = (ch * scale) + "px" } } } } -window.addEventListener("resize", zoom_map); +window.addEventListener("resize", zoom_map) window.addEventListener("keydown", (evt) => { if (evt.key === "Shift") - document.querySelector("body").classList.add("shift"); -}); + document.querySelector("body").classList.add("shift") +}) window.addEventListener("keyup", (evt) => { if (evt.key === "Shift") - document.querySelector("body").classList.remove("shift"); -}); + document.querySelector("body").classList.remove("shift") +}) /* ACTIONS */ function action_button_imp(action, label, callback) { if (params.mode === "replay") - return; - let id = action + "_button"; - let button = document.getElementById(id); + return + let id = action + "_button" + let button = document.getElementById(id) if (!button) { - button = document.createElement("button"); - button.id = id; - button.textContent = label; - button.addEventListener("click", callback); - document.getElementById("actions").appendChild(button); + button = document.createElement("button") + button.id = id + button.textContent = label + button.addEventListener("click", callback) + document.getElementById("actions").appendChild(button) } if (view.actions && action in view.actions) { - button.classList.remove("hide"); + button.classList.remove("hide") if (view.actions[action]) { if (label === undefined) - button.textContent = view.actions[action]; - button.disabled = false; + button.textContent = view.actions[action] + button.disabled = false } else { - button.disabled = true; + button.disabled = true } } else { - button.classList.add("hide"); + button.classList.add("hide") } } function action_button(action, label) { - action_button_imp(action, label, evt => send_action(action)); + action_button_imp(action, label, evt => send_action(action)) } function confirm_action_button(action, label, message) { - action_button_imp(action, label, evt => confirm_action(message, action)); + action_button_imp(action, label, evt => confirm_action(message, action)) } function send_action(verb, noun) { if (params.mode === "replay") - return; + return // Reset action list here so we don't send more than one action per server prompt! if (noun !== undefined) { - let realnoun = Array.isArray(noun) ? noun[0] : noun; + let realnoun = Array.isArray(noun) ? noun[0] : noun if (view.actions && view.actions[verb] && view.actions[verb].includes(realnoun)) { - view.actions = null; - send_message("action", [verb, noun]); - return true; + view.actions = null + send_message("action", [verb, noun]) + return true } } else { if (view.actions && view.actions[verb]) { - view.actions = null; - send_message("action", [verb]); - return true; + view.actions = null + send_message("action", [verb]) + return true } } - return false; + return false } function confirm_action(message, verb, noun) { if (window.confirm(message)) - send_action(verb, noun); + send_action(verb, noun) } -let replay_query = null; +let replay_query = null function send_query(q, param) { - if (param !== undefined) + if (param !== undefined) { if (replay_query) - replay_query(q, param); + replay_query(q, param) else - send_message("query", [q, param]); - else + send_message("query", [q, param]) + } else { if (replay_query) - replay_query(q, undefined); + replay_query(q, undefined) else - send_message("query", q); + send_message("query", q) + } } function confirm_resign() { if (window.confirm("Are you sure that you want to resign?")) - send_message("resign"); + send_message("resign") } /* MOBILE PHONE LAYOUT */ -let mobile_scroll_header = document.querySelector("header"); -let mobile_scroll_last_y = 0; +let mobile_scroll_header = document.querySelector("header") +let mobile_scroll_last_y = 0 window.addEventListener("scroll", function scroll_mobile_fix (evt) { if (mobile_scroll_header.clientWidth <= 640) { if (window.scrollY > 40) { if (mobile_scroll_last_y <= 40) - mobile_scroll_header.classList.add("mobilefix"); + mobile_scroll_header.classList.add("mobilefix") } else { if (mobile_scroll_last_y > 40) - mobile_scroll_header.classList.remove("mobilefix"); + mobile_scroll_header.classList.remove("mobilefix") } - mobile_scroll_last_y = window.scrollY; + mobile_scroll_last_y = window.scrollY } -}); +}) /* DEBUGGING */ function send_save() { - send_message("save"); + send_message("save") } function send_restore() { - send_message("restore", window.localStorage[params.title_id + "/save"]); + send_message("restore", window.localStorage[params.title_id + "/save"]) } function send_restart(scenario) { - send_message("restart", scenario); + send_message("restart", scenario) } /* REPLAY */ function adler32(data) { - let a = 1, b = 0; + let a = 1, b = 0 for (let i = 0, n = data.length; i < n; ++i) { - a = (a + data.charCodeAt(i)) % 65521; - b = (b + a) % 65521; + a = (a + data.charCodeAt(i)) % 65521 + b = (b + a) % 65521 } - return (b << 16) | a; + return (b << 16) | a } async function require(path) { - let cache = {}; + let cache = {} if (!path.endsWith(".js")) - path = path + ".js"; + path = path + ".js" if (path.startsWith("./")) - path = path.substring(2); + path = path.substring(2) - console.log("REQUIRE", path); + console.log("REQUIRE", path) - let response = await fetch(path); - let source = await response.text(); + let response = await fetch(path) + let source = await response.text() for (let [_, subpath] of source.matchAll(/require\(['"]([^)]*)['"]\)/g)) if (cache[subpath] === undefined) - cache[subpath] = await require(subpath); + cache[subpath] = await require(subpath) - let module = { exports: {} }; - Function("module", "exports", "require", source) - (module, module.exports, path => cache[path]); - return module.exports; + let module = { exports: {} } + Function("module", "exports", "require", source)(module, module.exports, path => cache[path]) + return module.exports } -let replay = null; +let replay = null async function init_replay() { - remove_resign_menu(); + remove_resign_menu() - document.getElementById("prompt").textContent = "Loading replay..."; + document.getElementById("prompt").textContent = "Loading replay..." - console.log("LOADING RULES"); - let rules = await require("rules.js"); + console.log("LOADING RULES") + let rules = await require("rules.js") - console.log("LOADING REPLAY"); - let response = await fetch("/replay/" + params.game_id); - let body = await response.json(); - replay = body.replay; + console.log("LOADING REPLAY") + let response = await fetch("/replay/" + params.game_id) + let body = await response.json() + replay = body.replay - init_player_names(body.players); + init_player_names(body.players) - let viewpoint = "Observer"; - let log_length = 0; - let p = 0; - let s = {}; + let viewpoint = "Observer" + let log_length = 0 + let p = 0 + let s = {} function eval_action(item) { switch (item.action) { case "setup": - s = rules.setup(item.arguments[0], item.arguments[1], item.arguments[2]); - break; + s = rules.setup(item.arguments[0], item.arguments[1], item.arguments[2]) + break case "resign": - s = rules.resign(s, item.role); - break; + s = rules.resign(s, item.role) + break default: - s = rules.action(s, item.role, item.action, item.arguments); - break; + s = rules.action(s, item.role, item.action, item.arguments) + break } } replay_query = function (query, params) { - let reply = rules.query(s, player, query, params); - on_reply(query, reply); + let reply = rules.query(s, player, query, params) + on_reply(query, reply) } - let ss; + let ss for (p = 0; p < replay.length; ++p) { - replay[p].arguments = JSON.parse(replay[p].arguments); + replay[p].arguments = JSON.parse(replay[p].arguments) if (rules.is_checkpoint) { - replay[p].is_checkpoint = (p > 0 && rules.is_checkpoint(ss, s)); - ss = Object.assign({}, s); + replay[p].is_checkpoint = (p > 0 && rules.is_checkpoint(ss, s)) + ss = Object.assign({}, s) } try { - eval_action(replay[p]); + eval_action(replay[p]) } catch (err) { - console.log("ERROR IN REPLAY %d %s %s/%s/%s", p, s.state, replay[p].role, replay[p].action, replay[p].arguments); - console.log(err); - replay.length = 0; - break; + console.log("ERROR IN REPLAY %d %s %s/%s/%s", p, s.state, replay[p].role, replay[p].action, replay[p].arguments) + console.log(err) + replay.length = 0 + break } - replay[p].digest = adler32(JSON.stringify(s)); + replay[p].digest = adler32(JSON.stringify(s)) for (let k = p-1; k > 0; --k) { if (replay[k].digest === replay[p].digest && !replay[k].is_undone) { for (let a = k+1; a <= p; ++a) if (!replay[a].is_undone) - replay[a].is_undone = true; - break; + replay[a].is_undone = true + break } } } - replay = replay.filter(x => !x.is_undone); + replay = replay.filter(x => !x.is_undone) function set_hash(n) { - history.replaceState(null, "", window.location.pathname + "#" + n); + history.replaceState(null, "", window.location.pathname + "#" + n) } - let timer = 0; + let timer = 0 function play_pause_replay(evt) { if (timer === 0) { - evt.target.textContent = "Stop"; + evt.target.textContent = "Stop" timer = setInterval(() => { if (p < replay.length) - goto_replay(p+1); + goto_replay(p+1) else - play_pause_replay(evt); - }, 1000); + play_pause_replay(evt) + }, 1000) } else { - evt.target.textContent = "Run"; - clearInterval(timer); - timer = 0; + evt.target.textContent = "Run" + clearInterval(timer) + timer = 0 } } function prev() { for (let i = p - 1; i > 1; --i) if (replay[i].is_checkpoint) - return i; - return 1; + return i + return 1 } function next() { for (let i = p + 1; i < replay.length; ++i) if (replay[i].is_checkpoint) - return i; - return replay.length; + return i + return replay.length } function on_hash_change() { - goto_replay(parseInt(window.location.hash.slice(1)) || 1); + goto_replay(parseInt(window.location.hash.slice(1)) || 1) } function goto_replay(np) { if (np < 1) - np = 1; + np = 1 if (np > replay.length) - np = replay.length; - set_hash(np); + np = replay.length + set_hash(np) if (p > np) - p = 0, s = {}; + p = 0, s = {} while (p < np) - eval_action(replay[p++]); - update_replay_view(); + eval_action(replay[p++]) + update_replay_view() } function update_replay_view() { - player = viewpoint; + player = viewpoint if (viewpoint === "Active") { - player = s.active; + player = s.active if (player === "All" || player === "Both" || player === "None" || !player) - player = "Observer"; + player = "Observer" } - let body = document.querySelector("body"); - body.classList.remove("Observer"); + let body = document.querySelector("body") + body.classList.remove("Observer") for (let i = 0; i < roles.length; ++i) - body.classList.remove(roles[i].role.replace(/ /g, "_")); - body.classList.add(player.replace(/ /g, "_")); + body.classList.remove(roles[i].role.replace(/ /g, "_")) + body.classList.add(player.replace(/ /g, "_")) - view = rules.view(s, player); - view.actions = null; + view = rules.view(s, player) + view.actions = null if (viewpoint === "Observer") - view.game_over = 1; + view.game_over = 1 if (s.state === "game_over") - view.game_over = 1; + view.game_over = 1 if (replay.length > 0) { if (document.querySelector("body").classList.contains("shift")) { - view.prompt = `[${p}/${replay.length}] ${s.active} / ${s.state}`; + view.prompt = `[${p}/${replay.length}] ${s.active} / ${s.state}` if (p < replay.length) - view.prompt += ` / ${replay[p].action} ${replay[p].arguments}`; + view.prompt += ` / ${replay[p].action} ${replay[p].arguments}` } else { - view.prompt = "[" + p + "/" + replay.length + "] " + view.prompt; + view.prompt = "[" + p + "/" + replay.length + "] " + view.prompt } } if (log_length < view.log.length) - view.log_start = log_length; + view.log_start = log_length else - view.log_start = view.log.length; - log_length = view.log.length; - view.log = view.log.slice(view.log_start); + view.log_start = view.log.length + log_length = view.log.length + view.log = view.log.slice(view.log_start) - on_update_header(); - on_update(); - on_update_log(); + on_update_header() + on_update() + on_update_log() } function text_button(div, txt, fn) { - let button = document.createElement("button"); - button.addEventListener("click", fn); - button.textContent = txt; - div.appendChild(button); - return button; + let button = document.createElement("button") + button.addEventListener("click", fn) + button.textContent = txt + div.appendChild(button) + return button } function set_viewpoint(vp) { - viewpoint = vp; - update_replay_view(); + viewpoint = vp + update_replay_view() } - let div = document.createElement("div"); - div.className = "replay"; + let div = document.createElement("div") + div.className = "replay" if (replay.length > 0) - text_button(div, "Active", () => set_viewpoint("Active")); + text_button(div, "Active", () => set_viewpoint("Active")) for (let r of roles) - text_button(div, r.role, () => set_viewpoint(r.role)); - text_button(div, "Observer", () => set_viewpoint("Observer")); - document.querySelector("header").appendChild(div); + text_button(div, r.role, () => set_viewpoint(r.role)) + text_button(div, "Observer", () => set_viewpoint("Observer")) + document.querySelector("header").appendChild(div) if (replay.length > 0) { - console.log("REPLAY READY"); - - div = document.createElement("div"); - div.className = "replay"; - text_button(div, "<<<", () => goto_replay(1)); - text_button(div, "<<", () => goto_replay(prev())); - text_button(div, "<\xa0", () => goto_replay(p-1)); - text_button(div, "\xa0>", () => goto_replay(p+1)); - text_button(div, ">>", () => goto_replay(next())); - text_button(div, "Run", play_pause_replay).style.width = "65px"; - document.querySelector("header").appendChild(div); + console.log("REPLAY READY") + + div = document.createElement("div") + div.className = "replay" + text_button(div, "<<<", () => goto_replay(1)) + text_button(div, "<<", () => goto_replay(prev())) + text_button(div, "<\xa0", () => goto_replay(p-1)) + text_button(div, "\xa0>", () => goto_replay(p+1)) + text_button(div, ">>", () => goto_replay(next())) + text_button(div, "Run", play_pause_replay).style.width = "65px" + document.querySelector("header").appendChild(div) if (window.location.hash === "") - set_hash(replay.length); + set_hash(replay.length) - on_hash_change(); + on_hash_change() - window.addEventListener("hashchange", on_hash_change); + window.addEventListener("hashchange", on_hash_change) } else { - console.log("REPLAY NOT AVAILABLE"); - s = JSON.parse(body.state); - update_replay_view(); + console.log("REPLAY NOT AVAILABLE") + s = JSON.parse(body.state) + update_replay_view() } } window.addEventListener("load", function () { - zoom_map(); + zoom_map() if (params.mode === "replay") - init_replay(); + init_replay() if (params.mode === "play") - connect_play(); -}); + connect_play() +}) function init_home_menu(link, text) { - let popup = document.querySelector(".menu_popup"); - let sep = document.createElement("div"); - sep.className = "menu_separator"; - popup.insertBefore(sep, popup.firstChild); - let item = document.createElement("div"); - item.className = "menu_item"; - item.onclick = () => window.open(link, "_self"); - item.textContent = text; - popup.insertBefore(item, popup.firstChild); -} - -init_home_menu("/games/active", "Go Home"); + let popup = document.querySelector(".menu_popup") + let sep = document.createElement("div") + sep.className = "menu_separator" + popup.insertBefore(sep, popup.firstChild) + let item = document.createElement("div") + item.className = "menu_item" + item.onclick = () => window.open(link, "_self") + item.textContent = text + popup.insertBefore(item, popup.firstChild) +} + +init_home_menu("/games/active", "Go Home") diff --git a/public/sort.js b/public/sort.js index b98334d..ed243c2 100644 --- a/public/sort.js +++ b/public/sort.js @@ -1,67 +1,82 @@ -"use strict"; +"use strict" function sort_table_column(table, column) { - const minute = 60000; - const hour = 60 * minute; - const day = 24 * hour; - const week = 7 * day; + const minute = 60000 + const hour = 60 * minute + const day = 24 * hour + const week = 7 * day function is_date(s) { if (s.match(/^\d{4}-\d{2}-\d{2}$/)) - return true; + return true if (s.match(/^\d+ (minutes?|hours?|days|weeks) ago$/)) - return true; + return true if (s.match(/^(Yesterday|now)$/)) - return true; - return false; + return true + return false } function parse_date(s) { - if (s.match(/^\d{4}-\d{2}-\d{2}$/)) return new Date(s).valueOf(); - if (s === 'now') return Date.now(); - if (s === 'Yesterday') return Date.now() - day; - let [ _, value, unit ] = s.match(/^(\d+) (minutes?|hours?|days|weeks) ago$/); + if (s.match(/^\d{4}-\d{2}-\d{2}$/)) + return new Date(s).valueOf() + if (s === "now") + return Date.now() + if (s === "Yesterday") + return Date.now() - day + let [ _, value, unit ] = s.match(/^(\d+) (minutes?|hours?|days|weeks) ago$/) switch (unit) { - default: unit = 0; break; - case 'minute': case 'minutes': unit = minute; break; - case 'hours': case 'hours': unit = hour; break; - case 'days': unit = day; break; - case 'weeks': unit = week; break; + default: + unit = 0 + break + case "minute": + case "minutes": + unit = minute + break + case "hours": + case "hours": + unit = hour + break + case "days": + unit = day + break + case "weeks": + unit = week + break } - return Date.now() - Number(value) * unit; + return Date.now() - Number(value) * unit } - let tbody = table.querySelector("tbody"); - let rows = Array.from(tbody.querySelectorAll("tr")); + let tbody = table.querySelector("tbody") + let rows = Array.from(tbody.querySelectorAll("tr")) rows.sort((row_a, row_b) => { - let cell_a = row_a.querySelectorAll("td")[column].textContent; - let cell_b = row_b.querySelectorAll("td")[column].textContent; + let cell_a = row_a.querySelectorAll("td")[column].textContent + let cell_b = row_b.querySelectorAll("td")[column].textContent if (is_date(cell_a) && is_date(cell_b)) { - let age_a = parse_date(cell_a); - let age_b = parse_date(cell_b); - if (age_a > age_b) return -1; - if (age_a < age_b) return 1; - return 0; + let age_a = parse_date(cell_a) + let age_b = parse_date(cell_b) + if (age_a > age_b) return -1 + if (age_a < age_b) return 1 + return 0 } else if (cell_a.match(/^\d+$/) && cell_b.match(/^\d+$/)) { - cell_a = Number(cell_a); - cell_b = Number(cell_b); - if (cell_a > cell_b) return -1; - if (cell_a < cell_b) return 1; - return 0; + cell_a = Number(cell_a) + cell_b = Number(cell_b) + if (cell_a > cell_b) return -1 + if (cell_a < cell_b) return 1 + return 0 } else { - if (cell_a > cell_b) return 1; - if (cell_a < cell_b) return -1; - return 0; + if (cell_a > cell_b) return 1 + if (cell_a < cell_b) return -1 + return 0 } - }); - rows.forEach(row => tbody.appendChild(row)); + }) + rows.forEach((row) => tbody.appendChild(row)) } -document.querySelectorAll("table.sort").forEach(table => { +document.querySelectorAll("table.sort").forEach((table) => { table.querySelectorAll("th").forEach((th, column) => { if (th.textContent !== "") { - th.addEventListener("click", evt => sort_table_column(table, column)); - th.style.cursor = "pointer"; + th.addEventListener("click", (evt) => sort_table_column(table, column)) + th.style.cursor = "pointer" } - }); -}); + }) +}) @@ -1,62 +1,62 @@ -"use strict"; +"use strict" -const fs = require('fs'); -const crypto = require('crypto'); -const http = require('http'); -const https = require('https'); -const { WebSocketServer } = require('ws'); -const express = require('express'); -const url = require('url'); -const compression = require('compression'); -const sqlite3 = require('better-sqlite3'); +const fs = require('fs') +const crypto = require('crypto') +const http = require('http') +const https = require('https') +const { WebSocketServer } = require('ws') +const express = require('express') +const url = require('url') +const compression = require('compression') +const sqlite3 = require('better-sqlite3') -require('dotenv').config(); +require('dotenv').config() -let DEBUG = process.env.DEBUG || 0; +let DEBUG = process.env.DEBUG || 0 -let HTTP_PORT = process.env.HTTP_PORT || 8080; -let HTTPS_PORT = process.env.HTTPS_PORT; +let HTTP_PORT = process.env.HTTP_PORT || 8080 +let HTTPS_PORT = process.env.HTTPS_PORT -let SITE_HOST = process.env.SITE_HOST || "localhost"; -let SITE_NAME = process.env.SITE_NAME || "Untitled"; -let SITE_URL = process.env.SITE_URL; +let SITE_HOST = process.env.SITE_HOST || "localhost" +let SITE_NAME = process.env.SITE_NAME || "Untitled" +let SITE_URL = process.env.SITE_URL if (!SITE_URL) { if (HTTPS_PORT) - SITE_URL = "https://" + SITE_HOST + ":" + HTTPS_PORT; + SITE_URL = "https://" + SITE_HOST + ":" + HTTPS_PORT else - SITE_URL = "http://" + SITE_HOST + ":" + HTTP_PORT; + SITE_URL = "http://" + SITE_HOST + ":" + HTTP_PORT } /* * Main database. */ -let db = new sqlite3(process.env.DATABASE || "./db"); -db.pragma("journal_mode = WAL"); -db.pragma("synchronous = NORMAL"); -db.pragma("foreign_keys = ON"); +let db = new sqlite3(process.env.DATABASE || "./db") +db.pragma("journal_mode = WAL") +db.pragma("synchronous = NORMAL") +db.pragma("foreign_keys = ON") function SQL(s) { - return db.prepare(s); + return db.prepare(s) } /* * Notification mail setup. */ -let mailer = null; +let mailer = null if (process.env.MAIL_HOST && process.env.MAIL_PORT && process.env.MAIL_FROM) { mailer = require("nodemailer").createTransport({ host: process.env.MAIL_HOST, port: process.env.MAIL_PORT, ignoreTLS: true - }); - console.log("Mail notifications enabled: ", mailer.options); + }) + console.log("Mail notifications enabled: ", mailer.options) } else { - console.log("Mail notifications disabled."); + console.log("Mail notifications disabled.") mailer = { sendMail(obj, callback) { - callback("DID NOT SEND: " + JSON.stringify(obj,0,4)); + callback("DID NOT SEND: " + JSON.stringify(obj,0,4)) } } } @@ -65,90 +65,90 @@ if (process.env.MAIL_HOST && process.env.MAIL_PORT && process.env.MAIL_FROM) { * Login session management. */ -const COOKIE = (process.env.COOKIE || "login") + "="; +const COOKIE = (process.env.COOKIE || "login") + "=" -db.exec("delete from logins where expires < julianday()"); -const login_sql_select = SQL("select user_id from logins where sid = ? and expires > julianday()").pluck(); -const login_sql_insert = SQL("insert into logins values (abs(random()) % (1<<48), ?, julianday() + 28) returning sid").pluck(); -const login_sql_delete = SQL("delete from logins where sid = ?"); -const login_sql_touch = SQL("update logins set expires = julianday() + 28 where sid = ? and expires < julianday() + 27"); +db.exec("delete from logins where expires < julianday()") +const login_sql_select = SQL("select user_id from logins where sid = ? and expires > julianday()").pluck() +const login_sql_insert = SQL("insert into logins values (abs(random()) % (1<<48), ?, julianday() + 28) returning sid").pluck() +const login_sql_delete = SQL("delete from logins where sid = ?") +const login_sql_touch = SQL("update logins set expires = julianday() + 28 where sid = ? and expires < julianday() + 27") function make_cookie(sid, age) { if (SITE_HOST !== "localhost") - return `${COOKIE}${sid}; Path=/; Domain=${SITE_HOST}; Max-Age=${age}; HttpOnly`; - return `${COOKIE}${sid}; Path=/; Max-Age=${age}; HttpOnly`; + return `${COOKIE}${sid}; Path=/; Domain=${SITE_HOST}; Max-Age=${age}; HttpOnly` + return `${COOKIE}${sid}; Path=/; Max-Age=${age}; HttpOnly` } function login_cookie(req) { - let c = req.headers.cookie; + let c = req.headers.cookie if (c) { - let i = c.indexOf(COOKIE); + let i = c.indexOf(COOKIE) if (i >= 0) - return parseInt(c.substring(i+COOKIE.length)); + return parseInt(c.substring(i+COOKIE.length)) } - return 0; + return 0 } function login_insert(res, user_id) { - let sid = login_sql_insert.get(user_id); - res.setHeader("Set-Cookie", make_cookie(sid, 2419200)); + let sid = login_sql_insert.get(user_id) + res.setHeader("Set-Cookie", make_cookie(sid, 2419200)) } function login_touch(res, sid) { if (login_sql_touch.run(sid).changes === 1) - res.setHeader("Set-Cookie", make_cookie(sid, 2419200)); + res.setHeader("Set-Cookie", make_cookie(sid, 2419200)) } function login_delete(res, sid) { - login_sql_delete.run(sid); - res.setHeader("Set-Cookie", make_cookie("", 0)); + login_sql_delete.run(sid) + res.setHeader("Set-Cookie", make_cookie("", 0)) } /* * Web server setup. */ -express.static.mime.define({ "image/avif": ["avif"] }); +express.static.mime.define({ "image/avif": ["avif"] }) function set_static_headers(res, path) { if (path.match(/\.(jpg|png|svg|avif|webp|ico|woff2)$/)) - res.setHeader("Cache-Control", "max-age=86400"); + res.setHeader("Cache-Control", "max-age=86400") else - res.setHeader("Cache-Control", "max-age=60"); + res.setHeader("Cache-Control", "max-age=60") } -let app = express(); -app.locals.SITE_NAME = SITE_NAME; -app.locals.SITE_URL = SITE_URL; -app.set('x-powered-by', false); -app.set('etag', false); -app.set('view engine', 'pug'); +let app = express() +app.locals.SITE_NAME = SITE_NAME +app.locals.SITE_URL = SITE_URL +app.set('x-powered-by', false) +app.set('etag', false) +app.set('view engine', 'pug') -app.use(compression()); -app.use(express.static('public', { redirect: false, etag: false, cacheControl: false, setHeaders: set_static_headers })); -app.use(express.urlencoded({extended:false})); +app.use(compression()) +app.use(express.static('public', { redirect: false, etag: false, cacheControl: false, setHeaders: set_static_headers })) +app.use(express.urlencoded({extended:false})) -let wss; +let wss if (HTTPS_PORT) { let https_server = https.createServer({ key: fs.readFileSync(process.env.SSL_KEY || "key.pem"), cert: fs.readFileSync(process.env.SSL_CERT || "cert.pem") - }, app); - wss = new WebSocketServer({server: https_server}); - https_server.listen(HTTPS_PORT, "0.0.0.0", () => console.log("Listening to HTTPS on *:" + HTTPS_PORT)); - https_server.keepAliveTimeout = 0; + }, app) + wss = new WebSocketServer({server: https_server}) + https_server.listen(HTTPS_PORT, "0.0.0.0", () => console.log("Listening to HTTPS on *:" + HTTPS_PORT)) + https_server.keepAliveTimeout = 0 // Force HTTPS by redirecting HTTP. - let redirect_app = express(); - redirect_app.all("*", (req, res) => res.redirect(308, SITE_URL + req.url)); - let redirect_server = http.createServer(redirect_app); - redirect_server.listen(HTTP_PORT, "0.0.0.0", () => console.log("Redirecting from HTTP on *:" + HTTP_PORT)); + let redirect_app = express() + redirect_app.all("*", (req, res) => res.redirect(308, SITE_URL + req.url)) + let redirect_server = http.createServer(redirect_app) + redirect_server.listen(HTTP_PORT, "0.0.0.0", () => console.log("Redirecting from HTTP on *:" + HTTP_PORT)) } else { - let http_server = http.createServer(app); - wss = new WebSocketServer({server: http_server}); - http_server.keepAliveTimeout = 0; - http_server.listen(HTTP_PORT, "0.0.0.0", () => console.log("Listening to HTTP on *:" + HTTP_PORT)); + let http_server = http.createServer(app) + wss = new WebSocketServer({server: http_server}) + http_server.keepAliveTimeout = 0 + http_server.listen(HTTP_PORT, "0.0.0.0", () => console.log("Listening to HTTP on *:" + HTTP_PORT)) } /* @@ -156,89 +156,89 @@ if (HTTPS_PORT) { */ function random_seed() { - return crypto.randomInt(1, 0x7ffffffe); + return crypto.randomInt(1, 0x7ffffffe) } function SLOG(socket, ...msg) { - let time = new Date().toISOString().substring(11,19); - let name = (socket.user ? socket.user.name : "guest").padEnd(20); - let ip = String(socket.ip).padEnd(15); - let ws = "----------"; + let time = new Date().toISOString().substring(11,19) + let name = (socket.user ? socket.user.name : "guest").padEnd(20) + let ip = String(socket.ip).padEnd(15) + let ws = "----------" console.log(time, ip, ws, name, "WS", socket.title_id, socket.game_id, socket.role, - ...msg); + ...msg) } function human_date(time) { - var date = time ? new Date(time + " UTC") : new Date(0); - var seconds = (Date.now() - date.getTime()) / 1000; - var days = Math.floor(seconds / 86400); + var date = time ? new Date(time + " UTC") : new Date(0) + var seconds = (Date.now() - date.getTime()) / 1000 + var days = Math.floor(seconds / 86400) if (days === 0) { - if (seconds < 60) return "Now"; - if (seconds < 120) return "1 minute ago"; - if (seconds < 3600) return Math.floor(seconds / 60) + " minutes ago"; - if (seconds < 7200) return "1 hour ago"; - if (seconds < 86400) return Math.floor(seconds / 3600) + " hours ago"; + if (seconds < 60) return "Now" + if (seconds < 120) return "1 minute ago" + if (seconds < 3600) return Math.floor(seconds / 60) + " minutes ago" + if (seconds < 7200) return "1 hour ago" + if (seconds < 86400) return Math.floor(seconds / 3600) + " hours ago" } - if (days === 1) return "Yesterday"; - if (days < 14) return days + " days ago"; - if (days < 31) return Math.floor(days / 7) + " weeks ago"; - return date.toISOString().substring(0,10); + if (days === 1) return "Yesterday" + if (days < 14) return days + " days ago" + if (days < 31) return Math.floor(days / 7) + " weeks ago" + return date.toISOString().substring(0,10) } function is_email(email) { - return email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/); + return email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/) } function clean_user_name(name) { - name = name.replace(/^ */,'').replace(/ *$/,'').replace(/ */g,' '); + name = name.replace(/^ */,'').replace(/ *$/,'').replace(/ */g,' ') if (name.length > 50) - name = name.substring(0, 50); - return name; + name = name.substring(0, 50) + return name } -const USER_NAME_RE = /^[\p{Alpha}\p{Number}'_-]+( [\p{Alpha}\p{Number}'_-]+)*$/u; +const USER_NAME_RE = /^[\p{Alpha}\p{Number}'_-]+( [\p{Alpha}\p{Number}'_-]+)*$/u function is_valid_user_name(name) { if (name.length < 2) - return false; + return false if (name.length > 50) - return false; - return USER_NAME_RE.test(name); + return false + return USER_NAME_RE.test(name) } function hash_password(password, salt) { - let hash = crypto.createHash('sha256'); - hash.update(password); - hash.update(salt); - return hash.digest('hex'); + let hash = crypto.createHash('sha256') + hash.update(password) + hash.update(salt) + return hash.digest('hex') } function get_avatar(mail) { if (!mail) - mail = "foo@example.com"; - let digest = crypto.createHash('md5').update(mail.trim().toLowerCase()).digest('hex'); - return '//www.gravatar.com/avatar/' + digest + '?d=mp'; + mail = "foo@example.com" + let digest = crypto.createHash('md5').update(mail.trim().toLowerCase()).digest('hex') + return '//www.gravatar.com/avatar/' + digest + '?d=mp' } /* * USER AUTHENTICATION */ -const SQL_BLACKLIST_MAIL = SQL("SELECT EXISTS ( SELECT 1 FROM blacklist_mail WHERE ? LIKE mail )").pluck(); +const SQL_BLACKLIST_MAIL = SQL("SELECT EXISTS ( SELECT 1 FROM blacklist_mail WHERE ? LIKE mail )").pluck() -const SQL_EXISTS_USER_NAME = SQL("SELECT EXISTS ( SELECT 1 FROM users WHERE name=? )").pluck(); -const SQL_EXISTS_USER_MAIL = SQL("SELECT EXISTS ( SELECT 1 FROM users WHERE mail=? )").pluck(); +const SQL_EXISTS_USER_NAME = SQL("SELECT EXISTS ( SELECT 1 FROM users WHERE name=? )").pluck() +const SQL_EXISTS_USER_MAIL = SQL("SELECT EXISTS ( SELECT 1 FROM users WHERE mail=? )").pluck() -const SQL_INSERT_USER = SQL("INSERT INTO users (name,mail,password,salt) VALUES (?,?,?,?) RETURNING user_id,name,mail,notify"); +const SQL_INSERT_USER = SQL("INSERT INTO users (name,mail,password,salt) VALUES (?,?,?,?) RETURNING user_id,name,mail,notify") -const SQL_SELECT_USER_BY_NAME = SQL("SELECT * FROM user_view WHERE name=?"); -const SQL_SELECT_LOGIN_BY_MAIL = SQL("SELECT * FROM user_login_view WHERE mail=?"); -const SQL_SELECT_LOGIN_BY_NAME = SQL("SELECT * FROM user_login_view WHERE name=?"); -const SQL_SELECT_USER_PROFILE = SQL("SELECT * FROM user_profile_view WHERE name=?"); -const SQL_SELECT_USER_NAME = SQL("SELECT name FROM users WHERE user_id=?").pluck(); +const SQL_SELECT_USER_BY_NAME = SQL("SELECT * FROM user_view WHERE name=?") +const SQL_SELECT_LOGIN_BY_MAIL = SQL("SELECT * FROM user_login_view WHERE mail=?") +const SQL_SELECT_LOGIN_BY_NAME = SQL("SELECT * FROM user_login_view WHERE name=?") +const SQL_SELECT_USER_PROFILE = SQL("SELECT * FROM user_profile_view WHERE name=?") +const SQL_SELECT_USER_NAME = SQL("SELECT name FROM users WHERE user_id=?").pluck() const SQL_SELECT_USER_INFO = SQL(` select user_id, @@ -269,329 +269,329 @@ const SQL_SELECT_USER_INFO = SQL(` from users where user_id = ? - `); + `) -const SQL_OFFLINE_USER = SQL("SELECT * FROM user_view NATURAL JOIN user_last_seen WHERE user_id=? AND datetime('now') > datetime(atime,?)"); +const SQL_OFFLINE_USER = SQL("SELECT * FROM user_view NATURAL JOIN user_last_seen WHERE user_id=? AND datetime('now') > datetime(atime,?)") -const SQL_SELECT_USER_NOTIFY = SQL("SELECT notify FROM users WHERE user_id=?").pluck(); -const SQL_UPDATE_USER_NOTIFY = SQL("UPDATE users SET notify=? WHERE user_id=?"); -const SQL_UPDATE_USER_NAME = SQL("UPDATE users SET name=? WHERE user_id=?"); -const SQL_UPDATE_USER_MAIL = SQL("UPDATE users SET mail=? WHERE user_id=?"); -const SQL_UPDATE_USER_ABOUT = SQL("UPDATE users SET about=? WHERE user_id=?"); -const SQL_UPDATE_USER_PASSWORD = SQL("UPDATE users SET password=?, salt=? WHERE user_id=?"); -const SQL_UPDATE_USER_LAST_SEEN = SQL("INSERT OR REPLACE INTO user_last_seen (user_id,atime) VALUES (?,datetime('now'))"); +const SQL_SELECT_USER_NOTIFY = SQL("SELECT notify FROM users WHERE user_id=?").pluck() +const SQL_UPDATE_USER_NOTIFY = SQL("UPDATE users SET notify=? WHERE user_id=?") +const SQL_UPDATE_USER_NAME = SQL("UPDATE users SET name=? WHERE user_id=?") +const SQL_UPDATE_USER_MAIL = SQL("UPDATE users SET mail=? WHERE user_id=?") +const SQL_UPDATE_USER_ABOUT = SQL("UPDATE users SET about=? WHERE user_id=?") +const SQL_UPDATE_USER_PASSWORD = SQL("UPDATE users SET password=?, salt=? WHERE user_id=?") +const SQL_UPDATE_USER_LAST_SEEN = SQL("INSERT OR REPLACE INTO user_last_seen (user_id,atime) VALUES (?,datetime('now'))") -const SQL_FIND_TOKEN = SQL("SELECT token FROM tokens WHERE user_id=? AND datetime('now') < datetime(time, '+5 minutes')").pluck(); -const SQL_CREATE_TOKEN = SQL("INSERT OR REPLACE INTO tokens (user_id,token,time) VALUES (?, lower(hex(randomblob(16))), datetime('now')) RETURNING token").pluck(); -const SQL_VERIFY_TOKEN = SQL("SELECT EXISTS ( SELECT 1 FROM tokens WHERE user_id=? AND datetime('now') < datetime(time, '+20 minutes') AND token=? )").pluck(); +const SQL_FIND_TOKEN = SQL("SELECT token FROM tokens WHERE user_id=? AND datetime('now') < datetime(time, '+5 minutes')").pluck() +const SQL_CREATE_TOKEN = SQL("INSERT OR REPLACE INTO tokens (user_id,token,time) VALUES (?, lower(hex(randomblob(16))), datetime('now')) RETURNING token").pluck() +const SQL_VERIFY_TOKEN = SQL("SELECT EXISTS ( SELECT 1 FROM tokens WHERE user_id=? AND datetime('now') < datetime(time, '+20 minutes') AND token=? )").pluck() function is_blacklisted(mail) { if (SQL_BLACKLIST_MAIL.get(mail) === 1) - return true; - return false; + return true + return false } function parse_user_agent(req) { - let user_agent = req.headers["user-agent"]; + let user_agent = req.headers["user-agent"] if (!user_agent) - return "Browser"; - let agent = user_agent; + return "Browser" + let agent = user_agent if (user_agent.indexOf("Firefox/") >= 0) - agent = "Firefox"; + agent = "Firefox" else if (user_agent.indexOf("Chrome/") >= 0) - agent = "Chrome"; + agent = "Chrome" else if (user_agent.indexOf("Safari/") >= 0) - agent = "Safari"; + agent = "Safari" else if (user_agent.indexOf("Edg/") >= 0) - agent = "Edge"; + agent = "Edge" else if (user_agent.indexOf("OPR/") >= 0) - agent = "Opera"; + agent = "Opera" else if (user_agent.indexOf("Opera") >= 0) - agent = "Opera"; + agent = "Opera" else if (user_agent.indexOf("Googlebot") >= 0) - agent = "Googlebot"; + agent = "Googlebot" else if (user_agent.indexOf("bingbot") >= 0) - agent = "Bingbot"; + agent = "Bingbot" else if (user_agent.indexOf("; MSIE") >= 0) - agent = "MSIE"; + agent = "MSIE" else if (user_agent.indexOf("Trident/") >= 0) - agent = "MSIE"; + agent = "MSIE" else if (user_agent.indexOf("AppleWebKit/") >= 0) - agent = "AppleWebKit"; + agent = "AppleWebKit" if (user_agent.indexOf("Mobile") >= 0) - return agent + "/M"; - return agent; + return agent + "/M" + return agent } app.use(function (req, res, next) { - req.user_agent = parse_user_agent(req); + req.user_agent = parse_user_agent(req) if (req.user_agent === "MSIE") - return res.redirect("/msie.html"); - let ip = req.ip || req.connection.remoteAddress || "0.0.0.0"; - res.setHeader('Cache-Control', 'no-store'); - let sid = login_cookie(req); + return res.redirect("/msie.html") + let ip = req.ip || req.connection.remoteAddress || "0.0.0.0" + res.setHeader('Cache-Control', 'no-store') + let sid = login_cookie(req) if (sid) { - let user_id = login_sql_select.get(sid); + let user_id = login_sql_select.get(sid) if (user_id) { - login_touch(res, sid); - req.user = SQL_SELECT_USER_INFO.get(user_id); - SQL_UPDATE_USER_LAST_SEEN.run(user_id); + login_touch(res, sid) + req.user = SQL_SELECT_USER_INFO.get(user_id) + SQL_UPDATE_USER_LAST_SEEN.run(user_id) } } // Log non-static accesses. - let time = new Date().toISOString().substring(11,19); - let name = (req.user ? req.user.name : "guest").padEnd(20); - let ua = req.user_agent.padEnd(10); - ip = String(ip).padEnd(15); - console.log(time, ip, ua, name, req.method, req.url); + let time = new Date().toISOString().substring(11,19) + let name = (req.user ? req.user.name : "guest").padEnd(20) + let ua = req.user_agent.padEnd(10) + ip = String(ip).padEnd(15) + console.log(time, ip, ua, name, req.method, req.url) - return next(); -}); + return next() +}) function must_be_logged_in(req, res, next) { if (!req.user) - return res.redirect('/login?redirect=' + encodeURIComponent(req.originalUrl)); - return next(); + return res.redirect('/login?redirect=' + encodeURIComponent(req.originalUrl)) + return next() } app.get('/', function (req, res) { - res.render('index.pug', { user: req.user, titles: TITLES }); -}); + res.render('index.pug', { user: req.user, titles: TITLES }) +}) app.get('/about', function (req, res) { - res.render('about.pug', { user: req.user }); -}); + res.render('about.pug', { user: req.user }) +}) app.get('/logout', function (req, res) { - let sid = login_cookie(req); + let sid = login_cookie(req) if (sid) - login_delete(res, sid); - res.redirect('/login'); -}); + login_delete(res, sid) + res.redirect('/login') +}) app.get('/login', function (req, res) { if (req.user) - return res.redirect('/'); - res.render('login.pug', { redirect: req.query.redirect || '/profile' }); -}); + return res.redirect('/') + res.render('login.pug', { redirect: req.query.redirect || '/profile' }) +}) app.post('/login', function (req, res) { - let name_or_mail = req.body.username; - let password = req.body.password; - let redirect = req.body.redirect; + let name_or_mail = req.body.username + let password = req.body.password + let redirect = req.body.redirect if (!is_email(name_or_mail)) - name_or_mail = clean_user_name(name_or_mail); - let user = SQL_SELECT_LOGIN_BY_NAME.get(name_or_mail); + name_or_mail = clean_user_name(name_or_mail) + let user = SQL_SELECT_LOGIN_BY_NAME.get(name_or_mail) if (!user) - user = SQL_SELECT_LOGIN_BY_MAIL.get(name_or_mail); + user = SQL_SELECT_LOGIN_BY_MAIL.get(name_or_mail) if (!user || is_blacklisted(user.mail) || hash_password(password, user.salt) != user.password) - return setTimeout(() => res.render('login.pug', { flash: "Invalid login." }), 1000); - login_insert(res, user.user_id); - res.redirect(redirect); -}); + return setTimeout(() => res.render('login.pug', { flash: "Invalid login." }), 1000) + login_insert(res, user.user_id) + res.redirect(redirect) +}) app.get('/signup', function (req, res) { if (req.user) - return res.redirect('/'); - res.render('signup.pug'); -}); + return res.redirect('/') + res.render('signup.pug') +}) app.post('/signup', function (req, res) { function err(msg) { - res.render('signup.pug', { flash: msg }); + res.render('signup.pug', { flash: msg }) } - let name = req.body.username; - let mail = req.body.mail; - let password = req.body.password; - name = clean_user_name(name); + let name = req.body.username + let mail = req.body.mail + let password = req.body.password + name = clean_user_name(name) if (!is_valid_user_name(name)) - return err("Invalid user name!"); + return err("Invalid user name!") if (SQL_EXISTS_USER_NAME.get(name)) - return err("That name is already taken."); + return err("That name is already taken.") if (!is_email(mail) || is_blacklisted(mail)) - return err("Invalid mail address!"); + return err("Invalid mail address!") if (SQL_EXISTS_USER_MAIL.get(mail)) - return err("That mail is already taken."); + return err("That mail is already taken.") if (password.length < 4) - return err("Password is too short!"); + return err("Password is too short!") if (password.length > 100) - return err("Password is too long!"); - let salt = crypto.randomBytes(32).toString('hex'); - let hash = hash_password(password, salt); - let user = SQL_INSERT_USER.get(name, mail, hash, salt); - login_insert(res, user.user_id); - res.redirect('/profile'); -}); + return err("Password is too long!") + let salt = crypto.randomBytes(32).toString('hex') + let hash = hash_password(password, salt) + let user = SQL_INSERT_USER.get(name, mail, hash, salt) + login_insert(res, user.user_id) + res.redirect('/profile') +}) app.get('/forgot-password', function (req, res) { if (req.user) - return res.redirect('/'); - res.render('forgot_password.pug'); -}); + return res.redirect('/') + res.render('forgot_password.pug') +}) app.post('/forgot-password', function (req, res) { - let mail = req.body.mail; - let user = SQL_SELECT_LOGIN_BY_MAIL.get(mail); + let mail = req.body.mail + let user = SQL_SELECT_LOGIN_BY_MAIL.get(mail) if (user) { - let token = SQL_FIND_TOKEN.get(user.user_id); + let token = SQL_FIND_TOKEN.get(user.user_id) if (!token) { - token = SQL_CREATE_TOKEN.get(user.user_id); - mail_password_reset_token(user, token); + token = SQL_CREATE_TOKEN.get(user.user_id) + mail_password_reset_token(user, token) } - return res.redirect('/reset-password/' + mail); + return res.redirect('/reset-password/' + mail) } - res.render('forgot_password.pug', { flash: "User not found." }); -}); + res.render('forgot_password.pug', { flash: "User not found." }) +}) app.get('/reset-password', function (req, res) { if (req.user) - return res.redirect('/'); - res.render('reset_password.pug', { mail: "", token: "" }); -}); + return res.redirect('/') + res.render('reset_password.pug', { mail: "", token: "" }) +}) app.get('/reset-password/:mail', function (req, res) { if (req.user) - return res.redirect('/'); - let mail = req.params.mail; - res.render('reset_password.pug', { mail: mail, token: "" }); -}); + return res.redirect('/') + let mail = req.params.mail + res.render('reset_password.pug', { mail: mail, token: "" }) +}) app.get('/reset-password/:mail/:token', function (req, res) { if (req.user) - return res.redirect('/'); - let mail = req.params.mail; - let token = req.params.token; - res.render('reset_password.pug', { mail: mail, token: token }); -}); + return res.redirect('/') + let mail = req.params.mail + let token = req.params.token + res.render('reset_password.pug', { mail: mail, token: token }) +}) app.post('/reset-password', function (req, res) { - let mail = req.body.mail; - let token = req.body.token; - let password = req.body.password; + let mail = req.body.mail + let token = req.body.token + let password = req.body.password function err(msg) { - res.render('reset_password.pug', { mail: mail, token: token }); + res.render('reset_password.pug', { mail: mail, token: token }) } - let user = SQL_SELECT_LOGIN_BY_MAIL.get(mail); + let user = SQL_SELECT_LOGIN_BY_MAIL.get(mail) if (!user) - return err("User not found."); + return err("User not found.") if (password.length < 4) - return err("Password is too short!"); + return err("Password is too short!") if (password.length > 100) - return err("Password is too long!"); + return err("Password is too long!") if (!SQL_VERIFY_TOKEN.get(user.user_id, token)) - return err("Invalid or expired token!"); - let salt = crypto.randomBytes(32).toString('hex'); - let hash = hash_password(password, salt); - SQL_UPDATE_USER_PASSWORD.run(hash, salt, user.user_id); - login_insert(res, user.user_id); - return res.redirect('/profile'); -}); + return err("Invalid or expired token!") + let salt = crypto.randomBytes(32).toString('hex') + let hash = hash_password(password, salt) + SQL_UPDATE_USER_PASSWORD.run(hash, salt, user.user_id) + login_insert(res, user.user_id) + return res.redirect('/profile') +}) app.get('/change-password', must_be_logged_in, function (req, res) { - res.render('change_password.pug', { user: req.user }); -}); + res.render('change_password.pug', { user: req.user }) +}) app.post('/change-password', must_be_logged_in, function (req, res) { - let oldpass = req.body.password; - let newpass = req.body.newpass; + let oldpass = req.body.password + let newpass = req.body.newpass // Get full user record including password and salt - let user = SQL_SELECT_LOGIN_BY_MAIL.get(req.user.mail); + let user = SQL_SELECT_LOGIN_BY_MAIL.get(req.user.mail) if (newpass.length < 4) - return res.render('change_password.pug', { user: req.user, flash: "Password is too short!" }); + return res.render('change_password.pug', { user: req.user, flash: "Password is too short!" }) if (newpass.length > 100) - return res.render('change_password.pug', { user: req.user, flash: "Password is too long!" }); - let oldhash = hash_password(oldpass, user.salt); + return res.render('change_password.pug', { user: req.user, flash: "Password is too long!" }) + let oldhash = hash_password(oldpass, user.salt) if (oldhash !== user.password) - return res.render('change_password.pug', { user: req.user, flash: "Wrong password!" }); - let salt = crypto.randomBytes(32).toString('hex'); - let hash = hash_password(newpass, salt); - return res.redirect('/profile'); -}); + return res.render('change_password.pug', { user: req.user, flash: "Wrong password!" }) + let salt = crypto.randomBytes(32).toString('hex') + let hash = hash_password(newpass, salt) + return res.redirect('/profile') +}) /* * USER PROFILE */ app.get('/subscribe', must_be_logged_in, function (req, res) { - SQL_UPDATE_USER_NOTIFY.run(1, req.user.user_id); - res.redirect('/profile'); -}); + SQL_UPDATE_USER_NOTIFY.run(1, req.user.user_id) + res.redirect('/profile') +}) app.get('/unsubscribe', must_be_logged_in, function (req, res) { - SQL_UPDATE_USER_NOTIFY.run(0, req.user.user_id); - res.redirect('/profile'); -}); + SQL_UPDATE_USER_NOTIFY.run(0, req.user.user_id) + res.redirect('/profile') +}) app.get('/change-name', must_be_logged_in, function (req, res) { - res.render('change_name.pug', { user: req.user }); -}); + res.render('change_name.pug', { user: req.user }) +}) app.post('/change-name', must_be_logged_in, function (req, res) { - let newname = clean_user_name(req.body.newname); + let newname = clean_user_name(req.body.newname) if (!is_valid_user_name(newname)) - return res.render('change_name.pug', { user: req.user, flash: "Invalid user name!" }); + return res.render('change_name.pug', { user: req.user, flash: "Invalid user name!" }) if (SQL_EXISTS_USER_NAME.get(newname)) - return res.render('change_name.pug', { user: req.user, flash: "That name is already taken!" }); - SQL_UPDATE_USER_NAME.run(newname, req.user.user_id); - return res.redirect('/profile'); -}); + return res.render('change_name.pug', { user: req.user, flash: "That name is already taken!" }) + SQL_UPDATE_USER_NAME.run(newname, req.user.user_id) + return res.redirect('/profile') +}) app.get('/change-mail', must_be_logged_in, function (req, res) { - res.render('change_mail.pug', { user: req.user }); -}); + res.render('change_mail.pug', { user: req.user }) +}) app.post('/change-mail', must_be_logged_in, function (req, res) { - let newmail = req.body.newmail; + let newmail = req.body.newmail if (!is_email(newmail)) - res.render('change_mail.pug', { user: req.user, flash: "Invalid mail address!" }); + res.render('change_mail.pug', { user: req.user, flash: "Invalid mail address!" }) if (SQL_EXISTS_USER_MAIL.get(newmail)) - res.render('change_mail.pug', { user: req.user, flash: "That mail address is already taken!" }); - SQL_UPDATE_USER_MAIL.run(newmail, req.user.user_id); - return res.redirect('/profile'); -}); + res.render('change_mail.pug', { user: req.user, flash: "That mail address is already taken!" }) + SQL_UPDATE_USER_MAIL.run(newmail, req.user.user_id) + return res.redirect('/profile') +}) app.get('/change-about', must_be_logged_in, function (req, res) { - let about = SQL_SELECT_USER_PROFILE.get(req.user.name).about; - res.render('change_about.pug', { user: req.user, about: about || "" }); -}); + let about = SQL_SELECT_USER_PROFILE.get(req.user.name).about + res.render('change_about.pug', { user: req.user, about: about || "" }) +}) app.post('/change-about', must_be_logged_in, function (req, res) { - SQL_UPDATE_USER_ABOUT.run(req.body.about, req.user.user_id); - return res.redirect('/profile'); -}); + SQL_UPDATE_USER_ABOUT.run(req.body.about, req.user.user_id) + return res.redirect('/profile') +}) app.get('/user/:who_name', function (req, res) { - let who = SQL_SELECT_USER_PROFILE.get(req.params.who_name); + let who = SQL_SELECT_USER_PROFILE.get(req.params.who_name) if (who) { - who.avatar = get_avatar(who.mail); - who.ctime = human_date(who.ctime); - who.atime = human_date(who.atime); - res.render('user.pug', { user: req.user, who: who }); + who.avatar = get_avatar(who.mail) + who.ctime = human_date(who.ctime) + who.atime = human_date(who.atime) + res.render('user.pug', { user: req.user, who: who }) } else { - return res.status(404).send("Invalid user name."); + return res.status(404).send("Invalid user name.") } -}); +}) app.get('/users', function (req, res) { - let rows = SQL("SELECT * FROM user_profile_view ORDER BY atime DESC").all(); + let rows = SQL("SELECT * FROM user_profile_view ORDER BY atime DESC").all() rows.forEach(row => { - row.avatar = get_avatar(row.mail); - row.ctime = human_date(row.ctime); - row.atime = human_date(row.atime); - }); - res.render('user_list.pug', { user: req.user, user_list: rows }); -}); + row.avatar = get_avatar(row.mail) + row.ctime = human_date(row.ctime) + row.atime = human_date(row.atime) + }) + res.render('user_list.pug', { user: req.user, user_list: rows }) +}) app.get('/chat', must_be_logged_in, function (req, res) { - let chat = SQL_SELECT_USER_CHAT_N.all(req.user.user_id, 12*20); - res.render('chat.pug', { user: req.user, chat: chat, page_size: 12 }); -}); + let chat = SQL_SELECT_USER_CHAT_N.all(req.user.user_id, 12*20) + res.render('chat.pug', { user: req.user, chat: chat, page_size: 12 }) +}) app.get('/chat/all', must_be_logged_in, function (req, res) { - let chat = SQL_SELECT_USER_CHAT.all(req.user.user_id); - res.render('chat.pug', { user: req.user, chat: chat, page_size: 0 }); -}); + let chat = SQL_SELECT_USER_CHAT.all(req.user.user_id) + res.render('chat.pug', { user: req.user, chat: chat, page_size: 0 }) +}) /* * MESSAGES @@ -601,57 +601,57 @@ const MESSAGE_LIST_INBOX = SQL(` SELECT message_id, from_name, subject, time, is_read FROM message_view WHERE to_id=? AND is_deleted_from_inbox=0 - ORDER BY message_id DESC`); + ORDER BY message_id DESC`) const MESSAGE_LIST_OUTBOX = SQL(` SELECT message_id, to_name, subject, time, 1 as is_read FROM message_view WHERE from_id=? AND is_deleted_from_outbox=0 - ORDER BY message_id DESC`); + ORDER BY message_id DESC`) -const MESSAGE_FETCH = SQL("SELECT * FROM message_view WHERE message_id=? AND ( from_id=? OR to_id=? )"); -const MESSAGE_SEND = SQL("INSERT INTO messages (from_id,to_id,subject,body) VALUES (?,?,?,?)"); -const MESSAGE_MARK_READ = SQL("UPDATE messages SET is_read=1 WHERE message_id=? AND is_read = 0"); -const MESSAGE_DELETE_INBOX = SQL("UPDATE messages SET is_deleted_from_inbox=1 WHERE message_id=? AND to_id=?"); -const MESSAGE_DELETE_OUTBOX = SQL("UPDATE messages SET is_deleted_from_outbox=1 WHERE message_id=? AND from_id=?"); -const MESSAGE_DELETE_ALL_OUTBOX = SQL("UPDATE messages SET is_deleted_from_outbox=1 WHERE from_id=?"); +const MESSAGE_FETCH = SQL("SELECT * FROM message_view WHERE message_id=? AND ( from_id=? OR to_id=? )") +const MESSAGE_SEND = SQL("INSERT INTO messages (from_id,to_id,subject,body) VALUES (?,?,?,?)") +const MESSAGE_MARK_READ = SQL("UPDATE messages SET is_read=1 WHERE message_id=? AND is_read = 0") +const MESSAGE_DELETE_INBOX = SQL("UPDATE messages SET is_deleted_from_inbox=1 WHERE message_id=? AND to_id=?") +const MESSAGE_DELETE_OUTBOX = SQL("UPDATE messages SET is_deleted_from_outbox=1 WHERE message_id=? AND from_id=?") +const MESSAGE_DELETE_ALL_OUTBOX = SQL("UPDATE messages SET is_deleted_from_outbox=1 WHERE from_id=?") app.get('/inbox', must_be_logged_in, function (req, res) { - let messages = MESSAGE_LIST_INBOX.all(req.user.user_id); + let messages = MESSAGE_LIST_INBOX.all(req.user.user_id) for (let i = 0; i < messages.length; ++i) - messages[i].time = human_date(messages[i].time); + messages[i].time = human_date(messages[i].time) res.render('message_inbox.pug', { user: req.user, messages: messages, - }); -}); + }) +}) app.get('/outbox', must_be_logged_in, function (req, res) { - let messages = MESSAGE_LIST_OUTBOX.all(req.user.user_id); + let messages = MESSAGE_LIST_OUTBOX.all(req.user.user_id) for (let i = 0; i < messages.length; ++i) - messages[i].time = human_date(messages[i].time); + messages[i].time = human_date(messages[i].time) res.render('message_outbox.pug', { user: req.user, messages: messages, - }); -}); + }) +}) app.get('/message/read/:message_id', must_be_logged_in, function (req, res) { - let message_id = req.params.message_id | 0; - let message = MESSAGE_FETCH.get(message_id, req.user.user_id, req.user.user_id); + let message_id = req.params.message_id | 0 + let message = MESSAGE_FETCH.get(message_id, req.user.user_id, req.user.user_id) if (!message) - return res.status(404).send("Invalid message ID."); + return res.status(404).send("Invalid message ID.") if (message.to_id === req.user.user_id && message.is_read === 0) { - MESSAGE_MARK_READ.run(message_id); - req.user.unread --; + MESSAGE_MARK_READ.run(message_id) + req.user.unread -- } - message.time = human_date(message.time); - message.body = linkify_post(message.body); + message.time = human_date(message.time) + message.body = linkify_post(message.body) res.render('message_read.pug', { user: req.user, message: message, - }); -}); + }) +}) app.get('/message/send', must_be_logged_in, function (req, res) { res.render('message_send.pug', { @@ -659,24 +659,24 @@ app.get('/message/send', must_be_logged_in, function (req, res) { to_name: "", subject: "", body: "", - }); -}); + }) +}) app.get('/message/send/:to_name', must_be_logged_in, function (req, res) { - let to_name = req.params.to_name; + let to_name = req.params.to_name res.render('message_send.pug', { user: req.user, to_name: to_name, subject: "", body: "", - }); -}); + }) +}) app.post('/message/send', must_be_logged_in, function (req, res) { - let to_name = req.body.to.trim(); - let subject = req.body.subject.trim(); - let body = req.body.body.trim(); - let to_user = SQL_SELECT_USER_BY_NAME.get(to_name); + let to_name = req.body.to.trim() + let subject = req.body.subject.trim() + let body = req.body.body.trim() + let to_user = SQL_SELECT_USER_BY_NAME.get(to_name) if (!to_user) { return res.render('message_send.pug', { user: req.user, @@ -685,274 +685,274 @@ app.post('/message/send', must_be_logged_in, function (req, res) { subject: subject, body: body, flash: "Cannot find that user." - }); + }) } - let info = MESSAGE_SEND.run(req.user.user_id, to_user.user_id, subject, body); + let info = MESSAGE_SEND.run(req.user.user_id, to_user.user_id, subject, body) if (to_user.notify) - mail_new_message(to_user, info.lastInsertRowid, req.user.name); - res.redirect('/inbox'); -}); + mail_new_message(to_user, info.lastInsertRowid, req.user.name) + res.redirect('/inbox') +}) function quote_body(message) { - let when = new Date(message.time).toDateString(); - let who = message.from_name; - let what = message.body.split("\n").join("\n> "); - return "\n\n" + "On " + when + " " + who + " wrote:\n> " + what + "\n"; + let when = new Date(message.time).toDateString() + let who = message.from_name + let what = message.body.split("\n").join("\n> ") + return "\n\n" + "On " + when + " " + who + " wrote:\n> " + what + "\n" } app.get('/message/reply/:message_id', must_be_logged_in, function (req, res) { - let message_id = req.params.message_id | 0; - let message = MESSAGE_FETCH.get(message_id, req.user.user_id, req.user.user_id); + let message_id = req.params.message_id | 0 + let message = MESSAGE_FETCH.get(message_id, req.user.user_id, req.user.user_id) if (!message) - return res.status(404).send("Invalid message ID."); + return res.status(404).send("Invalid message ID.") return res.render('message_send.pug', { user: req.user, to_id: message.from_id, to_name: message.from_name, subject: message.subject.startsWith("Re: ") ? message.subject : "Re: " + message.subject, body: quote_body(message), - }); -}); + }) +}) app.get('/message/delete/:message_id', must_be_logged_in, function (req, res) { - let message_id = req.params.message_id | 0; - MESSAGE_DELETE_INBOX.run(message_id, req.user.user_id); - MESSAGE_DELETE_OUTBOX.run(message_id, req.user.user_id); - res.redirect('/inbox'); -}); + let message_id = req.params.message_id | 0 + MESSAGE_DELETE_INBOX.run(message_id, req.user.user_id) + MESSAGE_DELETE_OUTBOX.run(message_id, req.user.user_id) + res.redirect('/inbox') +}) app.get('/outbox/delete', must_be_logged_in, function (req, res) { - MESSAGE_DELETE_ALL_OUTBOX.run(req.user.user_id); - res.redirect('/outbox'); -}); + MESSAGE_DELETE_ALL_OUTBOX.run(req.user.user_id) + res.redirect('/outbox') +}) /* * FORUM */ -const FORUM_PAGE_SIZE = 15; +const FORUM_PAGE_SIZE = 15 -const FORUM_COUNT_THREADS = SQL("SELECT COUNT(*) FROM threads").pluck(); -const FORUM_LIST_THREADS = SQL("SELECT * FROM thread_view ORDER BY mtime DESC LIMIT ? OFFSET ?"); -const FORUM_GET_THREAD = SQL("SELECT * FROM thread_view WHERE thread_id=?"); -const FORUM_LIST_POSTS = SQL("SELECT * FROM post_view WHERE thread_id=?"); -const FORUM_GET_POST = SQL("SELECT * FROM post_view WHERE post_id=?"); -const FORUM_NEW_THREAD = SQL("INSERT INTO threads (author_id,subject) VALUES (?,?)"); -const FORUM_NEW_POST = SQL("INSERT INTO posts (thread_id,author_id,body) VALUES (?,?,?)"); -const FORUM_EDIT_POST = SQL("UPDATE posts SET body=?, mtime=datetime('now') WHERE post_id=? AND author_id=? RETURNING thread_id").pluck(); +const FORUM_COUNT_THREADS = SQL("SELECT COUNT(*) FROM threads").pluck() +const FORUM_LIST_THREADS = SQL("SELECT * FROM thread_view ORDER BY mtime DESC LIMIT ? OFFSET ?") +const FORUM_GET_THREAD = SQL("SELECT * FROM thread_view WHERE thread_id=?") +const FORUM_LIST_POSTS = SQL("SELECT * FROM post_view WHERE thread_id=?") +const FORUM_GET_POST = SQL("SELECT * FROM post_view WHERE post_id=?") +const FORUM_NEW_THREAD = SQL("INSERT INTO threads (author_id,subject) VALUES (?,?)") +const FORUM_NEW_POST = SQL("INSERT INTO posts (thread_id,author_id,body) VALUES (?,?,?)") +const FORUM_EDIT_POST = SQL("UPDATE posts SET body=?, mtime=datetime('now') WHERE post_id=? AND author_id=? RETURNING thread_id").pluck() function show_forum_page(req, res, page) { - let thread_count = FORUM_COUNT_THREADS.get(); - let page_count = Math.ceil(thread_count / FORUM_PAGE_SIZE); - let threads = FORUM_LIST_THREADS.all(FORUM_PAGE_SIZE, FORUM_PAGE_SIZE * (page - 1)); + let thread_count = FORUM_COUNT_THREADS.get() + let page_count = Math.ceil(thread_count / FORUM_PAGE_SIZE) + let threads = FORUM_LIST_THREADS.all(FORUM_PAGE_SIZE, FORUM_PAGE_SIZE * (page - 1)) for (let thread of threads) { - thread.ctime = human_date(thread.ctime); - thread.mtime = human_date(thread.mtime); + thread.ctime = human_date(thread.ctime) + thread.mtime = human_date(thread.mtime) } res.render('forum_view.pug', { user: req.user, threads: threads, current_page: page, page_count: page_count, - }); + }) } function linkify_post(text) { - text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); + text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") text = text.replace(/https?:\/\/\S+/g, (match) => { if (match.endsWith(".jpg") || match.endsWith(".png") || match.endsWith(".svg")) - return `<a href="${match}"><img src="${match}"></a>`; - return `<a href="${match}">${match}</a>`; - }); - return text; + return `<a href="${match}"><img src="${match}"></a>` + return `<a href="${match}">${match}</a>` + }) + return text } app.get('/forum', function (req, res) { - show_forum_page(req, res, 1); -}); + show_forum_page(req, res, 1) +}) app.get('/forum/page/:page', function (req, res) { - show_forum_page(req, res, req.params.page | 0); -}); + show_forum_page(req, res, req.params.page | 0) +}) app.get('/forum/thread/:thread_id', function (req, res) { - let thread_id = req.params.thread_id | 0; - let thread = FORUM_GET_THREAD.get(thread_id); - let posts = FORUM_LIST_POSTS.all(thread_id); + let thread_id = req.params.thread_id | 0 + let thread = FORUM_GET_THREAD.get(thread_id) + let posts = FORUM_LIST_POSTS.all(thread_id) if (!thread) - return res.status(404).send("Invalid thread ID."); + return res.status(404).send("Invalid thread ID.") for (let i = 0; i < posts.length; ++i) { - posts[i].body = linkify_post(posts[i].body); - posts[i].edited = posts[i].mtime !== posts[i].ctime; - posts[i].ctime = human_date(posts[i].ctime); - posts[i].mtime = human_date(posts[i].mtime); + posts[i].body = linkify_post(posts[i].body) + posts[i].edited = posts[i].mtime !== posts[i].ctime + posts[i].ctime = human_date(posts[i].ctime) + posts[i].mtime = human_date(posts[i].mtime) } res.render('forum_thread.pug', { user: req.user, thread: thread, posts: posts, - }); -}); + }) +}) app.get('/forum/post', must_be_logged_in, function (req, res) { res.render('forum_post.pug', { user: req.user, - }); -}); + }) +}) app.post('/forum/post', must_be_logged_in, function (req, res) { - let user_id = req.user.user_id; - let subject = req.body.subject.trim(); - let body = req.body.body; + let user_id = req.user.user_id + let subject = req.body.subject.trim() + let body = req.body.body if (subject.length === 0) - subject = "Untitled"; - let thread_id = FORUM_NEW_THREAD.run(user_id, subject).lastInsertRowid; - FORUM_NEW_POST.run(thread_id, user_id, body); - res.redirect('/forum/thread/'+thread_id); -}); + subject = "Untitled" + let thread_id = FORUM_NEW_THREAD.run(user_id, subject).lastInsertRowid + FORUM_NEW_POST.run(thread_id, user_id, body) + res.redirect('/forum/thread/'+thread_id) +}) app.get('/forum/edit/:post_id', must_be_logged_in, function (req, res) { // TODO: edit subject if editing first post - let post_id = req.params.post_id | 0; - let post = FORUM_GET_POST.get(post_id); + let post_id = req.params.post_id | 0 + let post = FORUM_GET_POST.get(post_id) if (!post || post.author_id != req.user.user_id) - return res.status(404).send("Invalid post ID."); - post.ctime = human_date(post.ctime); - post.mtime = human_date(post.mtime); + return res.status(404).send("Invalid post ID.") + post.ctime = human_date(post.ctime) + post.mtime = human_date(post.mtime) res.render('forum_edit.pug', { user: req.user, post: post, - }); -}); + }) +}) app.post('/forum/edit/:post_id', must_be_logged_in, function (req, res) { - let user_id = req.user.user_id; - let post_id = req.params.post_id | 0; - let body = req.body.body; - let thread_id = FORUM_EDIT_POST.get(body, post_id, user_id); - res.redirect('/forum/thread/'+thread_id); -}); + let user_id = req.user.user_id + let post_id = req.params.post_id | 0 + let body = req.body.body + let thread_id = FORUM_EDIT_POST.get(body, post_id, user_id) + res.redirect('/forum/thread/'+thread_id) +}) app.get('/forum/reply/:post_id', must_be_logged_in, function (req, res) { - let post_id = req.params.post_id | 0; - let post = FORUM_GET_POST.get(post_id); + let post_id = req.params.post_id | 0 + let post = FORUM_GET_POST.get(post_id) if (!post) - return res.status(404).send("Invalid post ID."); - let thread = FORUM_GET_THREAD.get(post.thread_id); - post.body = linkify_post(post.body); - post.edited = post.mtime !== post.ctime; - post.ctime = human_date(post.ctime); - post.mtime = human_date(post.mtime); + return res.status(404).send("Invalid post ID.") + let thread = FORUM_GET_THREAD.get(post.thread_id) + post.body = linkify_post(post.body) + post.edited = post.mtime !== post.ctime + post.ctime = human_date(post.ctime) + post.mtime = human_date(post.mtime) res.render('forum_reply.pug', { user: req.user, thread: thread, post: post, - }); -}); + }) +}) app.post('/forum/reply/:thread_id', must_be_logged_in, function (req, res) { - let thread_id = req.params.thread_id | 0; - let user_id = req.user.user_id; - let body = req.body.body; - FORUM_NEW_POST.run(thread_id, user_id, body); - res.redirect('/forum/thread/'+thread_id); -}); + let thread_id = req.params.thread_id | 0 + let user_id = req.user.user_id + let body = req.body.body + FORUM_NEW_POST.run(thread_id, user_id, body) + res.redirect('/forum/thread/'+thread_id) +}) /* * GAME LOBBY */ -let TITLES = {}; -let RULES = {}; -let HTML_ABOUT = {}; -let HTML_CREATE = {}; +let TITLES = {} +let RULES = {} +let HTML_ABOUT = {} +let HTML_CREATE = {} function is_open_game(game) { - return game.status === 0 && !game.is_ready; + return game.status === 0 && !game.is_ready } function is_ready_game(game) { - return game.status === 0 && game.is_ready; + return game.status === 0 && game.is_ready } function is_replacement_game(game) { - return game.status === 1 && !game.is_ready; + return game.status === 1 && !game.is_ready } function is_active_game(game) { - return game.status === 1 && game.is_ready; + return game.status === 1 && game.is_ready } function is_finished_game(game) { - return game.status === 2; + return game.status === 2 } function load_rules() { - const SQL_SELECT_TITLES = SQL("SELECT * FROM titles"); + const SQL_SELECT_TITLES = SQL("SELECT * FROM titles") for (let title of SQL_SELECT_TITLES.all()) { - let title_id = title.title_id; + let title_id = title.title_id if (fs.existsSync(__dirname + "/public/" + title_id + "/rules.js")) { - console.log("Loading rules for " + title_id); + console.log("Loading rules for " + title_id) try { - TITLES[title_id] = title; - RULES[title_id] = require("./public/" + title_id + "/rules.js"); - HTML_ABOUT[title_id] = fs.readFileSync("./public/" + title_id + "/about.html"); - HTML_CREATE[title_id] = fs.readFileSync("./public/" + title_id + "/create.html"); + TITLES[title_id] = title + RULES[title_id] = require("./public/" + title_id + "/rules.js") + HTML_ABOUT[title_id] = fs.readFileSync("./public/" + title_id + "/about.html") + HTML_CREATE[title_id] = fs.readFileSync("./public/" + title_id + "/create.html") } catch (err) { - console.log(err); + console.log(err) } } else { - console.log("Cannot find rules for " + title_id); + console.log("Cannot find rules for " + title_id) } } } function get_game_roles(title_id, scenario, options) { - let roles = RULES[title_id].roles; + let roles = RULES[title_id].roles if (typeof roles === 'function') - return roles(scenario, options); - return roles; + return roles(scenario, options) + return roles } -load_rules(); +load_rules() -const SQL_INSERT_GAME = SQL("INSERT INTO games (owner_id,title_id,scenario,options,is_private,is_random,description) VALUES (?,?,?,?,?,?,?)"); -const SQL_DELETE_GAME = SQL("DELETE FROM games WHERE game_id=? AND owner_id=?"); +const SQL_INSERT_GAME = SQL("INSERT INTO games (owner_id,title_id,scenario,options,is_private,is_random,description) VALUES (?,?,?,?,?,?,?)") +const SQL_DELETE_GAME = SQL("DELETE FROM games WHERE game_id=? AND owner_id=?") -const SQL_SELECT_USER_CHAT = SQL("SELECT game_id,time,name,message FROM game_chat_view WHERE game_id IN ( SELECT DISTINCT game_id FROM players WHERE user_id=? ) ORDER BY chat_id DESC").raw(); -const SQL_SELECT_USER_CHAT_N = SQL("SELECT game_id,time,name,message FROM game_chat_view WHERE game_id IN ( SELECT DISTINCT game_id FROM players WHERE user_id=? ) ORDER BY chat_id DESC LIMIT ?").raw(); +const SQL_SELECT_USER_CHAT = SQL("SELECT game_id,time,name,message FROM game_chat_view WHERE game_id IN ( SELECT DISTINCT game_id FROM players WHERE user_id=? ) ORDER BY chat_id DESC").raw() +const SQL_SELECT_USER_CHAT_N = SQL("SELECT game_id,time,name,message FROM game_chat_view WHERE game_id IN ( SELECT DISTINCT game_id FROM players WHERE user_id=? ) ORDER BY chat_id DESC LIMIT ?").raw() -const SQL_SELECT_GAME_CHAT = SQL("SELECT chat_id,time,name,message FROM game_chat_view WHERE game_id=? AND chat_id>?").raw(); -const SQL_INSERT_GAME_CHAT = SQL("INSERT INTO game_chat (game_id,user_id,message) VALUES (?,?,?) RETURNING chat_id,time,'',message").raw(); +const SQL_SELECT_GAME_CHAT = SQL("SELECT chat_id,time,name,message FROM game_chat_view WHERE game_id=? AND chat_id>?").raw() +const SQL_INSERT_GAME_CHAT = SQL("INSERT INTO game_chat (game_id,user_id,message) VALUES (?,?,?) RETURNING chat_id,time,'',message").raw() -const SQL_SELECT_GAME_STATE = SQL("SELECT state FROM game_state WHERE game_id=?").pluck(); -const SQL_UPDATE_GAME_STATE = SQL("INSERT OR REPLACE INTO game_state (game_id,state,active,mtime) VALUES (?,?,?,datetime('now'))"); -const SQL_UPDATE_GAME_RESULT = SQL("UPDATE games SET status=?, result=? WHERE game_id=?"); -const SQL_UPDATE_GAME_PRIVATE = SQL("UPDATE games SET is_private=1 WHERE game_id=?"); -const SQL_INSERT_REPLAY = SQL("INSERT INTO game_replay (game_id,role,action,arguments) VALUES (?,?,?,?)"); -const SQL_SELECT_REPLAY = SQL("SELECT role,action,arguments FROM game_replay WHERE game_id=?"); +const SQL_SELECT_GAME_STATE = SQL("SELECT state FROM game_state WHERE game_id=?").pluck() +const SQL_UPDATE_GAME_STATE = SQL("INSERT OR REPLACE INTO game_state (game_id,state,active,mtime) VALUES (?,?,?,datetime('now'))") +const SQL_UPDATE_GAME_RESULT = SQL("UPDATE games SET status=?, result=? WHERE game_id=?") +const SQL_UPDATE_GAME_PRIVATE = SQL("UPDATE games SET is_private=1 WHERE game_id=?") +const SQL_INSERT_REPLAY = SQL("INSERT INTO game_replay (game_id,role,action,arguments) VALUES (?,?,?,?)") +const SQL_SELECT_REPLAY = SQL("SELECT role,action,arguments FROM game_replay WHERE game_id=?") -const SQL_SELECT_GAME = SQL("SELECT * FROM games WHERE game_id=?"); -const SQL_SELECT_GAME_VIEW = SQL("SELECT * FROM game_view WHERE game_id=?"); -const SQL_SELECT_GAME_FULL_VIEW = SQL("SELECT * FROM game_full_view WHERE game_id=?"); -const SQL_SELECT_GAME_TITLE = SQL("SELECT title_id FROM games WHERE game_id=?").pluck(); -const SQL_SELECT_GAME_RANDOM = SQL("SELECT is_random FROM games WHERE game_id=?").pluck(); +const SQL_SELECT_GAME = SQL("SELECT * FROM games WHERE game_id=?") +const SQL_SELECT_GAME_VIEW = SQL("SELECT * FROM game_view WHERE game_id=?") +const SQL_SELECT_GAME_FULL_VIEW = SQL("SELECT * FROM game_full_view WHERE game_id=?") +const SQL_SELECT_GAME_TITLE = SQL("SELECT title_id FROM games WHERE game_id=?").pluck() +const SQL_SELECT_GAME_RANDOM = SQL("SELECT is_random FROM games WHERE game_id=?").pluck() -const SQL_SELECT_GAME_HAS_TITLE_AND_STATUS = SQL("SELECT 1 FROM games WHERE game_id=? AND title_id=? AND status=?"); +const SQL_SELECT_GAME_HAS_TITLE_AND_STATUS = SQL("SELECT 1 FROM games WHERE game_id=? AND title_id=? AND status=?") -const SQL_SELECT_PLAYERS = SQL("SELECT * FROM players NATURAL JOIN user_view WHERE game_id=?"); -const SQL_SELECT_PLAYERS_JOIN = SQL("SELECT role, user_id, name FROM players NATURAL JOIN users WHERE game_id=?"); -const SQL_SELECT_PLAYER_ROLE = SQL("SELECT role FROM players WHERE game_id=? AND user_id=?").pluck(); -const SQL_INSERT_PLAYER_ROLE = SQL("INSERT OR IGNORE INTO players (game_id,role,user_id) VALUES (?,?,?)"); -const SQL_DELETE_PLAYER_ROLE = SQL("DELETE FROM players WHERE game_id=? AND role=?"); -const SQL_UPDATE_PLAYER_ROLE = SQL("UPDATE players SET role=? WHERE game_id=? AND role=? AND user_id=?"); +const SQL_SELECT_PLAYERS = SQL("SELECT * FROM players NATURAL JOIN user_view WHERE game_id=?") +const SQL_SELECT_PLAYERS_JOIN = SQL("SELECT role, user_id, name FROM players NATURAL JOIN users WHERE game_id=?") +const SQL_SELECT_PLAYER_ROLE = SQL("SELECT role FROM players WHERE game_id=? AND user_id=?").pluck() +const SQL_INSERT_PLAYER_ROLE = SQL("INSERT OR IGNORE INTO players (game_id,role,user_id) VALUES (?,?,?)") +const SQL_DELETE_PLAYER_ROLE = SQL("DELETE FROM players WHERE game_id=? AND role=?") +const SQL_UPDATE_PLAYER_ROLE = SQL("UPDATE players SET role=? WHERE game_id=? AND role=? AND user_id=?") -const SQL_AUTHORIZE_GAME_ROLE = SQL("SELECT 1 FROM players NATURAL JOIN games WHERE title_id=? AND game_id=? AND role=? AND user_id=?").pluck(); +const SQL_AUTHORIZE_GAME_ROLE = SQL("SELECT 1 FROM players NATURAL JOIN games WHERE title_id=? AND game_id=? AND role=? AND user_id=?").pluck() -const SQL_SELECT_OPEN_GAMES = SQL("SELECT * FROM games WHERE status=0"); -const SQL_COUNT_OPEN_GAMES = SQL("SELECT COUNT(*) FROM games WHERE owner_id=? AND status=0").pluck(); +const SQL_SELECT_OPEN_GAMES = SQL("SELECT * FROM games WHERE status=0") +const SQL_COUNT_OPEN_GAMES = SQL("SELECT COUNT(*) FROM games WHERE owner_id=? AND status=0").pluck() -const SQL_SELECT_REMATCH = SQL("SELECT game_id FROM games WHERE status < 3 AND description=?").pluck(); +const SQL_SELECT_REMATCH = SQL("SELECT game_id FROM games WHERE status < 3 AND description=?").pluck() const SQL_INSERT_REMATCH = SQL(` INSERT INTO games (owner_id, title_id, scenario, options, is_private, is_random, description) @@ -962,14 +962,14 @@ const SQL_INSERT_REMATCH = SQL(` WHERE game_id = $game_id AND NOT EXISTS ( SELECT * FROM games WHERE description=$magic ) -`); +`) const QUERY_LIST_PUBLIC_GAMES = SQL(` SELECT * FROM game_view WHERE is_private=0 AND status < 2 AND EXISTS ( SELECT 1 FROM players WHERE players.game_id = game_view.game_id ) ORDER BY mtime DESC - `); + `) const QUERY_LIST_GAMES_OF_TITLE = SQL(` SELECT * FROM game_view @@ -977,7 +977,7 @@ const QUERY_LIST_GAMES_OF_TITLE = SQL(` AND EXISTS ( SELECT 1 FROM players WHERE players.game_id = game_view.game_id ) ORDER BY mtime DESC LIMIT ? - `); + `) const QUERY_LIST_ACTIVE_GAMES_OF_USER = SQL(` select * from game_view @@ -986,7 +986,7 @@ const QUERY_LIST_ACTIVE_GAMES_OF_USER = SQL(` and ( status < 2 or mtime > datetime('now', '-7 days') ) order by status asc, mtime desc - `); + `) const QUERY_LIST_FINISHED_GAMES_OF_USER = SQL(` select * from game_view @@ -995,26 +995,26 @@ const QUERY_LIST_FINISHED_GAMES_OF_USER = SQL(` and status = 2 order by status asc, mtime desc - `); + `) function is_active(game, players, user_id) { if (game.status !== 1 || user_id === 0) - return false; - let active = game.active; + return false + let active = game.active for (let i = 0; i < players.length; ++i) { - let p = players[i]; + let p = players[i] if ((p.user_id === user_id) && (active === 'All' || active === 'Both' || active === p.role)) - return true; + return true } - return false; + return false } function is_shared(game, players, user_id) { - let n = 0; + let n = 0 for (let i = 0; i < players.length; ++i) if (players[i].user_id === user_id) - ++n; - return n > 1; + ++n + return n > 1 } function is_solo(players) { @@ -1023,14 +1023,14 @@ function is_solo(players) { function format_options(options) { function to_english(k) { - if (k === true || k === 1) return 'yes'; - if (k === false) return 'no'; - return k.replace(/_/g, " ").replace(/^\w/, c => c.toUpperCase()); + if (k === true || k === 1) return 'yes' + if (k === false) return 'no' + return k.replace(/_/g, " ").replace(/^\w/, c => c.toUpperCase()) } if (!options || options === '{}') - return "None"; - options = JSON.parse(options); - return Object.entries(options||{}).map(([k,v]) => (v === true || v === 1) ? to_english(k) : `${to_english(k)}=${to_english(v)}`).join(", "); + return "None" + options = JSON.parse(options) + return Object.entries(options||{}).map(([k,v]) => (v === true || v === 1) ? to_english(k) : `${to_english(k)}=${to_english(v)}`).join(", ") } function annotate_game(game, user_id) { @@ -1088,21 +1088,21 @@ function annotate_games(games, user_id) { } app.get('/profile', must_be_logged_in, function (req, res) { - req.user.notify = SQL_SELECT_USER_NOTIFY.get(req.user.user_id); - let avatar = get_avatar(req.user.mail); + req.user.notify = SQL_SELECT_USER_NOTIFY.get(req.user.user_id) + let avatar = get_avatar(req.user.mail) res.render('profile.pug', { user: req.user, avatar: avatar, - }); -}); + }) +}) app.get('/games', function (req, res) { - res.redirect('/games/public'); -}); + res.redirect('/games/public') +}) app.get('/games/active', must_be_logged_in, function (req, res) { - let games = QUERY_LIST_ACTIVE_GAMES_OF_USER.all({user_id: req.user.user_id}); - annotate_games(games, req.user.user_id); + let games = QUERY_LIST_ACTIVE_GAMES_OF_USER.all({user_id: req.user.user_id}) + annotate_games(games, req.user.user_id) res.render('games_active.pug', { user: req.user, open_games: games.filter(is_open_game), @@ -1110,24 +1110,24 @@ app.get('/games/active', must_be_logged_in, function (req, res) { ready_games: games.filter(is_ready_game), active_games: games.filter(is_active_game), finished_games: games.filter(is_finished_game), - }); -}); + }) +}) app.get('/games/finished', must_be_logged_in, function (req, res) { - let games = QUERY_LIST_FINISHED_GAMES_OF_USER.all({user_id: req.user.user_id}); - annotate_games(games, req.user.user_id); + let games = QUERY_LIST_FINISHED_GAMES_OF_USER.all({user_id: req.user.user_id}) + annotate_games(games, req.user.user_id) res.render('games_finished.pug', { user: req.user, finished_games: games, - }); -}); + }) +}) app.get('/games/public', function (req, res) { let games = QUERY_LIST_PUBLIC_GAMES.all() if (req.user) - annotate_games(games, req.user.user_id); + annotate_games(games, req.user.user_id) else - annotate_games(games, 0); + annotate_games(games, 0) res.render('games_public.pug', { user: req.user, open_games: games.filter(is_open_game), @@ -1135,21 +1135,21 @@ app.get('/games/public', function (req, res) { ready_games: games.filter(is_ready_game), active_games: games.filter(is_active_game), finished_games: games.filter(is_finished_game), - }); -}); + }) +}) app.get('/info/:title_id', function (req, res) { - return res.redirect('/' + req.params.title_id); -}); + return res.redirect('/' + req.params.title_id) +}) function get_title_page(req, res, title_id) { - let title = TITLES[title_id]; + let title = TITLES[title_id] if (!title) - return res.status(404).send("Invalid title."); - let active_games = QUERY_LIST_GAMES_OF_TITLE.all(title_id, 0, 1, 1000); - let finished_games = QUERY_LIST_GAMES_OF_TITLE.all(title_id, 2, 2, 50); - annotate_games(active_games, req.user ? req.user.user_id : 0); - annotate_games(finished_games, req.user ? req.user.user_id : 0); + return res.status(404).send("Invalid title.") + let active_games = QUERY_LIST_GAMES_OF_TITLE.all(title_id, 0, 1, 1000) + let finished_games = QUERY_LIST_GAMES_OF_TITLE.all(title_id, 2, 2, 50) + annotate_games(active_games, req.user ? req.user.user_id : 0) + annotate_games(finished_games, req.user ? req.user.user_id : 0) res.render('info.pug', { user: req.user, title: title, @@ -1159,464 +1159,464 @@ function get_title_page(req, res, title_id) { ready_games: active_games.filter(is_ready_game), active_games: active_games.filter(is_active_game), finished_games: finished_games, - }); + }) } for (let title_id in TITLES) - app.get('/' + title_id, (req, res) => get_title_page(req, res, title_id)); + app.get('/' + title_id, (req, res) => get_title_page(req, res, title_id)) app.get('/create/:title_id', must_be_logged_in, function (req, res) { - let title_id = req.params.title_id; - let title = TITLES[title_id]; + let title_id = req.params.title_id + let title = TITLES[title_id] if (!title) - return res.status(404).send("Invalid title."); + return res.status(404).send("Invalid title.") res.render('create.pug', { user: req.user, title: title, scenarios: RULES[title_id].scenarios, create_html: HTML_CREATE[title_id], - }); -}); + }) +}) function options_json_replacer(key, value) { - if (key === 'scenario') return undefined; - if (key === 'description') return undefined; - if (key === 'is_random') return undefined; - if (key === 'is_private') return undefined; - if (value === 'true') return true; - if (value === 'false') return false; - if (value === '') return undefined; - return value; + if (key === 'scenario') return undefined + if (key === 'description') return undefined + if (key === 'is_random') return undefined + if (key === 'is_private') return undefined + if (value === 'true') return true + if (value === 'false') return false + if (value === '') return undefined + return value } app.post('/create/:title_id', must_be_logged_in, function (req, res) { - let title_id = req.params.title_id; - let descr = req.body.description; - let priv = req.body.is_private === 'true'; - let rand = req.body.is_random === 'true'; - let user_id = req.user.user_id; - let scenario = req.body.scenario; - let options = JSON.stringify(req.body, options_json_replacer); - let count = SQL_COUNT_OPEN_GAMES.get(user_id); + let title_id = req.params.title_id + let descr = req.body.description + let priv = req.body.is_private === 'true' + let rand = req.body.is_random === 'true' + let user_id = req.user.user_id + let scenario = req.body.scenario + let options = JSON.stringify(req.body, options_json_replacer) + let count = SQL_COUNT_OPEN_GAMES.get(user_id) if (count >= 5) - return res.send("You have too many open games!"); + return res.send("You have too many open games!") if (!(title_id in RULES)) - return res.send("Invalid title."); + return res.send("Invalid title.") if (!RULES[title_id].scenarios.includes(scenario)) - return res.send("Invalid scenario."); - let info = SQL_INSERT_GAME.run(user_id, title_id, scenario, options, priv ? 1 : 0, rand ? 1 : 0, descr); - res.redirect('/join/'+info.lastInsertRowid); -}); + return res.send("Invalid scenario.") + let info = SQL_INSERT_GAME.run(user_id, title_id, scenario, options, priv ? 1 : 0, rand ? 1 : 0, descr) + res.redirect('/join/'+info.lastInsertRowid) +}) app.get('/delete/:game_id', must_be_logged_in, function (req, res) { - let game_id = req.params.game_id; - let title_id = SQL_SELECT_GAME_TITLE.get(game_id); - let info = SQL_DELETE_GAME.run(game_id, req.user.user_id); + let game_id = req.params.game_id + let title_id = SQL_SELECT_GAME_TITLE.get(game_id) + let info = SQL_DELETE_GAME.run(game_id, req.user.user_id) if (info.changes === 0) - return res.send("Not authorized to delete that game ID."); + return res.send("Not authorized to delete that game ID.") if (info.changes === 1) - update_join_clients_deleted(game_id); - res.redirect('/'+title_id); -}); + update_join_clients_deleted(game_id) + res.redirect('/'+title_id) +}) function join_rematch(req, res, game_id, role) { try { - let is_random = SQL_SELECT_GAME_RANDOM.get(game_id); + let is_random = SQL_SELECT_GAME_RANDOM.get(game_id) if (is_random) { - let role = SQL_SELECT_PLAYER_ROLE.get(game_id, req.user.user_id); + let role = SQL_SELECT_PLAYER_ROLE.get(game_id, req.user.user_id) if (!role) { for (let i = 1; i <= 6; ++i) { - let info = SQL_INSERT_PLAYER_ROLE.run(game_id, 'Random ' + i, req.user.user_id); + let info = SQL_INSERT_PLAYER_ROLE.run(game_id, 'Random ' + i, req.user.user_id) if (info.changes === 1) { - update_join_clients_players(game_id); - break; + update_join_clients_players(game_id) + break } } } } else { - let info = SQL_INSERT_PLAYER_ROLE.run(game_id, role, req.user.user_id); + let info = SQL_INSERT_PLAYER_ROLE.run(game_id, role, req.user.user_id) if (info.changes === 1) - update_join_clients_players(game_id); + update_join_clients_players(game_id) } } catch (err) { - console.log(err); + console.log(err) } - return res.redirect('/join/'+game_id); + return res.redirect('/join/'+game_id) } app.get('/rematch/:old_game_id/:role', must_be_logged_in, function (req, res) { - let old_game_id = req.params.old_game_id | 0; - let role = req.params.role; - let magic = "\u{1F503} " + old_game_id; - let new_game_id = 0; - let info = SQL_INSERT_REMATCH.run({user_id: req.user.user_id, game_id: old_game_id, magic: magic}); + let old_game_id = req.params.old_game_id | 0 + let role = req.params.role + let magic = "\u{1F503} " + old_game_id + let new_game_id = 0 + let info = SQL_INSERT_REMATCH.run({user_id: req.user.user_id, game_id: old_game_id, magic: magic}) if (info.changes === 1) - new_game_id = info.lastInsertRowid; + new_game_id = info.lastInsertRowid else - new_game_id = SQL_SELECT_REMATCH.get(magic); + new_game_id = SQL_SELECT_REMATCH.get(magic) if (new_game_id) - return join_rematch(req, res, new_game_id, role); - return res.status(404).send("Can't create or find rematch game!"); -}); + return join_rematch(req, res, new_game_id, role) + return res.status(404).send("Can't create or find rematch game!") +}) -let join_clients = {}; +let join_clients = {} function update_join_clients_deleted(game_id) { - let list = join_clients[game_id]; + let list = join_clients[game_id] if (list && list.length > 0) { for (let {res} of list) { - res.write("retry: 15000\n"); - res.write("event: deleted\n"); - res.write("data: The game doesn't exist.\n\n"); - res.flush(); + res.write("retry: 15000\n") + res.write("event: deleted\n") + res.write("data: The game doesn't exist.\n\n") + res.flush() } } } function update_join_clients_game(game_id) { - let list = join_clients[game_id]; + let list = join_clients[game_id] if (list && list.length > 0) { - let game = SQL_SELECT_GAME_VIEW.get(game_id); + let game = SQL_SELECT_GAME_VIEW.get(game_id) for (let {res} of list) { - res.write("retry: 15000\n"); - res.write("event: game\n"); - res.write("data: " + JSON.stringify(game) + "\n\n"); - res.flush(); + res.write("retry: 15000\n") + res.write("event: game\n") + res.write("data: " + JSON.stringify(game) + "\n\n") + res.flush() } } } function update_join_clients_players(game_id) { - let list = join_clients[game_id]; + let list = join_clients[game_id] if (list && list.length > 0) { - let players = SQL_SELECT_PLAYERS_JOIN.all(game_id); - let ready = RULES[list.title_id].ready(list.scenario, list.options, players); + let players = SQL_SELECT_PLAYERS_JOIN.all(game_id) + let ready = RULES[list.title_id].ready(list.scenario, list.options, players) for (let {res} of list) { - res.write("retry: 15000\n"); - res.write("event: players\n"); - res.write("data: " + JSON.stringify(players) + "\n\n"); - res.write("event: ready\n"); - res.write("data: " + ready + "\n\n"); - res.flush(); + res.write("retry: 15000\n") + res.write("event: players\n") + res.write("data: " + JSON.stringify(players) + "\n\n") + res.write("event: ready\n") + res.write("data: " + ready + "\n\n") + res.flush() } } } app.get('/join/:game_id', must_be_logged_in, function (req, res) { - let game_id = req.params.game_id | 0; - let game = SQL_SELECT_GAME_VIEW.get(game_id); + let game_id = req.params.game_id | 0 + let game = SQL_SELECT_GAME_VIEW.get(game_id) if (!game) - return res.status(404).send("Invalid game ID."); - annotate_game(game, req.user.user_id); - let roles = get_game_roles(game.title_id, game.scenario, game.options); - let players = SQL_SELECT_PLAYERS_JOIN.all(game_id); - let ready = (game.status === 0) && RULES[game.title_id].ready(game.scenario, game.options, players); + return res.status(404).send("Invalid game ID.") + annotate_game(game, req.user.user_id) + let roles = get_game_roles(game.title_id, game.scenario, game.options) + let players = SQL_SELECT_PLAYERS_JOIN.all(game_id) + let ready = (game.status === 0) && RULES[game.title_id].ready(game.scenario, game.options, players) res.render('join.pug', { user: req.user, game: game, roles: roles, players: players, ready: ready, - }); -}); + }) +}) app.get('/join-events/:game_id', must_be_logged_in, function (req, res) { - let game_id = req.params.game_id | 0; - let game = SQL_SELECT_GAME_VIEW.get(game_id); - let players = SQL_SELECT_PLAYERS_JOIN.all(game_id); + let game_id = req.params.game_id | 0 + let game = SQL_SELECT_GAME_VIEW.get(game_id) + let players = SQL_SELECT_PLAYERS_JOIN.all(game_id) - res.setHeader("Content-Type", "text/event-stream"); - res.setHeader("Connection", "keep-alive"); + res.setHeader("Content-Type", "text/event-stream") + res.setHeader("Connection", "keep-alive") if (!game) { - return res.send("event: deleted\ndata: The game doesn't exist.\n\n"); + return res.send("event: deleted\ndata: The game doesn't exist.\n\n") } if (!(game_id in join_clients)) { - join_clients[game_id] = []; - join_clients[game_id].title_id = game.title_id; - join_clients[game_id].scenario = game.scenario; - join_clients[game_id].options = JSON.parse(game.options); + join_clients[game_id] = [] + join_clients[game_id].title_id = game.title_id + join_clients[game_id].scenario = game.scenario + join_clients[game_id].options = JSON.parse(game.options) } - join_clients[game_id].push({ res: res, user_id: req.user.user_id}); + join_clients[game_id].push({ res: res, user_id: req.user.user_id}) res.on('close', () => { - let list = join_clients[game_id]; - let i = list.findIndex(item => item.res === res); + let list = join_clients[game_id] + let i = list.findIndex(item => item.res === res) if (i >= 0) - list.splice(i, 1); - }); + list.splice(i, 1) + }) - res.write("retry: 15000\n\n"); - res.write("event: game\n"); - res.write("data: " + JSON.stringify(game) + "\n\n"); - res.write("event: players\n"); - res.write("data: " + JSON.stringify(players) + "\n\n"); - res.flush(); -}); + res.write("retry: 15000\n\n") + res.write("event: game\n") + res.write("data: " + JSON.stringify(game) + "\n\n") + res.write("event: players\n") + res.write("data: " + JSON.stringify(players) + "\n\n") + res.flush() +}) app.post('/join/:game_id/:role', must_be_logged_in, function (req, res) { - let game_id = req.params.game_id | 0; - let role = req.params.role; - let game = SQL_SELECT_GAME.get(game_id); - let roles = get_game_roles(game.title_id, game.scenario, game.options); + let game_id = req.params.game_id | 0 + let role = req.params.role + let game = SQL_SELECT_GAME.get(game_id) + let roles = get_game_roles(game.title_id, game.scenario, game.options) if (game.is_random && game.status === 0) { - let m = role.match(/^Random (\d+)$/); + let m = role.match(/^Random (\d+)$/) if (!m || Number(m[1]) < 1 || Number(m[1]) > roles.length) - return res.status(404).send("Invalid role."); + return res.status(404).send("Invalid role.") } else { if (!roles.includes(role)) - return res.status(404).send("Invalid role."); + return res.status(404).send("Invalid role.") } - let info = SQL_INSERT_PLAYER_ROLE.run(game_id, role, req.user.user_id); + let info = SQL_INSERT_PLAYER_ROLE.run(game_id, role, req.user.user_id) if (info.changes === 1) { - update_join_clients_players(game_id); - res.send("SUCCESS"); + update_join_clients_players(game_id) + res.send("SUCCESS") } else { - res.send("Could not join game."); + res.send("Could not join game.") } -}); +}) app.post('/part/:game_id/:role', must_be_logged_in, function (req, res) { - let game_id = req.params.game_id | 0; - let role = req.params.role; - SQL_DELETE_PLAYER_ROLE.run(game_id, role); - update_join_clients_players(game_id); - res.send("SUCCESS"); -}); + let game_id = req.params.game_id | 0 + let role = req.params.role + SQL_DELETE_PLAYER_ROLE.run(game_id, role) + update_join_clients_players(game_id) + res.send("SUCCESS") +}) function assign_random_roles(game, players) { function pick_random_item(list) { - let k = crypto.randomInt(list.length); - let r = list[k]; - list.splice(k, 1); - return r; + let k = crypto.randomInt(list.length) + let r = list[k] + list.splice(k, 1) + return r } - let roles = get_game_roles(game.title_id, game.scenario, game.options).slice(); + let roles = get_game_roles(game.title_id, game.scenario, game.options).slice() for (let p of players) { - let old_role = p.role; - p.role = pick_random_item(roles); - console.log("ASSIGN ROLE", "(" + p.name + ")", old_role, "->", p.role); - SQL_UPDATE_PLAYER_ROLE.run(p.role, game.game_id, old_role, p.user_id); + let old_role = p.role + p.role = pick_random_item(roles) + console.log("ASSIGN ROLE", "(" + p.name + ")", old_role, "->", p.role) + SQL_UPDATE_PLAYER_ROLE.run(p.role, game.game_id, old_role, p.user_id) } } function start_game(game_id, game) { - let players = SQL_SELECT_PLAYERS.all(game_id); + let players = SQL_SELECT_PLAYERS.all(game_id) if (!RULES[game.title_id].ready(game.scenario, game.options, players)) - return res.send("Invalid scenario/options/player configuration!"); + return res.send("Invalid scenario/options/player configuration!") if (game.is_random) { - assign_random_roles(game, players); - players = SQL_SELECT_PLAYERS.all(game_id); - update_join_clients_players(game_id); + assign_random_roles(game, players) + players = SQL_SELECT_PLAYERS.all(game_id) + update_join_clients_players(game_id) } - let options = game.options ? JSON.parse(game.options) : {}; - let seed = random_seed(); - let state = RULES[game.title_id].setup(seed, game.scenario, options, players); - put_replay(game_id, null, 'setup', [seed, game.scenario, options, players]); - SQL_UPDATE_GAME_RESULT.run(1, null, game_id); - SQL_UPDATE_GAME_STATE.run(game_id, JSON.stringify(state), state.active); + let options = game.options ? JSON.parse(game.options) : {} + let seed = random_seed() + let state = RULES[game.title_id].setup(seed, game.scenario, options, players) + put_replay(game_id, null, 'setup', [seed, game.scenario, options, players]) + SQL_UPDATE_GAME_RESULT.run(1, null, game_id) + SQL_UPDATE_GAME_STATE.run(game_id, JSON.stringify(state), state.active) if (is_solo(players)) - SQL_UPDATE_GAME_PRIVATE.run(game_id); - update_join_clients_game(game_id); - mail_game_started_notification_to_offline_users(game_id, game.owner_id); - mail_your_turn_notification_to_offline_users(game_id, null, state.active); + SQL_UPDATE_GAME_PRIVATE.run(game_id) + update_join_clients_game(game_id) + mail_game_started_notification_to_offline_users(game_id, game.owner_id) + mail_your_turn_notification_to_offline_users(game_id, null, state.active) } app.post('/start/:game_id', must_be_logged_in, function (req, res) { - let game_id = req.params.game_id | 0; - let game = SQL_SELECT_GAME.get(game_id); + let game_id = req.params.game_id | 0 + let game = SQL_SELECT_GAME.get(game_id) if (game.owner_id !== req.user.user_id) - return res.send("Not authorized to start that game ID."); + return res.send("Not authorized to start that game ID.") if (game.status !== 0) - return res.send("The game is already started."); - start_game(game_id, game); - res.send("SUCCESS"); -}); + return res.send("The game is already started.") + start_game(game_id, game) + res.send("SUCCESS") +}) app.get('/play/:game_id/:role', function (req, res) { - let game_id = req.params.game_id | 0; - let role = req.params.role; - let title = SQL_SELECT_GAME_TITLE.get(game_id); + let game_id = req.params.game_id | 0 + let role = req.params.role + let title = SQL_SELECT_GAME_TITLE.get(game_id) if (!title) - return res.status(404).send("Invalid game ID."); - res.redirect('/'+title+'/play:'+game_id+':'+role); -}); + return res.status(404).send("Invalid game ID.") + res.redirect('/'+title+'/play:'+game_id+':'+role) +}) app.get('/play/:game_id', function (req, res) { - let game_id = req.params.game_id | 0; - let user_id = req.user ? req.user.user_id : 0; - let title = SQL_SELECT_GAME_TITLE.get(game_id); + let game_id = req.params.game_id | 0 + let user_id = req.user ? req.user.user_id : 0 + let title = SQL_SELECT_GAME_TITLE.get(game_id) if (!title) - return res.status(404).send("Invalid game ID."); - let role = SQL_SELECT_PLAYER_ROLE.get(game_id, user_id); + return res.status(404).send("Invalid game ID.") + let role = SQL_SELECT_PLAYER_ROLE.get(game_id, user_id) if (role) - res.redirect('/'+title+'/play:'+game_id+':'+role); + res.redirect('/'+title+'/play:'+game_id+':'+role) else - res.redirect('/'+title+'/play:'+game_id); -}); + res.redirect('/'+title+'/play:'+game_id) +}) app.get('/:title_id/play\::game_id\::role', must_be_logged_in, function (req, res) { - let user_id = req.user ? req.user.user_id : 0; - let title_id = req.params.title_id; - let game_id = req.params.game_id; - let role = req.params.role; + let user_id = req.user ? req.user.user_id : 0 + let title_id = req.params.title_id + let game_id = req.params.game_id + let role = req.params.role if (!SQL_AUTHORIZE_GAME_ROLE.get(title_id, game_id, role, user_id)) - return res.status(404).send("Invalid game ID."); - return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); -}); + return res.status(404).send("Invalid game ID.") + return res.sendFile(__dirname + '/public/' + title_id + '/play.html') +}) app.get('/:title_id/play\::game_id', function (req, res) { - let title_id = req.params.title_id; - let game_id = req.params.game_id; - let a_title = SQL_SELECT_GAME_TITLE.get(game_id); + let title_id = req.params.title_id + let game_id = req.params.game_id + let a_title = SQL_SELECT_GAME_TITLE.get(game_id) if (a_title !== title_id) - return res.status(404).send("Invalid game ID."); - return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); -}); + return res.status(404).send("Invalid game ID.") + return res.sendFile(__dirname + '/public/' + title_id + '/play.html') +}) app.get('/:title_id/replay\::game_id', function (req, res) { - let title_id = req.params.title_id; - let game_id = req.params.game_id; - let game = SQL_SELECT_GAME.get(game_id); + let title_id = req.params.title_id + let game_id = req.params.game_id + let game = SQL_SELECT_GAME.get(game_id) if (!game) - return res.status(404).send("Invalid game ID."); + return res.status(404).send("Invalid game ID.") if (game.title_id !== title_id) - return res.status(404).send("Invalid game ID."); + return res.status(404).send("Invalid game ID.") if (game.status < 2) - return res.status(404).send("Invalid game ID."); - return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); -}); + return res.status(404).send("Invalid game ID.") + return res.sendFile(__dirname + '/public/' + title_id + '/play.html') +}) app.get('/replay/:game_id', function (req, res) { - let game_id = req.params.game_id; - let game = SQL_SELECT_GAME.get(game_id); + let game_id = req.params.game_id + let game = SQL_SELECT_GAME.get(game_id) if (game.status < 2) - return res.status(404).send("Invalid game ID."); - let players = SQL_SELECT_PLAYERS_JOIN.all(game_id); - let state = SQL_SELECT_GAME_STATE.get(game_id); - let replay = SQL_SELECT_REPLAY.all(game_id); - return res.json({players, state, replay}); -}); + return res.status(404).send("Invalid game ID.") + let players = SQL_SELECT_PLAYERS_JOIN.all(game_id) + let state = SQL_SELECT_GAME_STATE.get(game_id) + let replay = SQL_SELECT_REPLAY.all(game_id) + return res.json({players, state, replay}) +}) /* * MAIL NOTIFICATIONS */ -const MAIL_FROM = process.env.MAIL_FROM || "user@localhost"; -const MAIL_FOOTER = "\n--\nYou can unsubscribe from notifications on your profile page:\n" + SITE_URL + "/profile\n"; +const MAIL_FROM = process.env.MAIL_FROM || "user@localhost" +const MAIL_FOOTER = "\n--\nYou can unsubscribe from notifications on your profile page:\n" + SITE_URL + "/profile\n" -const SQL_SELECT_NOTIFIED = SQL("SELECT datetime('now') < datetime(time,?) FROM last_notified WHERE game_id=? AND user_id=?").pluck(); -const SQL_INSERT_NOTIFIED = SQL("INSERT OR REPLACE INTO last_notified (game_id,user_id,time) VALUES (?,?,datetime('now'))"); -const SQL_DELETE_NOTIFIED = SQL("DELETE FROM last_notified WHERE game_id=? AND user_id=?"); +const SQL_SELECT_NOTIFIED = SQL("SELECT datetime('now') < datetime(time,?) FROM last_notified WHERE game_id=? AND user_id=?").pluck() +const SQL_INSERT_NOTIFIED = SQL("INSERT OR REPLACE INTO last_notified (game_id,user_id,time) VALUES (?,?,datetime('now'))") +const SQL_DELETE_NOTIFIED = SQL("DELETE FROM last_notified WHERE game_id=? AND user_id=?") -const QUERY_LIST_YOUR_TURN = SQL("SELECT * FROM your_turn_reminder"); +const QUERY_LIST_YOUR_TURN = SQL("SELECT * FROM your_turn_reminder") function mail_callback(err, info) { if (err) - console.log("MAIL ERROR", err); + console.log("MAIL ERROR", err) } function mail_addr(user) { - return user.name + " <" + user.mail + ">"; + return user.name + " <" + user.mail + ">" } function mail_game_info(game) { - let desc = `Game: ${game.title_name}\n`; - desc += `Scenario: ${game.scenario}\n`; - desc += `Players: ${game.player_names}\n`; + let desc = `Game: ${game.title_name}\n` + desc += `Scenario: ${game.scenario}\n` + desc += `Players: ${game.player_names}\n` if (game.description.length > 0) - desc += `Description: ${game.description}\n`; - return desc + "\n"; + desc += `Description: ${game.description}\n` + return desc + "\n" } function mail_game_link(game_id, user) { - return SITE_URL + "/play/" + game_id + "/" + encodeURI(user.role) + "\n"; + return SITE_URL + "/play/" + game_id + "/" + encodeURI(user.role) + "\n" } function mail_password_reset_token(user, token) { if (mailer) { - let subject = "Password reset request"; + let subject = "Password reset request" let body = "Your password reset token is: " + token + "\n\n" + SITE_URL + "/reset-password/" + user.mail + "/" + token + "\n\n" + - "If you did not request a password reset you can ignore this mail.\n"; - console.log("SENT MAIL:", mail_addr(user), subject); - mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback); + "If you did not request a password reset you can ignore this mail.\n" + console.log("SENT MAIL:", mail_addr(user), subject) + mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback) } } function mail_new_message(user, msg_id, msg_from) { if (mailer) { - let subject = "You have a new message from " + msg_from + "."; + let subject = "You have a new message from " + msg_from + "." let body = "Read the message here:\n" + SITE_URL + "/message/read/" + msg_id + "\n" + - MAIL_FOOTER; - console.log("SENT MAIL:", mail_addr(user), subject); - mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback); + MAIL_FOOTER + console.log("SENT MAIL:", mail_addr(user), subject) + mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback) } } function mail_game_started_notification(user, game_id) { if (mailer) { - let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id); - let subject = `${game.title_name} #${game_id} (${user.role}) - Started!`; + let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id) + let subject = `${game.title_name} #${game_id} (${user.role}) - Started!` let body = mail_game_info(game) + "The game has started!\n\n" + mail_game_link(game_id, user) + - MAIL_FOOTER; - console.log("SENT MAIL:", mail_addr(user), subject); - mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback); + MAIL_FOOTER + console.log("SENT MAIL:", mail_addr(user), subject) + mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback) } } function mail_game_over_notification(user, game_id, result, victory) { if (mailer) { - let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id); - let subject = `${game.title_name} #${game_id} (${user.role}) - Finished!`; + let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id) + let subject = `${game.title_name} #${game_id} (${user.role}) - Finished!` let body = mail_game_info(game) + victory + "\n\n" + mail_game_link(game_id, user) + - MAIL_FOOTER; - console.log("SENT MAIL:", mail_addr(user), subject); - mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback); + MAIL_FOOTER + console.log("SENT MAIL:", mail_addr(user), subject) + mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback) } } function mail_your_turn_notification(user, game_id, interval) { if (mailer) { - let too_soon = SQL_SELECT_NOTIFIED.get(interval, game_id, user.user_id); + let too_soon = SQL_SELECT_NOTIFIED.get(interval, game_id, user.user_id) if (!too_soon) { - SQL_INSERT_NOTIFIED.run(game_id, user.user_id); - let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id); - let subject = `${game.title_name} #${game_id} (${user.role}) - Your turn!`; + SQL_INSERT_NOTIFIED.run(game_id, user.user_id) + let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id) + let subject = `${game.title_name} #${game_id} (${user.role}) - Your turn!` let body = mail_game_info(game) + "It's your turn.\n\n" + mail_game_link(game_id, user) + - MAIL_FOOTER; - console.log("SENT MAIL:", mail_addr(user), subject); - mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback); + MAIL_FOOTER + console.log("SENT MAIL:", mail_addr(user), subject) + mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback) } } } function reset_your_turn_notification(user, game_id) { - SQL_DELETE_NOTIFIED.run(game_id, user.user_id); + SQL_DELETE_NOTIFIED.run(game_id, user.user_id) } function mail_ready_to_start_notification(user, game_id, interval) { if (mailer) { - let too_soon = SQL_SELECT_NOTIFIED.get(interval, game_id, user.user_id); + let too_soon = SQL_SELECT_NOTIFIED.get(interval, game_id, user.user_id) if (!too_soon) { - SQL_INSERT_NOTIFIED.run(game_id, user.user_id); - let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id); - let subject = `${game.title_name} #${game_id} - Ready to start!`; + SQL_INSERT_NOTIFIED.run(game_id, user.user_id) + let game = SQL_SELECT_GAME_FULL_VIEW.get(game_id) + let subject = `${game.title_name} #${game_id} - Ready to start!` let body = mail_game_info(game) + "Your game is ready to start.\n\n" + SITE_URL + "/join/" + game_id + "\n" + - MAIL_FOOTER; - console.log("SENT MAIL:", mail_addr(user), subject); - mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback); + MAIL_FOOTER + console.log("SENT MAIL:", mail_addr(user), subject) + mailer.sendMail({ from: MAIL_FROM, to: mail_addr(user), subject: subject, text: body }, mail_callback) } } } @@ -1624,390 +1624,410 @@ function mail_ready_to_start_notification(user, game_id, interval) { function mail_your_turn_notification_to_offline_users(game_id, old_active, active) { // Only send notifications when the active player changes. if (old_active === active) - return; + return - let players = SQL_SELECT_PLAYERS.all(game_id); + let players = SQL_SELECT_PLAYERS.all(game_id) for (let p of players) { if (p.notify) { - let p_was_active = (old_active === p.role || old_active === 'Both' || old_active === 'All'); - let p_is_active = (active === p.role || active === 'Both' || active === 'All'); + let p_was_active = (old_active === p.role || old_active === 'Both' || old_active === 'All') + let p_is_active = (active === p.role || active === 'Both' || active === 'All') if (!p_was_active && p_is_active) { if (is_online(game_id, p.user_id)) { - reset_your_turn_notification(p, game_id); + reset_your_turn_notification(p, game_id) } else { - mail_your_turn_notification(p, game_id, '+15 minutes'); + mail_your_turn_notification(p, game_id, '+15 minutes') } } else { - reset_your_turn_notification(p, game_id); + reset_your_turn_notification(p, game_id) } } } } function mail_game_started_notification_to_offline_users(game_id, owner_id) { - let players = SQL_SELECT_PLAYERS.all(game_id); + let players = SQL_SELECT_PLAYERS.all(game_id) for (let p of players) if (p.notify && !is_online(game_id, p.user_id)) - mail_game_started_notification(p, game_id); + mail_game_started_notification(p, game_id) } function mail_game_over_notification_to_offline_users(game_id, result, victory) { - let players = SQL_SELECT_PLAYERS.all(game_id); + let players = SQL_SELECT_PLAYERS.all(game_id) for (let p of players) if (p.notify && !is_online(game_id, p.user_id)) - mail_game_over_notification(p, game_id, result, victory); + mail_game_over_notification(p, game_id, result, victory) } function notify_your_turn_reminder() { for (let item of QUERY_LIST_YOUR_TURN.all()) { - mail_your_turn_notification(item, item.game_id, '+25 hours'); + mail_your_turn_notification(item, item.game_id, '+25 hours') } } function notify_ready_to_start_reminder() { for (let game of SQL_SELECT_OPEN_GAMES.all()) { - let players = SQL_SELECT_PLAYERS.all(game.game_id); - let rules = RULES[game.title_id]; + let players = SQL_SELECT_PLAYERS.all(game.game_id) + let rules = RULES[game.title_id] if (rules && rules.ready(game.scenario, game.options, players)) { - let owner = SQL_OFFLINE_USER.get(game.owner_id, '+3 minutes'); + let owner = SQL_OFFLINE_USER.get(game.owner_id, '+3 minutes') if (owner) { if (owner.notify) - mail_ready_to_start_notification(owner, game.game_id, '+25 hours'); + mail_ready_to_start_notification(owner, game.game_id, '+25 hours') } } } } // Check and send daily 'your turn' reminders every 15 minutes. -setInterval(notify_your_turn_reminder, 15 * 60 * 1000); +setInterval(notify_your_turn_reminder, 15 * 60 * 1000) // Check and send ready to start notifications every 5 minutes. -setInterval(notify_ready_to_start_reminder, 5 * 60 * 1000); +setInterval(notify_ready_to_start_reminder, 5 * 60 * 1000) /* * GAME SERVER */ -let clients = {}; +let clients = {} function is_online(game_id, user_id) { if (clients[game_id]) for (let other of clients[game_id]) if (other.user && other.user.user_id === user_id) - return true; + return true if (join_clients[game_id]) for (let other of join_clients[game_id]) if (other.user_id === user_id) - return true; - return false; + return true + return false } function send_message(socket, cmd, arg) { - socket.send(JSON.stringify([cmd, arg])); + socket.send(JSON.stringify([cmd, arg])) } function send_state(socket, state) { try { - let view = socket.rules.view(state, socket.role); + let view = socket.rules.view(state, socket.role) if (socket.seen < view.log.length) - view.log_start = socket.seen; + view.log_start = socket.seen else - view.log_start = view.log.length; - socket.seen = view.log.length; - view.log = view.log.slice(view.log_start); + view.log_start = view.log.length + socket.seen = view.log.length + view.log = view.log.slice(view.log_start) if (state.state === 'game_over') - view.game_over = 1; - view = JSON.stringify(['state', view]); + view.game_over = 1 + view = JSON.stringify(['state', view]) if (socket.last_view !== view) { - socket.send(view); - socket.last_view = view; + socket.send(view) + socket.last_view = view } } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function get_game_state(game_id) { - let game_state = SQL_SELECT_GAME_STATE.get(game_id); + let game_state = SQL_SELECT_GAME_STATE.get(game_id) if (!game_state) - throw new Error("No game with that ID"); - return JSON.parse(game_state); + throw new Error("No game with that ID") + return JSON.parse(game_state) } function put_game_state(game_id, state, old_active) { if (state.state === 'game_over') { - SQL_UPDATE_GAME_RESULT.run(2, state.result, game_id); - mail_game_over_notification_to_offline_users(game_id, state.result, state.victory); + SQL_UPDATE_GAME_RESULT.run(2, state.result, game_id) + mail_game_over_notification_to_offline_users(game_id, state.result, state.victory) } - SQL_UPDATE_GAME_STATE.run(game_id, JSON.stringify(state), state.active); + SQL_UPDATE_GAME_STATE.run(game_id, JSON.stringify(state), state.active) for (let other of clients[game_id]) - send_state(other, state); - update_join_clients_game(game_id); - mail_your_turn_notification_to_offline_users(game_id, old_active, state.active); + send_state(other, state) + update_join_clients_game(game_id) + mail_your_turn_notification_to_offline_users(game_id, old_active, state.active) } function put_replay(game_id, role, action, args) { if (args !== undefined && args !== null) - args = JSON.stringify(args); - SQL_INSERT_REPLAY.run(game_id, role, action, args); + args = JSON.stringify(args) + SQL_INSERT_REPLAY.run(game_id, role, action, args) } function on_action(socket, action, arg) { if (arg !== undefined) - SLOG(socket, "ACTION", action, JSON.stringify(arg)); + SLOG(socket, "ACTION", action, JSON.stringify(arg)) else - SLOG(socket, "ACTION", action); + SLOG(socket, "ACTION", action) try { - let state = get_game_state(socket.game_id); - let old_active = state.active; - state = socket.rules.action(state, socket.role, action, arg); - put_game_state(socket.game_id, state, old_active); - put_replay(socket.game_id, socket.role, action, arg); + let state = get_game_state(socket.game_id) + let old_active = state.active + state = socket.rules.action(state, socket.role, action, arg) + put_game_state(socket.game_id, state, old_active) + put_replay(socket.game_id, socket.role, action, arg) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function on_query(socket, q) { - let params = undefined; + let params = undefined if (Array.isArray(q)) { - params = q[1]; - q = q[0]; + params = q[1] + q = q[0] } if (params !== undefined) - SLOG(socket, "QUERY", q, JSON.stringify(params)); + SLOG(socket, "QUERY", q, JSON.stringify(params)) else - SLOG(socket, "QUERY", q); + SLOG(socket, "QUERY", q) try { if (socket.rules.query) { - let state = get_game_state(socket.game_id); - let reply = socket.rules.query(state, socket.role, q, params); - send_message(socket, 'reply', [q, reply]); + let state = get_game_state(socket.game_id) + let reply = socket.rules.query(state, socket.role, q, params) + send_message(socket, 'reply', [q, reply]) } } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function on_resign(socket) { - SLOG(socket, "RESIGN"); + SLOG(socket, "RESIGN") try { - let state = get_game_state(socket.game_id); - let old_active = state.active; + let state = get_game_state(socket.game_id) + let old_active = state.active // TODO: shared "resign" function - state = socket.rules.resign(state, socket.role); - put_game_state(socket.game_id, state, old_active); - put_replay(socket.game_id, socket.role, 'resign', null); + state = socket.rules.resign(state, socket.role) + put_game_state(socket.game_id, state, old_active) + put_replay(socket.game_id, socket.role, 'resign', null) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function on_getchat(socket, seen) { try { - let chat = SQL_SELECT_GAME_CHAT.all(socket.game_id, seen); + let chat = SQL_SELECT_GAME_CHAT.all(socket.game_id, seen) if (chat.length > 0) - SLOG(socket, "GETCHAT", seen, chat.length); + SLOG(socket, "GETCHAT", seen, chat.length) for (let i = 0; i < chat.length; ++i) - send_message(socket, 'chat', chat[i]); + send_message(socket, 'chat', chat[i]) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function on_chat(socket, message) { - message = message.substring(0,4000); + message = message.substring(0,4000) try { - let chat = SQL_INSERT_GAME_CHAT.get(socket.game_id, socket.user.user_id, message); - chat[2] = socket.user.name; - SLOG(socket, "CHAT"); + let chat = SQL_INSERT_GAME_CHAT.get(socket.game_id, socket.user.user_id, message) + chat[2] = socket.user.name + SLOG(socket, "CHAT") for (let other of clients[socket.game_id]) if (other.role !== "Observer") - send_message(other, 'chat', chat); + send_message(other, 'chat', chat) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function on_debug(socket) { if (!DEBUG) - send_message(socket, 'error', "Debugging is not enabled on this server."); - SLOG(socket, "DEBUG"); + send_message(socket, 'error', "Debugging is not enabled on this server.") + SLOG(socket, "DEBUG") try { - let game_state = SQL_SELECT_GAME_STATE.get(socket.game_id); + let game_state = SQL_SELECT_GAME_STATE.get(socket.game_id) if (!game_state) - return send_message(socket, 'error', "No game with that ID."); - send_message(socket, 'debug', game_state); + return send_message(socket, 'error', "No game with that ID.") + send_message(socket, 'debug', game_state) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function on_save(socket) { if (!DEBUG) - send_message(socket, 'error', "Debugging is not enabled on this server."); - SLOG(socket, "SAVE"); + send_message(socket, 'error', "Debugging is not enabled on this server.") + SLOG(socket, "SAVE") try { - let game_state = SQL_SELECT_GAME_STATE.get(socket.game_id); + let game_state = SQL_SELECT_GAME_STATE.get(socket.game_id) if (!game_state) - return send_message(socket, 'error', "No game with that ID."); - send_message(socket, 'save', game_state); + return send_message(socket, 'error', "No game with that ID.") + send_message(socket, 'save', game_state) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function on_restore(socket, state_text) { if (!DEBUG) - send_message(socket, 'error', "Debugging is not enabled on this server."); - SLOG(socket, "RESTORE"); + send_message(socket, 'error', "Debugging is not enabled on this server.") + SLOG(socket, "RESTORE") try { - let state = JSON.parse(state_text); - state.seed = random_seed(); // reseed! - state_text = JSON.stringify(state); - SQL_UPDATE_GAME_RESULT.run(1, null, socket.game_id); - SQL_UPDATE_GAME_STATE.run(socket.game_id, state_text, state.active); - put_replay(socket.game_id, null, 'restore', state_text); + let state = JSON.parse(state_text) + state.seed = random_seed() // reseed! + state_text = JSON.stringify(state) + SQL_UPDATE_GAME_RESULT.run(1, null, socket.game_id) + SQL_UPDATE_GAME_STATE.run(socket.game_id, state_text, state.active) + put_replay(socket.game_id, null, 'restore', state_text) for (let other of clients[socket.game_id]) - send_state(other, state); + send_state(other, state) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function broadcast_presence(game_id) { - let presence = {}; + let presence = {} for (let socket of clients[game_id]) - presence[socket.role] = true; + presence[socket.role] = true for (let socket of clients[game_id]) - send_message(socket, 'presence', presence); + send_message(socket, 'presence', presence) } function on_restart(socket, scenario) { if (!DEBUG) - send_message(socket, 'error', "Debugging is not enabled on this server."); + send_message(socket, 'error', "Debugging is not enabled on this server.") try { - let seed = random_seed(); - let options = JSON.parse(SQL_SELECT_GAME.get(socket.game_id).options); - let state = socket.rules.setup(seed, scenario, options, socket.players); - put_replay(socket.game_id, null, 'setup', [seed, scenario, options, socket.players]); + let seed = random_seed() + let options = JSON.parse(SQL_SELECT_GAME.get(socket.game_id).options) + let state = socket.rules.setup(seed, scenario, options, socket.players) + put_replay(socket.game_id, null, 'setup', [seed, scenario, options, socket.players]) for (let other of clients[socket.game_id]) { - other.seen = 0; - send_state(other, state); + other.seen = 0 + send_state(other, state) } - let state_text = JSON.stringify(state); - SQL_UPDATE_GAME_RESULT.run(1, null, socket.game_id); - SQL_UPDATE_GAME_STATE.run(socket.game_id, state_text, state.active); + let state_text = JSON.stringify(state) + SQL_UPDATE_GAME_RESULT.run(1, null, socket.game_id) + SQL_UPDATE_GAME_STATE.run(socket.game_id, state_text, state.active) } catch (err) { - console.log(err); - return send_message(socket, 'error', err.toString()); + console.log(err) + return send_message(socket, 'error', err.toString()) } } function handle_player_message(socket, cmd, arg) { switch (cmd) { - case 'action': on_action(socket, arg[0], arg[1]); break; - case 'query': on_query(socket, arg); break; - case 'resign': on_resign(socket); break; - case 'getchat': on_getchat(socket, arg); break; - case 'chat': on_chat(socket, arg); break; - case 'debug': on_debug(socket); break; - case 'save': on_save(socket); break; - case 'restore': on_restore(socket, arg); break; - case 'restart': on_restart(socket, arg); break; + case "action": + on_action(socket, arg[0], arg[1]) + break + case "query": + on_query(socket, arg) + break + case "resign": + on_resign(socket) + break + case "getchat": + on_getchat(socket, arg) + break + case "chat": + on_chat(socket, arg) + break + case "debug": + on_debug(socket) + break + case "save": + on_save(socket) + break + case "restore": + on_restore(socket, arg) + break + case "restart": + on_restart(socket, arg) + break } } function handle_observer_message(socket, cmd, arg) { switch (cmd) { - case 'query': on_query(socket, arg); break; + case 'query': + on_query(socket, arg) + break } } wss.on('connection', (socket, req, client) => { - let u = url.parse(req.url, true); + let u = url.parse(req.url, true) if (u.pathname !== '/play-socket') - return setTimeout(() => socket.close(1000, "Invalid request."), 30000); - req.query = u.query; + return setTimeout(() => socket.close(1000, "Invalid request."), 30000) + req.query = u.query - let user_id = 0; - let sid = login_cookie(req); + let user_id = 0 + let sid = login_cookie(req) if (sid) - user_id = login_sql_select.get(sid); + user_id = login_sql_select.get(sid) if (user_id) - socket.user = SQL_SELECT_USER_INFO.get(user_id); + socket.user = SQL_SELECT_USER_INFO.get(user_id) - socket.ip = req.ip || req.connection.remoteAddress || "0.0.0.0"; - socket.title_id = req.query.title || "unknown"; - socket.game_id = req.query.game | 0; - socket.role = req.query.role; - socket.seen = req.query.seen | 0; - socket.rules = RULES[socket.title_id]; + socket.ip = req.ip || req.connection.remoteAddress || "0.0.0.0" + socket.title_id = req.query.title || "unknown" + socket.game_id = req.query.game | 0 + socket.role = req.query.role + socket.seen = req.query.seen | 0 + socket.rules = RULES[socket.title_id] - SLOG(socket, "OPEN " + socket.seen); + SLOG(socket, "OPEN " + socket.seen) try { - let title_id = SQL_SELECT_GAME_TITLE.get(socket.game_id); + let title_id = SQL_SELECT_GAME_TITLE.get(socket.game_id) if (title_id !== socket.title_id) - return socket.close(1000, "Invalid game ID."); + return socket.close(1000, "Invalid game ID.") - let players = socket.players = SQL_SELECT_PLAYERS_JOIN.all(socket.game_id); + let players = socket.players = SQL_SELECT_PLAYERS_JOIN.all(socket.game_id) if (socket.role !== "Observer") { if (!socket.user) - return socket.close(1000, "You are not logged in!"); + return socket.close(1000, "You are not logged in!") if (socket.role && socket.role !== 'undefined' && socket.role !== 'null') { - let me = players.find(p => p.user_id === socket.user.user_id && p.role === socket.role); + let me = players.find(p => p.user_id === socket.user.user_id && p.role === socket.role) if (!me) - return socket.close(1000, "You aren't assigned that role!"); + return socket.close(1000, "You aren't assigned that role!") } else { - let me = players.find(p => p.user_id === socket.user.user_id); - socket.role = me ? me.role : "Observer"; + let me = players.find(p => p.user_id === socket.user.user_id) + socket.role = me ? me.role : "Observer" } } if (socket.seen === 0) - send_message(socket, 'players', [socket.role, players]); + send_message(socket, 'players', [socket.role, players]) if (clients[socket.game_id]) - clients[socket.game_id].push(socket); + clients[socket.game_id].push(socket) else - clients[socket.game_id] = [ socket ]; + clients[socket.game_id] = [ socket ] socket.on('close', (code, reason) => { - SLOG(socket, "CLOSE " + code); - clients[socket.game_id].splice(clients[socket.game_id].indexOf(socket), 1); - broadcast_presence(socket.game_id); - }); + SLOG(socket, "CLOSE " + code) + clients[socket.game_id].splice(clients[socket.game_id].indexOf(socket), 1) + broadcast_presence(socket.game_id) + }) socket.on('message', (data) => { try { - let [ cmd, arg ] = JSON.parse(data); + let [ cmd, arg ] = JSON.parse(data) if (socket.role !== "Observer") - handle_player_message(socket, cmd, arg); + handle_player_message(socket, cmd, arg) else - handle_observer_message(socket, cmd, arg); + handle_observer_message(socket, cmd, arg) } catch (err) { - send_message(socket, 'error', err); + send_message(socket, 'error', err) } - }); + }) - broadcast_presence(socket.game_id); - send_state(socket, get_game_state(socket.game_id)); + broadcast_presence(socket.game_id) + send_state(socket, get_game_state(socket.game_id)) } catch (err) { - console.log(err); - socket.close(1000, err.message); + console.log(err) + socket.close(1000, err.message) } -}); +}) /* * HIDDEN EXTRAS @@ -2040,21 +2060,21 @@ const SQL_GAME_STATS = SQL(` title_id, scenario, options having total > 12 - `); + `) app.get('/stats', function (req, res) { - let stats = SQL_GAME_STATS.all(); + let stats = SQL_GAME_STATS.all() stats.forEach(row => { - row.title_name = TITLES[row.title_id].title_name; - row.options = format_options(row.options); - row.result_role = row.result_role.split(","); - row.result_count = row.result_count.split(",").map(Number); - }); + row.title_name = TITLES[row.title_id].title_name + row.options = format_options(row.options) + row.result_role = row.result_role.split(",") + row.result_count = row.result_count.split(",").map(Number) + }) res.render('stats.pug', { user: req.user, stats: stats, - }); -}); + }) +}) const SQL_USER_STATS = SQL(` select @@ -2075,14 +2095,14 @@ const SQL_USER_STATS = SQL(` title_name, scenario, role - `); + `) app.get('/user-stats/:who_name', function (req, res) { - let who = SQL_SELECT_USER_BY_NAME.get(req.params.who_name); + let who = SQL_SELECT_USER_BY_NAME.get(req.params.who_name) if (who) { - let stats = SQL_USER_STATS.all(who.user_id); - res.render('user_stats.pug', { user: req.user, who: who, stats: stats }); + let stats = SQL_USER_STATS.all(who.user_id) + res.render('user_stats.pug', { user: req.user, who: who, stats: stats }) } else { - return res.status(404).send("Invalid user name."); + return res.status(404).send("Invalid user name.") } -}); +}) diff --git a/views/chat.pug b/views/chat.pug index 15d99df..3fb9b35 100644 --- a/views/chat.pug +++ b/views/chat.pug @@ -38,56 +38,56 @@ html p: a(href="/chat/all") All messages script. - let chat_data = !{ JSON.stringify(chat).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">") }; - let page_size = !{ page_size }; - let me = !{ JSON.stringify(user.name) }; - let table = document.querySelector("tbody"); - let foot = document.querySelector("#foot"); - let chat_lines = []; + let chat_data = !{ JSON.stringify(chat).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">") } + let page_size = !{ page_size } + let me = !{ JSON.stringify(user.name) } + let table = document.querySelector("tbody") + let foot = document.querySelector("#foot") + let chat_lines = [] function add(tr,text,link) { - let td = document.createElement("td"); + let td = document.createElement("td") if (link) { - let a = document.createElement("a"); - a.href = link; - a.textContent = text; - td.appendChild(a); + let a = document.createElement("a") + a.href = link + a.textContent = text + td.appendChild(a) } else { - td.textContent = text; + td.textContent = text } - tr.appendChild(td); + tr.appendChild(td) } function format_date(date) { - let t = date.toISOString(); - return t.substring(0,10) + " " + t.substring(11,19); + let t = date.toISOString() + return t.substring(0,10) + " " + t.substring(11,19) } for (let [game_id,time,user,message] of chat_data) { - let tr = document.createElement("tr"); - if (user === me) tr.className = "me"; - add(tr,game_id,"/join/"+game_id); - add(tr,format_date(new Date(time+"Z"))); - add(tr,user,"/user/"+user); - add(tr,message); - chat_lines.push(tr); + let tr = document.createElement("tr") + if (user === me) tr.className = "me" + add(tr,game_id,"/join/"+game_id) + add(tr,format_date(new Date(time+"Z"))) + add(tr,user,"/user/"+user) + add(tr,message) + chat_lines.push(tr) } function next() { if (page > 0) --page; update(); } function back() { if (page < page_count-1) ++page; update(); } function newest() { page=0; update(); } function oldest() { page=page_count-1; update(); } - let chat_size = chat_lines.length; - let page_count = Math.ceil(chat_size / page_size); - let page = 0; + let chat_size = chat_lines.length + let page_count = Math.ceil(chat_size / page_size) + let page = 0 function update() { - table.innerHTML = ""; + table.innerHTML = "" for (let i = 0; i < page_size; ++i) { - let k = page * page_size + ( page_size - i - 1); + let k = page * page_size + ( page_size - i - 1) if (k >= 0 && k < chat_size) - table.appendChild(chat_lines[k]); - foot.textContent = `${page_count-page} / ${page_count}`; + table.appendChild(chat_lines[k]) + foot.textContent = `${page_count-page} / ${page_count}` } } if (page_size > 0) { - newest(); + newest() } else { for (let i = chat_size-1; i >= 0; --i) - table.appendChild(chat_lines[i]); + table.appendChild(chat_lines[i]) } diff --git a/views/forum_post.pug b/views/forum_post.pug index d3c0e80..a0ff207 100644 --- a/views/forum_post.pug +++ b/views/forum_post.pug @@ -9,10 +9,10 @@ html script. function next(event,sel) { if (event.keyCode === 13) { - document.querySelector(sel).focus(); - return false; + document.querySelector(sel).focus() + return false } - return true; + return true } body include header diff --git a/views/join.pug b/views/join.pug index bc1567a..8a19a09 100644 --- a/views/join.pug +++ b/views/join.pug @@ -14,11 +14,11 @@ html td a { text-decoration: underline; color: blue; } .hide { display: none; } script. - let game = !{ JSON.stringify(game) }; - let roles = !{ JSON.stringify(roles) }; - let players = !{ JSON.stringify(players) }; - let user_id = !{ user.user_id }; - let ready = !{ ready }; + let game = !{ JSON.stringify(game) } + let roles = !{ JSON.stringify(roles) } + let players = !{ JSON.stringify(players) } + let user_id = !{ user.user_id } + let ready = !{ ready } script(src="/join.js") body include header diff --git a/views/message_outbox.pug b/views/message_outbox.pug index 57d6d60..5da84c6 100644 --- a/views/message_outbox.pug +++ b/views/message_outbox.pug @@ -8,9 +8,9 @@ html td a { display: block } script. function delete_all() { - let warning = "Are you sure you want to delete ALL the messages?"; + let warning = "Are you sure you want to delete ALL the messages?" if (window.confirm(warning)) - window.location.href = "/outbox/delete"; + window.location.href = "/outbox/delete" } body include header diff --git a/views/message_read.pug b/views/message_read.pug index db43423..f02c2f7 100644 --- a/views/message_read.pug +++ b/views/message_read.pug @@ -8,12 +8,12 @@ html .head {white-space:pre-wrap} script. function delete_message(id) { - let warning = "Are you sure you want to DELETE this message?"; + let warning = "Are you sure you want to DELETE this message?" if (window.confirm(warning)) - window.location.href = "/message/delete/" + id; + window.location.href = "/message/delete/" + id } function reply_message(id) { - window.location.href = "/message/reply/" + id; + window.location.href = "/message/reply/" + id } body include header |