diff --git a/client/multiplayer/room.jsx b/client/multiplayer/room.jsx index 6e465801..de973377 100644 --- a/client/multiplayer/room.jsx +++ b/client/multiplayer/room.jsx @@ -746,7 +746,7 @@ function updateTimerDisplay (time) { } function vkInit ({ targetUsername, threshold }) { - logEvent(`A votekick has been started against user ${targetUsername} and needs ${threshold} votes to suceed.`); + logEvent(`A votekick has been started against user ${targetUsername} and needs ${threshold} votes to succeed.`); } function vkHandle ({ targetUsername, targetId }) { diff --git a/client/multiplayer/room.min.js b/client/multiplayer/room.min.js index ab329843..7d980ff4 100644 --- a/client/multiplayer/room.min.js +++ b/client/multiplayer/room.min.js @@ -3,4 +3,4 @@ import questionStats from"../scripts/auth/question-stats.js";import api from"../ */const players={},ROOM_NAME=decodeURIComponent(window.location.pathname.substring(13));let tossup={},USER_ID=window.localStorage.getItem("USER_ID")||"unknown",username=window.localStorage.getItem("multiplayer-username")||api.getRandomName();const socket=new window.WebSocket(window.location.href.replace("http","ws")+(window.location.href.endsWith("?private=true")?"&":"?")+new URLSearchParams({roomName:ROOM_NAME,userId:USER_ID,username}).toString()),PING_INTERVAL_ID=setInterval(()=>socket.send(JSON.stringify({type:"ping"})),45e3);// Ping server every 45 seconds to prevent socket disconnection socket.onclose=function(a){const{code:b}=a;3e3!==b&&window.alert("Disconnected from server"),clearInterval(PING_INTERVAL_ID)},socket.onmessage=function(a){const b=JSON.parse(a.data);switch(b.type){case"buzz":return buzz(b);case"chat":return chat(b,!1);case"chat-live-update":return chat(b,!0);case"clear-stats":return clearStats(b);case"confirm-ban":return confirmBan(b);case"connection-acknowledged":return connectionAcknowledged(b);case"connection-acknowledged-query":return connectionAcknowledgedQuery(b);case"connection-acknowledged-tossup":return connectionAcknowledgedTossup(b);case"enforcing-removal":return ackRemovedFromRoom(b);case"end-of-set":return endOfSet(b);case"error":return handleError(b);case"force-username":return forceUsername(b);case"give-answer":return giveAnswer(b);case"give-answer-live-update":return logGiveAnswer(b,!0);case"initiated-vk":return vkInit(b);case"join":return join(b);case"leave":return leave(b);case"lost-buzzer-race":return lostBuzzerRace(b);case"mute-player":return mutePlayer(b);case"next":return next(b);case"no-questions-found":return noQuestionsFound(b);case"pause":return pause(b);case"reveal-answer":return revealAnswer(b);case"set-categories":return setCategories(b);case"set-difficulties":return setDifficulties(b);case"set-reading-speed":return setReadingSpeed(b);case"set-packet-numbers":return setPacketNumbers(b);case"set-strictness":return setStrictness(b);case"set-set-name":return setSetName(b);case"set-username":return setUsername(b);case"set-year-range":return setYearRange(b);case"skip":return next(b);case"start":return next(b);case"successful-vk":return vkHandle(b);case"timer-update":return updateTimerDisplay(b.timeRemaining);case"toggle-lock":return toggleLock(b);case"toggle-login-required":return toggleLoginRequired(b);case"toggle-powermark-only":return togglePowermarkOnly(b);case"toggle-public":return togglePublic(b);case"toggle-rebuzz":return toggleRebuzz(b);case"toggle-select-by-set-name":return toggleSelectBySetName(b);case"toggle-skip":return toggleSkip(b);case"toggle-standard-only":return toggleStandardOnly(b);case"toggle-timer":return toggleTimer(b);case"update-question":return updateQuestion(b)}};// if a banned/kicked user tries to join a room they were removed from this is the response function ackRemovedFromRoom({removalType:a}){"kick"===a?window.alert("You were kicked from this room by players, and cannot rejoin it."):window.alert("You were banned from this room by the room owner, and cannot rejoin it."),setTimeout(()=>{window.location.replace("../")},100)}function buzz({userId:a,username:b}){logEvent(b,"buzzed"),document.getElementById("buzz").disabled=!0,document.getElementById("pause").disabled=!0,document.getElementById("next").disabled=!0,document.getElementById("skip").disabled=!0,a===USER_ID&&(document.getElementById("answer-input-group").classList.remove("d-none"),document.getElementById("answer-input").focus())}function chat({message:a,userId:c,username:d},e=!1){if(!muteList.includes(c)){if(!e&&""===a)return void document.getElementById("live-chat-"+c).parentElement.remove();if(!e&&a)return document.getElementById("live-chat-"+c).className="",void(document.getElementById("live-chat-"+c).id="");if(document.getElementById("live-chat-"+c))return void(document.getElementById("live-chat-"+c).textContent=a);const f=document.createElement("b");f.textContent=d;const b=document.createElement("span");b.classList.add("text-muted"),b.id="live-chat-"+c,b.textContent=a;const g=document.createElement("li");g.appendChild(f),g.appendChild(document.createTextNode(" ")),g.appendChild(b),document.getElementById("room-history").prepend(g)}}function clearStats({userId:a}){for(const b of["celerity","negs","points","powers","tens","tuh","zeroes"])players[a][b]=0;upsertPlayerItem(players[a],USER_ID,ownerId,socket,globalPublic),sortPlayerListGroup()}function confirmBan({targetId:a,targetUsername:b}){a===USER_ID?(window.alert("You were banned from this room by the room owner."),setTimeout(()=>{window.location.replace("../")},100)):logEvent(b+" has been banned from this room.")}function connectionAcknowledged({buzzedIn:a,canBuzz:b,isPermanent:c,ownerId:d,players:e,questionProgress:f,settings:g,userId:h}){document.getElementById("buzz").disabled=!b,c&&(document.getElementById("category-select-button").disabled=!0,document.getElementById("strictness").disabled=!0,document.getElementById("toggle-public").disabled=!0,document.getElementById("toggle-select-by-set-name").disabled=!0,document.getElementById("private-chat-warning").innerHTML="This is a permanent room. Some settings have been restricted."),ownerId=d;for(const i of Object.keys(e))e[i].celerity=e[i].celerity.correct.average,players[i]=e[i],upsertPlayerItem(players[i],USER_ID,ownerId,socket,globalPublic);sortPlayerListGroup();0===f?(document.getElementById("next").textContent="Start",document.getElementById("next").classList.remove("btn-primary"),document.getElementById("next").classList.add("btn-success")):1===f?(showSkipButton(),document.getElementById("settings").classList.add("d-none"),a?(document.getElementById("buzz").disabled=!0,document.getElementById("next").disabled=!0,document.getElementById("pause").disabled=!0):(document.getElementById("buzz").disabled=!1,document.getElementById("pause").disabled=!1)):2===f?(showNextButton(),document.getElementById("settings").classList.add("d-none")):void 0;document.getElementById("toggle-lock").checked=g.lock,document.getElementById("toggle-login-required").checked=g.loginRequired,document.getElementById("chat").disabled=g.public,document.getElementById("toggle-lock").disabled=g.public,document.getElementById("toggle-login-required").disabled=g.public,document.getElementById("toggle-timer").disabled=g.public,document.getElementById("toggle-public").checked=g.public,globalPublic=g.public,document.getElementById("reading-speed").value=g.readingSpeed,document.getElementById("reading-speed-display").textContent=g.readingSpeed,document.getElementById("strictness").value=g.strictness,document.getElementById("strictness-display").textContent=g.strictness,document.getElementById("toggle-rebuzz").checked=g.rebuzz,document.getElementById("toggle-skip").checked=g.skip,document.getElementById("timer").classList.toggle("d-none",!g.timer),document.getElementById("toggle-timer").checked=g.timer,USER_ID=h,window.localStorage.setItem("USER_ID",USER_ID)}async function connectionAcknowledgedQuery({difficulties:k=[],minYear:a,maxYear:b,packetNumbers:l=[],powermarkOnly:c,selectBySetName:d,setName:m="",standardOnly:e,alternateSubcategories:f,categories:g,subcategories:h,percentView:i,categoryPercents:j}){setDifficulties({difficulties:k}),$("#slider").slider("values",0,a),$("#slider").slider("values",1,b),document.getElementById("year-range-a").textContent=a,document.getElementById("year-range-b").textContent=b,document.getElementById("packet-number").value=arrayToRange(l),document.getElementById("toggle-powermark-only").checked=c,document.getElementById("difficulty-settings").classList.toggle("d-none",d),document.getElementById("set-settings").classList.toggle("d-none",!d),document.getElementById("toggle-select-by-set-name").checked=d,document.getElementById("toggle-powermark-only").disabled=d,document.getElementById("toggle-standard-only").disabled=d,document.getElementById("set-name").value=m,maxPacketNumber=await api.getNumPackets(m),""!==m&&0===maxPacketNumber&&document.getElementById("set-name").classList.add("is-invalid"),document.getElementById("toggle-standard-only").checked=e,categoryManager.import({categories:g,subcategories:h,alternateSubcategories:f,percentView:i,categoryPercents:j}),categoryManager.loadCategoryModal()}function connectionAcknowledgedTossup({tossup:a}){tossup=a,document.getElementById("set-name-info").textContent=tossup?.set?.name??"",document.getElementById("packet-number-info").textContent=tossup?.packet?.number??"-",document.getElementById("question-number-info").textContent=tossup?.number??"-"}function endOfSet(){window.alert("You have reached the end of the set")}function forceUsername({message:a,username:b}){window.alert(a),window.localStorage.setItem("multiplayer-username",b),document.querySelector("#username").value=b}async function giveAnswer({celerity:a,directive:b,directedPrompt:c,givenAnswer:d,perQuestionCelerity:e,score:f,tossup:g,userId:h,username:i}){document.getElementById("answer-input").value="",document.getElementById("answer-input-group").classList.add("d-none"),document.getElementById("answer-input").blur(),logGiveAnswer({directive:b,message:d,username:i}),"prompt"===b&&c?logEvent(i,`was prompted with "${c}"`):"prompt"===b?logEvent(i,"was prompted"):logEvent(i,`${0{a.textContent=parseInt(a.innerHTML)+1})),"reject"===b&&(document.getElementById("buzz").disabled=!document.getElementById("toggle-rebuzz").checked&&h===USER_ID),10f&&players[h].negs++,players[h].points+=f,players[h].tuh++,players[h].celerity=a,upsertPlayerItem(players[h],USER_ID,ownerId,socket,globalPublic),sortPlayerListGroup()),"prompt"!==b&&h===USER_ID&&questionStats.recordTossup(g,0b!==a))}function next({oldTossup:a,tossup:b,type:c,username:d}){switch(c){case"next":logEvent(d,"went to the next question");break;case"skip":logEvent(d,"skipped the question");break;case"start":logEvent(d,"started the game");break;default:throw new Error("Invalid type")}"next"===c||"skip"===c?createTossupCard(a):"start"===c&&(document.getElementById("next").classList.add("btn-primary"),document.getElementById("next").classList.remove("btn-success"),document.getElementById("next").textContent="Next"),tossup=b,document.getElementById("packet-number-info").textContent=tossup?.packet.number??"-",document.getElementById("question-number-info").textContent=tossup?.number??"-",document.getElementById("set-name-info").textContent=tossup?.set.name??"",document.getElementById("answer").textContent="",document.getElementById("question").textContent="",document.getElementById("buzz").textContent="Buzz",document.getElementById("buzz").disabled=!1,document.getElementById("pause").textContent="Pause",document.getElementById("pause").disabled=!1,document.getElementById("settings").classList.add("d-none"),showSkipButton(),updateTimerDisplay(100)}function noQuestionsFound(){window.alert("No questions found")}function pause({paused:a,username:b}){logEvent(b,`${a?"":"un"}paused the game`)}function revealAnswer({answer:a,question:b}){document.getElementById("question").innerHTML=b,document.getElementById("answer").innerHTML="ANSWER: "+a,document.getElementById("pause").disabled=!0,showNextButton()}function showNextButton(){document.getElementById("next").classList.remove("d-none"),document.getElementById("next").disabled=!1,document.getElementById("skip").classList.add("d-none"),document.getElementById("skip").disabled=!0}function showSkipButton(){document.getElementById("skip").classList.remove("d-none"),document.getElementById("skip").disabled=!document.getElementById("toggle-skip").checked,document.getElementById("next").classList.add("d-none"),document.getElementById("next").disabled=!0}function sortPlayerListGroup(c=!0){const d=document.getElementById("player-list-group"),e=Array.from(d.children),f=11;e.sort((d,a)=>{const b=parseInt(document.getElementById("points-"+d.id.substring(f)).innerHTML),e=parseInt(document.getElementById("points-"+a.id.substring(f)).innerHTML);// if points are equal, sort alphabetically by username -if(b===e){const b=document.getElementById("username-"+d.id.substring(f)).innerHTML,e=document.getElementById("username-"+a.id.substring(f)).innerHTML;return c?b.localeCompare(e):e.localeCompare(b)}return c?e-b:b-e}).forEach(a=>{d.appendChild(a)})}function setCategories({alternateSubcategories:a,categories:b,subcategories:c,percentView:d,categoryPercents:e,username:f}){logEvent(f,"updated the categories"),categoryManager.import({categories:b,subcategories:c,alternateSubcategories:a,percentView:d,categoryPercents:e}),categoryManager.loadCategoryModal()}function setDifficulties({difficulties:a,username:b=void 0}){return b&&logEvent(b,0{const c=b.querySelector("input");a.includes(parseInt(c.value))?(c.checked=!0,b.classList.add("active")):(c.checked=!1,b.classList.remove("active"))}):void(startingDifficulties=a)}function setPacketNumbers({username:a,packetNumbers:b}){b=arrayToRange(b),logEvent(a,0{upsertPlayerItem(players[a],USER_ID,ownerId,socket,globalPublic)})}function updateQuestion({word:a}){"(*)"===a||(document.getElementById("question").innerHTML+=a+" ")}function updateTimerDisplay(a){const b=Math.floor(a/10);document.querySelector(".timer .face").innerText=b,document.querySelector(".timer .fraction").innerText="."+a%10}function vkInit({targetUsername:a,threshold:b}){logEvent(`A votekick has been started against user ${a} and needs ${b} votes to suceed.`)}function vkHandle({targetUsername:a,targetId:b}){USER_ID===b?(window.alert("You were vote kicked from this room by others."),setTimeout(()=>{window.location.replace("../")},100)):logEvent(a+" has been vote kicked from this room.")}document.getElementById("answer-form").addEventListener("submit",function(a){a.preventDefault(),a.stopPropagation();const b=document.getElementById("answer-input").value;socket.send(JSON.stringify({type:"give-answer",givenAnswer:b}))}),document.getElementById("answer-input").addEventListener("input",function(){socket.send(JSON.stringify({type:"give-answer-live-update",message:this.value}))}),document.getElementById("buzz").addEventListener("click",function(){this.blur(),audio.soundEffects&&audio.buzz.play(),socket.send(JSON.stringify({type:"buzz"})),socket.send(JSON.stringify({type:"give-answer-live-update",message:""}))}),document.getElementById("chat").addEventListener("click",function(){this.blur(),document.getElementById("chat-input-group").classList.remove("d-none"),document.getElementById("chat-input").focus(),socket.send(JSON.stringify({type:"chat-live-update",message:""}))}),document.getElementById("chat-form").addEventListener("submit",function(a){a.preventDefault(),a.stopPropagation();const b=document.getElementById("chat-input").value;document.getElementById("chat-input").value="",document.getElementById("chat-input-group").classList.add("d-none"),document.getElementById("chat-input").blur(),socket.send(JSON.stringify({type:"chat",message:b}))}),document.getElementById("chat-input").addEventListener("input",function(){socket.send(JSON.stringify({type:"chat-live-update",message:this.value}))}),document.getElementById("clear-stats").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"clear-stats"}))}),document.getElementById("next").addEventListener("click",function(){switch(this.blur(),this.innerHTML){case"Start":socket.send(JSON.stringify({type:"start"}));break;case"Next":socket.send(JSON.stringify({type:"next"}))}}),document.getElementById("skip").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"skip"}))}),document.getElementById("packet-number").addEventListener("change",function(){const a=rangeToArray(this.value,maxPacketNumber);return a.some(a=>1>a||a>maxPacketNumber)?void document.getElementById("packet-number").classList.add("is-invalid"):void(document.getElementById("packet-number").classList.remove("is-invalid"),socket.send(JSON.stringify({type:"set-packet-numbers",packetNumbers:a})))}),document.getElementById("pause").addEventListener("click",function(){this.blur();const a=parseFloat(document.querySelector(".timer .face").innerText),b=parseFloat(document.querySelector(".timer .fraction").innerText);socket.send(JSON.stringify({type:"pause",pausedTime:10*(a+b)}))}),document.getElementById("reading-speed").addEventListener("change",function(){socket.send(JSON.stringify({type:"set-reading-speed",readingSpeed:this.value}))}),document.getElementById("reading-speed").addEventListener("input",function(){document.getElementById("reading-speed-display").textContent=this.value}),document.getElementById("report-question-submit").addEventListener("click",function(){api.reportQuestion(document.getElementById("report-question-id").value,document.getElementById("report-question-reason").value,document.getElementById("report-question-description").value)}),document.getElementById("set-name").addEventListener("change",async function(){api.getSetList().includes(this.value)||0===this.value.length?this.classList.remove("is-invalid"):this.classList.add("is-invalid"),maxPacketNumber=await api.getNumPackets(this.value),document.getElementById("packet-number").value=""===this.value||0===maxPacketNumber?"":`1-${maxPacketNumber}`,socket.send(JSON.stringify({type:"set-set-name",setName:this.value,packetNumbers:rangeToArray(document.getElementById("packet-number").value)}))}),document.getElementById("strictness").addEventListener("change",function(){this.blur(),socket.send(JSON.stringify({type:"set-strictness",strictness:this.value}))}),document.getElementById("strictness").addEventListener("input",function(){document.getElementById("strictness-display").textContent=this.value}),document.getElementById("toggle-lock").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-lock",lock:this.checked}))}),document.getElementById("toggle-login-required").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-login-required",loginRequired:this.checked}))}),document.getElementById("toggle-powermark-only").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-powermark-only",powermarkOnly:this.checked}))}),document.getElementById("toggle-rebuzz").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-rebuzz",rebuzz:this.checked}))}),document.getElementById("toggle-skip").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-skip",skip:this.checked}))}),document.getElementById("toggle-select-by-set-name").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-select-by-set-name",setName:document.getElementById("set-name").value,selectBySetName:this.checked}))}),document.getElementById("toggle-settings").addEventListener("click",function(){this.blur(),document.getElementById("buttons").classList.toggle("col-lg-9"),document.getElementById("buttons").classList.toggle("col-lg-12"),document.getElementById("content").classList.toggle("col-lg-9"),document.getElementById("content").classList.toggle("col-lg-12"),document.getElementById("settings").classList.toggle("d-none"),document.getElementById("settings").classList.toggle("d-lg-none")}),document.getElementById("toggle-standard-only").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-standard-only",standardOnly:this.checked}))}),document.getElementById("toggle-timer").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-timer",timer:this.checked}))}),document.getElementById("toggle-public").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-public",public:this.checked}))}),document.getElementById("username").addEventListener("change",function(){socket.send(JSON.stringify({type:"set-username",userId:USER_ID,username:this.value})),username=this.value,window.localStorage.setItem("multiplayer-username",username)}),document.getElementById("year-range-a").onchange=function(){const[a,b]=$("#slider").slider("values");if(b{if("Escape"===a.key&&"chat-input"===document.activeElement.id&&(document.getElementById("chat-input").value="",document.getElementById("chat-input-group").classList.add("d-none"),document.getElementById("chat-input").blur(),socket.send(JSON.stringify({type:"chat",message:""}))),!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement.tagName))switch(a.key?.toLowerCase()){case" ":document.getElementById("buzz").click(),a.target===document.body&&a.preventDefault();break;case"e":return document.getElementById("toggle-settings").click();case"k":return document.getElementsByClassName("card-header-clickable")[0].click();case"p":return document.getElementById("pause").click();case"t":return document.getElementsByClassName("star-tossup")[0].click();case"y":return navigator.clipboard.writeText(tossup._id??"");case"n":case"s":document.getElementById("next").click(),document.getElementById("skip").click()}}),document.addEventListener("keypress",function(a){"Enter"===a.key&&a.target===document.body&&document.getElementById("chat").click()}),document.getElementById("username").value=username,ReactDOM.createRoot(document.getElementById("category-modal-root")).render(/*#__PURE__*/React.createElement(CategoryModal,{categoryManager:categoryManager,onClose:()=>{oldCategories!==JSON.stringify(categoryManager.export())&&socket.send(JSON.stringify({type:"set-categories",...categoryManager.export()})),oldCategories=JSON.stringify(categoryManager.export())}})),ReactDOM.createRoot(document.getElementById("difficulty-dropdown-root")).render(/*#__PURE__*/React.createElement(DifficultyDropdown,{startingDifficulties:startingDifficulties,onChange:()=>socket.send(JSON.stringify({type:"set-difficulties",difficulties:getDropdownValues("difficulties")}))})); \ No newline at end of file +if(b===e){const b=document.getElementById("username-"+d.id.substring(f)).innerHTML,e=document.getElementById("username-"+a.id.substring(f)).innerHTML;return c?b.localeCompare(e):e.localeCompare(b)}return c?e-b:b-e}).forEach(a=>{d.appendChild(a)})}function setCategories({alternateSubcategories:a,categories:b,subcategories:c,percentView:d,categoryPercents:e,username:f}){logEvent(f,"updated the categories"),categoryManager.import({categories:b,subcategories:c,alternateSubcategories:a,percentView:d,categoryPercents:e}),categoryManager.loadCategoryModal()}function setDifficulties({difficulties:a,username:b=void 0}){return b&&logEvent(b,0{const c=b.querySelector("input");a.includes(parseInt(c.value))?(c.checked=!0,b.classList.add("active")):(c.checked=!1,b.classList.remove("active"))}):void(startingDifficulties=a)}function setPacketNumbers({username:a,packetNumbers:b}){b=arrayToRange(b),logEvent(a,0{upsertPlayerItem(players[a],USER_ID,ownerId,socket,globalPublic)})}function updateQuestion({word:a}){"(*)"===a||(document.getElementById("question").innerHTML+=a+" ")}function updateTimerDisplay(a){const b=Math.floor(a/10);document.querySelector(".timer .face").innerText=b,document.querySelector(".timer .fraction").innerText="."+a%10}function vkInit({targetUsername:a,threshold:b}){logEvent(`A votekick has been started against user ${a} and needs ${b} votes to succeed.`)}function vkHandle({targetUsername:a,targetId:b}){USER_ID===b?(window.alert("You were vote kicked from this room by others."),setTimeout(()=>{window.location.replace("../")},100)):logEvent(a+" has been vote kicked from this room.")}document.getElementById("answer-form").addEventListener("submit",function(a){a.preventDefault(),a.stopPropagation();const b=document.getElementById("answer-input").value;socket.send(JSON.stringify({type:"give-answer",givenAnswer:b}))}),document.getElementById("answer-input").addEventListener("input",function(){socket.send(JSON.stringify({type:"give-answer-live-update",message:this.value}))}),document.getElementById("buzz").addEventListener("click",function(){this.blur(),audio.soundEffects&&audio.buzz.play(),socket.send(JSON.stringify({type:"buzz"})),socket.send(JSON.stringify({type:"give-answer-live-update",message:""}))}),document.getElementById("chat").addEventListener("click",function(){this.blur(),document.getElementById("chat-input-group").classList.remove("d-none"),document.getElementById("chat-input").focus(),socket.send(JSON.stringify({type:"chat-live-update",message:""}))}),document.getElementById("chat-form").addEventListener("submit",function(a){a.preventDefault(),a.stopPropagation();const b=document.getElementById("chat-input").value;document.getElementById("chat-input").value="",document.getElementById("chat-input-group").classList.add("d-none"),document.getElementById("chat-input").blur(),socket.send(JSON.stringify({type:"chat",message:b}))}),document.getElementById("chat-input").addEventListener("input",function(){socket.send(JSON.stringify({type:"chat-live-update",message:this.value}))}),document.getElementById("clear-stats").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"clear-stats"}))}),document.getElementById("next").addEventListener("click",function(){switch(this.blur(),this.innerHTML){case"Start":socket.send(JSON.stringify({type:"start"}));break;case"Next":socket.send(JSON.stringify({type:"next"}))}}),document.getElementById("skip").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"skip"}))}),document.getElementById("packet-number").addEventListener("change",function(){const a=rangeToArray(this.value,maxPacketNumber);return a.some(a=>1>a||a>maxPacketNumber)?void document.getElementById("packet-number").classList.add("is-invalid"):void(document.getElementById("packet-number").classList.remove("is-invalid"),socket.send(JSON.stringify({type:"set-packet-numbers",packetNumbers:a})))}),document.getElementById("pause").addEventListener("click",function(){this.blur();const a=parseFloat(document.querySelector(".timer .face").innerText),b=parseFloat(document.querySelector(".timer .fraction").innerText);socket.send(JSON.stringify({type:"pause",pausedTime:10*(a+b)}))}),document.getElementById("reading-speed").addEventListener("change",function(){socket.send(JSON.stringify({type:"set-reading-speed",readingSpeed:this.value}))}),document.getElementById("reading-speed").addEventListener("input",function(){document.getElementById("reading-speed-display").textContent=this.value}),document.getElementById("report-question-submit").addEventListener("click",function(){api.reportQuestion(document.getElementById("report-question-id").value,document.getElementById("report-question-reason").value,document.getElementById("report-question-description").value)}),document.getElementById("set-name").addEventListener("change",async function(){api.getSetList().includes(this.value)||0===this.value.length?this.classList.remove("is-invalid"):this.classList.add("is-invalid"),maxPacketNumber=await api.getNumPackets(this.value),document.getElementById("packet-number").value=""===this.value||0===maxPacketNumber?"":`1-${maxPacketNumber}`,socket.send(JSON.stringify({type:"set-set-name",setName:this.value,packetNumbers:rangeToArray(document.getElementById("packet-number").value)}))}),document.getElementById("strictness").addEventListener("change",function(){this.blur(),socket.send(JSON.stringify({type:"set-strictness",strictness:this.value}))}),document.getElementById("strictness").addEventListener("input",function(){document.getElementById("strictness-display").textContent=this.value}),document.getElementById("toggle-lock").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-lock",lock:this.checked}))}),document.getElementById("toggle-login-required").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-login-required",loginRequired:this.checked}))}),document.getElementById("toggle-powermark-only").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-powermark-only",powermarkOnly:this.checked}))}),document.getElementById("toggle-rebuzz").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-rebuzz",rebuzz:this.checked}))}),document.getElementById("toggle-skip").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-skip",skip:this.checked}))}),document.getElementById("toggle-select-by-set-name").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-select-by-set-name",setName:document.getElementById("set-name").value,selectBySetName:this.checked}))}),document.getElementById("toggle-settings").addEventListener("click",function(){this.blur(),document.getElementById("buttons").classList.toggle("col-lg-9"),document.getElementById("buttons").classList.toggle("col-lg-12"),document.getElementById("content").classList.toggle("col-lg-9"),document.getElementById("content").classList.toggle("col-lg-12"),document.getElementById("settings").classList.toggle("d-none"),document.getElementById("settings").classList.toggle("d-lg-none")}),document.getElementById("toggle-standard-only").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-standard-only",standardOnly:this.checked}))}),document.getElementById("toggle-timer").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-timer",timer:this.checked}))}),document.getElementById("toggle-public").addEventListener("click",function(){this.blur(),socket.send(JSON.stringify({type:"toggle-public",public:this.checked}))}),document.getElementById("username").addEventListener("change",function(){socket.send(JSON.stringify({type:"set-username",userId:USER_ID,username:this.value})),username=this.value,window.localStorage.setItem("multiplayer-username",username)}),document.getElementById("year-range-a").onchange=function(){const[a,b]=$("#slider").slider("values");if(b{if("Escape"===a.key&&"chat-input"===document.activeElement.id&&(document.getElementById("chat-input").value="",document.getElementById("chat-input-group").classList.add("d-none"),document.getElementById("chat-input").blur(),socket.send(JSON.stringify({type:"chat",message:""}))),!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement.tagName))switch(a.key?.toLowerCase()){case" ":document.getElementById("buzz").click(),a.target===document.body&&a.preventDefault();break;case"e":return document.getElementById("toggle-settings").click();case"k":return document.getElementsByClassName("card-header-clickable")[0].click();case"p":return document.getElementById("pause").click();case"t":return document.getElementsByClassName("star-tossup")[0].click();case"y":return navigator.clipboard.writeText(tossup._id??"");case"n":case"s":document.getElementById("next").click(),document.getElementById("skip").click()}}),document.addEventListener("keypress",function(a){"Enter"===a.key&&a.target===document.body&&document.getElementById("chat").click()}),document.getElementById("username").value=username,ReactDOM.createRoot(document.getElementById("category-modal-root")).render(/*#__PURE__*/React.createElement(CategoryModal,{categoryManager:categoryManager,onClose:()=>{oldCategories!==JSON.stringify(categoryManager.export())&&socket.send(JSON.stringify({type:"set-categories",...categoryManager.export()})),oldCategories=JSON.stringify(categoryManager.export())}})),ReactDOM.createRoot(document.getElementById("difficulty-dropdown-root")).render(/*#__PURE__*/React.createElement(DifficultyDropdown,{startingDifficulties:startingDifficulties,onChange:()=>socket.send(JSON.stringify({type:"set-difficulties",difficulties:getDropdownValues("difficulties")}))})); \ No newline at end of file diff --git a/client/scripts/upsertPlayerItem.js b/client/scripts/upsertPlayerItem.js index 7351fc7c..fd1ccb65 100644 --- a/client/scripts/upsertPlayerItem.js +++ b/client/scripts/upsertPlayerItem.js @@ -29,11 +29,14 @@ export default function upsertPlayerItem (player, USER_ID, ownerId, socket, isPu const displayUsername = (playerIsOwner && !isPublic) ? `👑 ${escapeHTML(username)}` : escapeHTML(username); playerItem.innerHTML = ` -
- ${displayUsername} - ${points} +
+
+ ${displayUsername} +
- `; + ${points} +
+`; // Set attributes for the popover playerItem.setAttribute('data-bs-container', 'body'); @@ -56,55 +59,80 @@ export default function upsertPlayerItem (player, USER_ID, ownerId, socket, isPu
  • Is Owner?${playerIsOwner ? 'Yes' : 'No'}
  • `); - document.getElementById('player-list-group').appendChild(playerItem); + const banTrigger = (ownerId === USER_ID) && userId !== ownerId && !isPublic && userId !== 'ai-bot'; + const muteTrigger = userId !== 'ai-bot' && userId !== USER_ID && !isPublic; + const vkTrigger = userId !== USER_ID && (isPublic || (userId !== ownerId && userId !== 'ai-bot')); - // ban button if the viewer is the owner and the player is not, also room has to be private - if ((ownerId === USER_ID) && userId !== ownerId && !isPublic && userId !== 'ai-bot') { - const banButton = document.createElement('button'); - banButton.className = 'btn btn-danger btn-sm mt-2 me-1'; - banButton.title = 'Ban an user. They can no longer join the room.'; - banButton.innerText = 'Ban'; - playerItem.appendChild(banButton); - banButton.addEventListener('click', () => { - socket.send(JSON.stringify({ type: 'ban', targetId: userId, targetUsername: username })); - }); - } + if (banTrigger || muteTrigger || vkTrigger) { + const dropdownContainer = document.createElement('div'); + dropdownContainer.className = 'ms-1'; - // votekick button. cannot vk an owner (change? idk) - if (userId !== USER_ID && (isPublic || (userId !== ownerId && userId !== 'ai-bot'))) { - const vkButton = document.createElement('button'); - vkButton.className = 'btn btn-warning btn-sm mt-2 me-1'; - vkButton.title = 'Initiate a votekick on an user. 90 second cooldown.'; - vkButton.innerText = 'VK'; - playerItem.appendChild(vkButton); - vkButton.addEventListener('click', () => { - socket.send(JSON.stringify({ type: 'votekick-vote', targetId: userId })); - socket.send(JSON.stringify({ type: 'votekick-init', targetId: userId })); - vkButton.disabled = true; - vkButton.innerText = 'Cooldown'; - setTimeout(() => { - vkButton.disabled = false; - vkButton.innerText = 'VK'; - }, 90000); - }); - } - // User cannot be ai or yourself - if (userId !== 'ai-bot' && userId !== USER_ID && !isPublic) { - const muteButton = document.createElement('button'); - - muteButton.className = 'btn btn-warning btn-sm mt-2 me-1'; - muteButton.title = 'Mute/Unmute an user to change visibility of what they say in chat.'; - muteButton.innerText = 'Mute'; - playerItem.appendChild(muteButton); - muteButton.addEventListener('click', () => { - socket.send(JSON.stringify({ type: 'toggle-mute', targetId: userId, muteStatus: muteButton.innerText })); - if (muteButton.innerText === 'Unmute') { - muteButton.innerText = 'Mute'; - } else { - muteButton.innerText = 'Unmute'; - } - }); + const toggleButton = document.createElement('button'); + toggleButton.className = 'btn btn-default dropdown-toggle py-0 px-1'; + toggleButton.setAttribute('data-bs-toggle', 'dropdown'); + toggleButton.setAttribute('type', 'button'); + dropdownContainer.appendChild(toggleButton); + + const dropdownMenu = document.createElement('ul'); + dropdownMenu.className = 'dropdown-menu'; + dropdownMenu.setAttribute('aria-labelledby', 'playerActionsDropdown'); + + if (muteTrigger) { + const muteItem = document.createElement('li'); + const muteButton = document.createElement('button'); + muteButton.innerText = 'Mute'; + muteButton.className = 'btn btn-warning btn-sm mt-2 me-1 dropdown-item'; + muteButton.title = 'Mute/Unmute an user to change visibility of what they say in chat.'; + muteItem.appendChild(muteButton); + dropdownMenu.appendChild(muteItem); + + muteButton.addEventListener('click', () => { + socket.send(JSON.stringify({ type: 'toggle-mute', targetId: userId, muteStatus: muteButton.innerText })); + muteButton.innerText = muteButton.innerText === 'Unmute' ? 'Mute' : 'Unmute'; + }); + } + + if (vkTrigger) { + const kickItem = document.createElement('li'); + const vkButton = document.createElement('button'); + vkButton.className = 'btn btn-warning btn-sm mt-2 me-1 dropdown-item'; + vkButton.title = 'Initiate a votekick on an user. 90 second cooldown.'; + vkButton.innerText = 'Votekick'; + kickItem.appendChild(vkButton); + dropdownMenu.appendChild(kickItem); + + vkButton.addEventListener('click', () => { + socket.send(JSON.stringify({ type: 'votekick-vote', targetId: userId })); + socket.send(JSON.stringify({ type: 'votekick-init', targetId: userId })); + vkButton.disabled = true; + vkButton.innerText = 'Cooldown'; + setTimeout(() => { + vkButton.disabled = false; + vkButton.innerText = 'VK'; + }, 90000); + }); + } + + if (banTrigger) { + const banItem = document.createElement('li'); + const banButton = document.createElement('button'); + banButton.className = 'btn btn-danger btn-sm mt-2 me-1 dropdown-item'; + banButton.title = 'Ban an user. They can no longer join the room.'; + banButton.innerText = 'Ban'; + banItem.appendChild(banButton); + dropdownMenu.appendChild(banItem); + + banButton.addEventListener('click', () => { + socket.send(JSON.stringify({ type: 'ban', targetId: userId, targetUsername: username })); + }); + } + + dropdownContainer.appendChild(dropdownMenu); + + const usernameSpan = playerItem.querySelector(`#username-${userId}`); + usernameSpan.classList.add('me-2'); + usernameSpan.after(dropdownContainer); } // bootstrap requires "new" to be called on each popover diff --git a/package-lock.json b/package-lock.json index 05f0c476..2b9996d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "ws": "^8.8.0" }, "devDependencies": { - "@babel/cli": "^7.19.3", + "@babel/cli": "^7.25.9", "@babel/core": "^7.20.2", "@babel/preset-react": "^7.18.6", "babel-preset-minify": "^0.5.2", @@ -1576,14 +1576,15 @@ } }, "node_modules/@babel/cli": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.19.3.tgz", - "integrity": "sha512-643/TybmaCAe101m2tSVHi9UKpETXP9c/Ff4mD2tAwkdP6esKIfaauZFc67vGEM6r9fekbEGid+sZhbEnSe3dg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.9.tgz", + "integrity": "sha512-I+02IfrTiSanpxJBlZQYb18qCxB6c2Ih371cVpfgIrPQrjAYkf45XxomTJOG8JBWX5GY35/+TmhCMdJ4ZPkL8Q==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.8", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", + "@jridgewell/trace-mapping": "^0.3.25", + "commander": "^6.2.0", + "convert-source-map": "^2.0.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.2.0", "make-dir": "^2.1.0", @@ -1598,17 +1599,55 @@ }, "optionalDependencies": { "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0" + "chokidar": "^3.6.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/cli/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "node_modules/@babel/cli/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@babel/cli/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/cli/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { "node": ">= 6" } @@ -4041,6 +4080,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -10077,28 +10126,55 @@ } }, "@babel/cli": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.19.3.tgz", - "integrity": "sha512-643/TybmaCAe101m2tSVHi9UKpETXP9c/Ff4mD2tAwkdP6esKIfaauZFc67vGEM6r9fekbEGid+sZhbEnSe3dg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.25.9.tgz", + "integrity": "sha512-I+02IfrTiSanpxJBlZQYb18qCxB6c2Ih371cVpfgIrPQrjAYkf45XxomTJOG8JBWX5GY35/+TmhCMdJ4ZPkL8Q==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.8", + "@jridgewell/trace-mapping": "^0.3.25", "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", + "chokidar": "^3.6.0", + "commander": "^6.2.0", + "convert-source-map": "^2.0.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.2.0", "make-dir": "^2.1.0", "slash": "^2.0.0" }, "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -11979,6 +12055,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/package.json b/package.json index 0075059c..391fb676 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "ws": "^8.8.0" }, "devDependencies": { - "@babel/cli": "^7.19.3", + "@babel/cli": "^7.25.9", "@babel/core": "^7.20.2", "@babel/preset-react": "^7.18.6", "babel-preset-minify": "^0.5.2", diff --git a/server/multiplayer/ServerTossupRoom.js b/server/multiplayer/ServerTossupRoom.js index 9dcb8c05..92df1dff 100644 --- a/server/multiplayer/ServerTossupRoom.js +++ b/server/multiplayer/ServerTossupRoom.js @@ -12,6 +12,7 @@ import getSetList from '../../database/qbreader/get-set-list.js'; import getNumPackets from '../../database/qbreader/get-num-packets.js'; import checkAnswer from 'qb-answer-checker'; +const BAN_DURATION = 1000 * 60 * 30; // 30 minutes export default class ServerTossupRoom extends TossupRoom { constructor (name, ownerId, isPermanent = false, categories = [], subcategories = [], alternateSubcategories = []) { @@ -23,8 +24,8 @@ export default class ServerTossupRoom extends TossupRoom { this.getRandomTossups = getRandomTossups; this.getSet = getSet; this.getSetList = getSetList; - this.bannedUserList = []; - this.kickedUserList = []; + this.bannedUserList = new Map(); + this.kickedUserList = new Map(); this.votekickList = []; this.lastVotekickTime = {}; @@ -38,6 +39,7 @@ export default class ServerTossupRoom extends TossupRoom { }; this.getSetList().then(setList => { this.setList = setList; }); + setInterval(this.cleanupExpiredBansAndKicks.bind(this), 5 * 60 * 1000); // 5 minutes } async message (userId, message) { @@ -62,11 +64,12 @@ export default class ServerTossupRoom extends TossupRoom { console.log('Checked, owner sent ban'); this.emitMessage({ type: 'confirm-ban', targetId, targetUsername }); - this.bannedUserList.push(targetId); + this.bannedUserList.set(targetId, Date.now()); } connection (socket, userId, username) { console.log(`Connection in room ${HEADER}${this.name}${ENDC} - ID of owner: ${OKBLUE}${this.ownerId}${ENDC} - userId: ${OKBLUE}${userId}${ENDC}, username: ${OKBLUE}${username}${ENDC} - with settings ${OKGREEN}${Object.keys(this.settings).map(key => [key, this.settings[key]].join(': ')).join('; ')};${ENDC}`); + this.cleanupExpiredBansAndKicks(); const isNew = !(userId in this.players); if (isNew) { this.players[userId] = new ServerPlayer(userId); } @@ -74,13 +77,13 @@ export default class ServerTossupRoom extends TossupRoom { this.sockets[userId] = socket; username = this.players[userId].safelySetUsername(username); - if (this.bannedUserList.includes(userId)) { + if (this.bannedUserList.has(userId)) { console.log(`Banned user ${userId} (${username}) tried to join a room`); this.sendToSocket(userId, { type: 'enforcing-removal', removalType: 'ban' }); return; } - if (this.kickedUserList.includes(userId)) { + if (this.kickedUserList.has(userId)) { console.log(`Kicked user ${userId} (${username}) tried to join a room`); this.sendToSocket(userId, { type: 'enforcing-removal', removalType: 'kick' }); return; @@ -153,6 +156,24 @@ export default class ServerTossupRoom extends TossupRoom { this.emitMessage({ type: 'chat-live-update', message, username, userId }); } + cleanupExpiredBansAndKicks () { + const now = Date.now(); + + this.bannedUserList.forEach((banTime, userId) => { + if (now - banTime > BAN_DURATION) { + this.bannedUserList.delete(userId); + console.log(`User ${userId} ban expired and removed from banned list`); + } + }); + + this.kickedUserList.forEach((kickTime, userId) => { + if (now - kickTime > BAN_DURATION) { + this.kickedUserList.delete(userId); + console.log(`User ${userId} kick expired and removed from kicked list`); + } + }); + } + close (userId) { if (this.buzzedIn === userId) { this.giveAnswer(userId, ''); @@ -266,15 +287,21 @@ export default class ServerTossupRoom extends TossupRoom { for (const votekick of this.votekickList) { if (votekick.exists(targetId)) { return; } } + let activePlayers = 0; + Object.keys(this.players).forEach(playerId => { + if (this.players[playerId].online) { + activePlayers += 1; + } + }); - const threshold = Math.max((Object.keys(this.players).length) - 1, 0); + const threshold = Math.max(activePlayers - 2, 2); const votekick = new Votekick(targetId, threshold, []); votekick.vote(userId); if (votekick.check()) { this.emitMessage({ type: 'successful-vk', targetUsername, targetId }); - this.kickedUserList.push(targetId); + this.kickedUserList.set(targetId, Date.now()); } else { - this.votekickList.push(votekick); + this.kickedUserList.set(targetId, Date.now()); this.emitMessage({ type: 'initiated-vk', targetUsername, threshold }); } } @@ -295,7 +322,7 @@ export default class ServerTossupRoom extends TossupRoom { thisVotekick.vote(userId); if (thisVotekick.check()) { this.emitMessage({ type: 'successful-vk', targetUsername, targetId }); - this.kickedUserList.push(targetId); + this.kickedUserList.set(targetId, Date.now()); } } }