initial commit
This commit is contained in:
commit
5fe7ca30fd
8 changed files with 747 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/messages.db
|
||||
/config.json
|
||||
110
assets/code.js
Normal file
110
assets/code.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
const input = document.getElementById("input")
|
||||
const display = document.getElementById("displayBody")
|
||||
const glitch = document.getElementById("glitch")
|
||||
let lastMessage = undefined
|
||||
let listener
|
||||
|
||||
const addMessage = (from, content) => {
|
||||
const tr = document.createElement("tr")
|
||||
const fromTd = document.createElement("td")
|
||||
fromTd.innerText = from
|
||||
fromTd.setAttribute("class", from === "COMP/CON" ? "from compcon" : "from")
|
||||
|
||||
const textTd = document.createElement("td")
|
||||
textTd.innerText = content
|
||||
textTd.setAttribute("class", "content")
|
||||
|
||||
tr.append(fromTd, textTd)
|
||||
|
||||
display.appendChild(tr)
|
||||
tr.scrollIntoView()
|
||||
}
|
||||
|
||||
const makeConnection = () => {
|
||||
const ws = new WebSocket((window.location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/comm")
|
||||
|
||||
ws.onmessage = (msg) => {
|
||||
const message = JSON.parse(msg.data)
|
||||
switch (message.type) {
|
||||
case "error":
|
||||
addMessage("*", message.content)
|
||||
break
|
||||
case "message":
|
||||
addMessage(message.user, message.content)
|
||||
break
|
||||
case "history":
|
||||
addMessage(message.user, message.content)
|
||||
break
|
||||
case "ready":
|
||||
display.innerHTML = ""
|
||||
if (lastMessage) {
|
||||
localStorage.setItem("username", lastMessage)
|
||||
}
|
||||
addMessage("*", message.content)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = () => {
|
||||
setTimeout(makeConnection, 1000)
|
||||
}
|
||||
ws.onclose = () => {
|
||||
setTimeout(makeConnection, 1000)
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
if (localStorage.getItem("username")) {
|
||||
ws.send(JSON.stringify({
|
||||
msg: localStorage.getItem("username")
|
||||
}))
|
||||
}
|
||||
}
|
||||
listener = ws
|
||||
}
|
||||
|
||||
makeConnection()
|
||||
|
||||
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
listener.send(JSON.stringify({
|
||||
msg: input.innerText
|
||||
}))
|
||||
lastMessage = input.innerText
|
||||
e.preventDefault()
|
||||
input.innerText = ""
|
||||
}
|
||||
})
|
||||
|
||||
const fun = (pool) => {
|
||||
const len = Math.ceil(3 + (Math.random() * 15))
|
||||
let s = ""
|
||||
for (let i = 0; i < len; i++) {
|
||||
s = s + pool.at(Math.floor(Math.random() * pool.length))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
const pools = [
|
||||
[1, () => ["LIGHT THE FIRES", "green"],],
|
||||
[5, () => [fun("◯◉"), "cyan"],],
|
||||
[10, () => [fun("🮗🮖▓▧🮐█🮙▩▥▦🮕▤🮘▨░"), "white"],],
|
||||
[12, () => ["🯁🯂🯃🮲🮳", "white"]],
|
||||
[100, () => [fun(" !\"#$%&\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ'"), "gray"]],
|
||||
]
|
||||
|
||||
const spaz = () => {
|
||||
const r = Math.random() * 100
|
||||
for (const [chance, func] of pools) {
|
||||
if (r < chance) {
|
||||
const [text, color] = func()
|
||||
glitch.style.color = color
|
||||
glitch.innerText = text
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
spaz()
|
||||
|
||||
window.addEventListener("keydown", () => {
|
||||
spaz()
|
||||
})
|
||||
58
assets/index.html
Normal file
58
assets/index.html
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CENTCOM</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="code.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="bgWrap">
|
||||
<pre class="logo"> ◢◤◢█
|
||||
🬕🬂 🬴🬰 ▙▐ ▜▛ 🬕🬂 🬕🬨 🬺🬻 ◢◤◢
|
||||
▙▄ ▙▄ ▌🬨 ▐▌ ▙▄ ▙▟ ▌▐ ◢◤◢
|
||||
◢◤◢</pre>
|
||||
</div>
|
||||
<pre class="licenseHeader">
|
||||
LICENSE OWNER:
|
||||
RELEASE:
|
||||
TRIAL EXPIRY:
|
||||
COMPCON BUILD: </pre>
|
||||
<div class="licenseValues">
|
||||
<div class="yellow">TODD (EVALUATION COPY)</div>
|
||||
<div class="yellow">234-22b</div>
|
||||
<div class="syserror">NaN</div>
|
||||
<div id="glitch">ERR</div>
|
||||
</div>
|
||||
<pre class="trailerSlide">
|
||||
████◤
|
||||
███◤
|
||||
██◤
|
||||
█◤
|
||||
</pre>
|
||||
<div class="trailer"></div>
|
||||
</div>
|
||||
<div class="resources">
|
||||
CENTCOM RESOURCES ARE AVAILABLE AT: <a href="https://spaghetti.top/share/DND%20stuff/Lancer/Lancer%20-%20Core%20Book.pdf" target="_blank">HANDBOOK</a>
|
||||
<a href="https://spaghetti.top/share/DND%20stuff/Lancer/Lancer%20-%20Field%20Guide%20to%20the%20Karrakin%20Trade%20Baronies.pdf" target="_blank">BARONIES</a>
|
||||
<a href="https://spaghetti.top/share/DND%20stuff/Lancer/" target="_blank">MORE</a>
|
||||
|
||||
</div>
|
||||
<div class="display">
|
||||
<div class="padder"></div>
|
||||
<table id="displayTable">
|
||||
<tbody id="displayBody">
|
||||
<tr>
|
||||
<td>*</td>
|
||||
<td>Please enter your callsign. It will be remembered.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="cmdinput">
|
||||
<span>></span>
|
||||
<span id="input" contenteditable="true"></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
218
assets/style.css
Normal file
218
assets/style.css
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
@font-face {
|
||||
font-family: "Unscii";
|
||||
src: url("unscii-16.woff");
|
||||
}
|
||||
|
||||
@keyframes converge {
|
||||
0% {text-shadow: rgba(0, 255, 255, 0.8361107273433098) 6.0px 0 0, rgba(253, 72, 253, 0.9569224401158167) -5.0px 0 0;}
|
||||
1% {text-shadow: rgba(0, 255, 255, 0.8619150110069808) 5.95px 0 0, rgba(253, 72, 253, 0.9579050158210693) -4.95px 0 0;}
|
||||
2% {text-shadow: rgba(0, 255, 255, 0.854336177145079) 5.9px 0 0, rgba(253, 72, 253, 0.8426867483104116) -4.9px 0 0;}
|
||||
3% {text-shadow: rgba(0, 255, 255, 0.9236866898385188) 5.85px 0 0, rgba(253, 72, 253, 0.821628447103927) -4.85px 0 0;}
|
||||
4% {text-shadow: rgba(0, 255, 255, 0.8585164669305969) 5.8px 0 0, rgba(253, 72, 253, 0.9886992839559834) -4.8px 0 0;}
|
||||
5% {text-shadow: rgba(0, 255, 255, 0.8219830480816586) 5.75px 0 0, rgba(253, 72, 253, 0.9484480449115142) -4.75px 0 0;}
|
||||
6% {text-shadow: rgba(0, 255, 255, 0.8003809923844546) 5.7px 0 0, rgba(253, 72, 253, 0.8487031108723073) -4.7px 0 0;}
|
||||
7% {text-shadow: rgba(0, 255, 255, 0.8219776552014745) 5.65px 0 0, rgba(253, 72, 253, 0.9113050127726174) -4.65px 0 0;}
|
||||
8% {text-shadow: rgba(0, 255, 255, 0.9183556894624699) 5.6px 0 0, rgba(253, 72, 253, 0.8550701555053435) -4.6px 0 0;}
|
||||
9% {text-shadow: rgba(0, 255, 255, 0.805410457966203) 5.55px 0 0, rgba(253, 72, 253, 0.8329987799719435) -4.55px 0 0;}
|
||||
10% {text-shadow: rgba(0, 255, 255, 0.8398715925070842) 5.5px 0 0, rgba(253, 72, 253, 0.9482139656058877) -4.5px 0 0;}
|
||||
11% {text-shadow: rgba(0, 255, 255, 0.967386840612134) 5.45px 0 0, rgba(253, 72, 253, 0.8766333722942649) -4.45px 0 0;}
|
||||
12% {text-shadow: rgba(0, 255, 255, 0.897129710183972) 5.4px 0 0, rgba(253, 72, 253, 0.8057922325743391) -4.4px 0 0;}
|
||||
13% {text-shadow: rgba(0, 255, 255, 0.9053727693999201) 5.35px 0 0, rgba(253, 72, 253, 0.9054686470293101) -4.35px 0 0;}
|
||||
14% {text-shadow: rgba(0, 255, 255, 0.82040606118395) 5.3px 0 0, rgba(253, 72, 253, 0.8027565893856392) -4.3px 0 0;}
|
||||
15% {text-shadow: rgba(0, 255, 255, 0.9088155190766086) 5.25px 0 0, rgba(253, 72, 253, 0.8311901733209509) -4.25px 0 0;}
|
||||
16% {text-shadow: rgba(0, 255, 255, 0.830204171315196) 5.2px 0 0, rgba(253, 72, 253, 0.8610423937277876) -4.2px 0 0;}
|
||||
17% {text-shadow: rgba(0, 255, 255, 0.9993440956157889) 5.15px 0 0, rgba(253, 72, 253, 0.8785756628537958) -4.15px 0 0;}
|
||||
18% {text-shadow: rgba(0, 255, 255, 0.8440951076104983) 5.1px 0 0, rgba(253, 72, 253, 0.8367897342791965) -4.1px 0 0;}
|
||||
19% {text-shadow: rgba(0, 255, 255, 0.8794669548632236) 5.05px 0 0, rgba(253, 72, 253, 0.8658150502454226) -4.05px 0 0;}
|
||||
20% {text-shadow: rgba(0, 255, 255, 0.8296208755041845) 5.0px 0 0, rgba(253, 72, 253, 0.8508951332041956) -4.0px 0 0;}
|
||||
21% {text-shadow: rgba(0, 255, 255, 0.9374895228216961) 4.95px 0 0, rgba(253, 72, 253, 0.9407681729537316) -3.95px 0 0;}
|
||||
22% {text-shadow: rgba(0, 255, 255, 0.9472441467097896) 4.9px 0 0, rgba(253, 72, 253, 0.8108230906405419) -3.9px 0 0;}
|
||||
23% {text-shadow: rgba(0, 255, 255, 0.8841608212385841) 4.85px 0 0, rgba(253, 72, 253, 0.9467742323921511) -3.8499999999999996px 0 0;}
|
||||
24% {text-shadow: rgba(0, 255, 255, 0.9857281595178842) 4.8px 0 0, rgba(253, 72, 253, 0.983746771430065) -3.8px 0 0;}
|
||||
25% {text-shadow: rgba(0, 255, 255, 0.8901276017385813) 4.75px 0 0, rgba(253, 72, 253, 0.9006720333978879) -3.75px 0 0;}
|
||||
26% {text-shadow: rgba(0, 255, 255, 0.8420623794073254) 4.7px 0 0, rgba(253, 72, 253, 0.841030258954916) -3.7px 0 0;}
|
||||
27% {text-shadow: rgba(0, 255, 255, 0.80500475794336) 4.65px 0 0, rgba(253, 72, 253, 0.8719997374695452) -3.65px 0 0;}
|
||||
28% {text-shadow: rgba(0, 255, 255, 0.8263800795911168) 4.6px 0 0, rgba(253, 72, 253, 0.9560571433379201) -3.5999999999999996px 0 0;}
|
||||
29% {text-shadow: rgba(0, 255, 255, 0.9522238613350336) 4.55px 0 0, rgba(253, 72, 253, 0.8677264298768816) -3.55px 0 0;}
|
||||
30% {text-shadow: rgba(0, 255, 255, 0.8633010908515628) 4.5px 0 0, rgba(253, 72, 253, 0.8370655185763168) -3.5px 0 0;}
|
||||
31% {text-shadow: rgba(0, 255, 255, 0.833147207138388) 4.45px 0 0, rgba(253, 72, 253, 0.9032649254449511) -3.45px 0 0;}
|
||||
32% {text-shadow: rgba(0, 255, 255, 0.911593155082442) 4.4px 0 0, rgba(253, 72, 253, 0.9704127292549265) -3.4px 0 0;}
|
||||
33% {text-shadow: rgba(0, 255, 255, 0.8730770169688556) 4.35px 0 0, rgba(253, 72, 253, 0.8418315619280776) -3.3499999999999996px 0 0;}
|
||||
34% {text-shadow: rgba(0, 255, 255, 0.8254344860492141) 4.3px 0 0, rgba(253, 72, 253, 0.945122258350945) -3.3px 0 0;}
|
||||
35% {text-shadow: rgba(0, 255, 255, 0.8994536950486153) 4.25px 0 0, rgba(253, 72, 253, 0.9635174671899864) -3.25px 0 0;}
|
||||
36% {text-shadow: rgba(0, 255, 255, 0.8059786830820536) 4.2px 0 0, rgba(253, 72, 253, 0.8107823840459341) -3.2px 0 0;}
|
||||
37% {text-shadow: rgba(0, 255, 255, 0.8356685072101536) 4.15px 0 0, rgba(253, 72, 253, 0.8490396342365222) -3.15px 0 0;}
|
||||
38% {text-shadow: rgba(0, 255, 255, 0.870306753026597) 4.1px 0 0, rgba(253, 72, 253, 0.8684046884770916) -3.0999999999999996px 0 0;}
|
||||
39% {text-shadow: rgba(0, 255, 255, 0.829512957206739) 4.05px 0 0, rgba(253, 72, 253, 0.920924832912171) -3.05px 0 0;}
|
||||
40% {text-shadow: rgba(0, 255, 255, 0.981042227568678) 4.0px 0 0, rgba(253, 72, 253, 0.8478604614587707) -3.0px 0 0;}
|
||||
41% {text-shadow: rgba(0, 255, 255, 0.9028953642095886) 3.9499999999999997px 0 0, rgba(253, 72, 253, 0.9836375058993726) -2.9499999999999997px 0 0;}
|
||||
42% {text-shadow: rgba(0, 255, 255, 0.8298506090024876) 3.9px 0 0, rgba(253, 72, 253, 0.8122960604729693) -2.9px 0 0;}
|
||||
43% {text-shadow: rgba(0, 255, 255, 0.9574737190797513) 3.85px 0 0, rgba(253, 72, 253, 0.8207214451650879) -2.85px 0 0;}
|
||||
44% {text-shadow: rgba(0, 255, 255, 0.9912354008852822) 3.8px 0 0, rgba(253, 72, 253, 0.9258944470696935) -2.8px 0 0;}
|
||||
45% {text-shadow: rgba(0, 255, 255, 0.964617559155446) 3.75px 0 0, rgba(253, 72, 253, 0.8413653094926936) -2.75px 0 0;}
|
||||
46% {text-shadow: rgba(0, 255, 255, 0.8556860472070148) 3.6999999999999997px 0 0, rgba(253, 72, 253, 0.8039703790715326) -2.6999999999999997px 0 0;}
|
||||
47% {text-shadow: rgba(0, 255, 255, 0.9786225078037258) 3.65px 0 0, rgba(253, 72, 253, 0.8532601491001363) -2.65px 0 0;}
|
||||
48% {text-shadow: rgba(0, 255, 255, 0.8136939294439446) 3.5999999999999996px 0 0, rgba(253, 72, 253, 0.9066287030351223) -2.5999999999999996px 0 0;}
|
||||
49% {text-shadow: rgba(0, 255, 255, 0.9115638029179485) 3.55px 0 0, rgba(253, 72, 253, 0.8155810075227841) -2.55px 0 0;}
|
||||
50% {text-shadow: rgba(0, 255, 255, 0.8486201913333553) 3.5px 0 0, rgba(253, 72, 253, 0.8457649243118975) -2.5px 0 0;}
|
||||
51% {text-shadow: rgba(0, 255, 255, 0.8414566201644388) 3.4499999999999997px 0 0, rgba(253, 72, 253, 0.9361224309069724) -2.4499999999999997px 0 0;}
|
||||
52% {text-shadow: rgba(0, 255, 255, 0.8359647720401094) 3.4px 0 0, rgba(253, 72, 253, 0.8149059343179613) -2.4px 0 0;}
|
||||
53% {text-shadow: rgba(0, 255, 255, 0.9116885963738237) 3.3499999999999996px 0 0, rgba(253, 72, 253, 0.8478810737968121) -2.3499999999999996px 0 0;}
|
||||
54% {text-shadow: rgba(0, 255, 255, 0.9156512221006986) 3.3px 0 0, rgba(253, 72, 253, 0.8255903859323139) -2.3px 0 0;}
|
||||
55% {text-shadow: rgba(0, 255, 255, 0.8153528842487034) 3.25px 0 0, rgba(253, 72, 253, 0.8994146728664133) -2.25px 0 0;}
|
||||
56% {text-shadow: rgba(0, 255, 255, 0.8635217426110132) 3.1999999999999997px 0 0, rgba(253, 72, 253, 0.8100068480800539) -2.1999999999999997px 0 0;}
|
||||
57% {text-shadow: rgba(0, 255, 255, 0.8694106443899178) 3.15px 0 0, rgba(253, 72, 253, 0.8650065669920302) -2.15px 0 0;}
|
||||
58% {text-shadow: rgba(0, 255, 255, 0.9205282486640333) 3.0999999999999996px 0 0, rgba(253, 72, 253, 0.9146371017699718) -2.0999999999999996px 0 0;}
|
||||
59% {text-shadow: rgba(0, 255, 255, 0.9216335826736727) 3.05px 0 0, rgba(253, 72, 253, 0.9535489883131107) -2.05px 0 0;}
|
||||
60% {text-shadow: rgba(0, 255, 255, 0.8678550560876669) 3.0px 0 0, rgba(253, 72, 253, 0.9922895844184012) -2.0px 0 0;}
|
||||
61% {text-shadow: rgba(0, 255, 255, 0.8047208304855602) 2.9499999999999997px 0 0, rgba(253, 72, 253, 0.8072053478662209) -1.9499999999999997px 0 0;}
|
||||
62% {text-shadow: rgba(0, 255, 255, 0.8742226362552165) 2.9px 0 0, rgba(253, 72, 253, 0.9882488598219596) -1.9px 0 0;}
|
||||
63% {text-shadow: rgba(0, 255, 255, 0.8187279699943009) 2.8499999999999996px 0 0, rgba(253, 72, 253, 0.847193828631679) -1.8499999999999996px 0 0;}
|
||||
64% {text-shadow: rgba(0, 255, 255, 0.915873594484333) 2.8px 0 0, rgba(253, 72, 253, 0.9951780311982585) -1.7999999999999998px 0 0;}
|
||||
65% {text-shadow: rgba(0, 255, 255, 0.9091957667405316) 2.75px 0 0, rgba(253, 72, 253, 0.9785125139107118) -1.75px 0 0;}
|
||||
66% {text-shadow: rgba(0, 255, 255, 0.9732074805184896) 2.6999999999999997px 0 0, rgba(253, 72, 253, 0.8549161753597928) -1.6999999999999997px 0 0;}
|
||||
67% {text-shadow: rgba(0, 255, 255, 0.9942565533650556) 2.65px 0 0, rgba(253, 72, 253, 0.8763648231855105) -1.65px 0 0;}
|
||||
68% {text-shadow: rgba(0, 255, 255, 0.9965568662919517) 2.5999999999999996px 0 0, rgba(253, 72, 253, 0.9761753235501034) -1.5999999999999996px 0 0;}
|
||||
69% {text-shadow: rgba(0, 255, 255, 0.955938836304943) 2.55px 0 0, rgba(253, 72, 253, 0.9003910147622634) -1.5499999999999998px 0 0;}
|
||||
70% {text-shadow: rgba(0, 255, 255, 0.9485273044201737) 2.5px 0 0, rgba(253, 72, 253, 0.9807777183990174) -1.5px 0 0;}
|
||||
71% {text-shadow: rgba(0, 255, 255, 0.9674912652650286) 2.4499999999999997px 0 0, rgba(253, 72, 253, 0.9022210932456923) -1.4499999999999997px 0 0;}
|
||||
72% {text-shadow: rgba(0, 255, 255, 0.9717372249876183) 2.4px 0 0, rgba(253, 72, 253, 0.8765635698101958) -1.4px 0 0;}
|
||||
73% {text-shadow: rgba(0, 255, 255, 0.9645985514947042) 2.3499999999999996px 0 0, rgba(253, 72, 253, 0.8633552189493626) -1.3499999999999996px 0 0;}
|
||||
74% {text-shadow: rgba(0, 255, 255, 0.9930937453603206) 2.3px 0 0, rgba(253, 72, 253, 0.8051761331103191) -1.2999999999999998px 0 0;}
|
||||
75% {text-shadow: rgba(0, 255, 255, 0.9967694300664716) 2.25px 0 0, rgba(253, 72, 253, 0.8850713876932538) -1.25px 0 0;}
|
||||
76% {text-shadow: rgba(0, 255, 255, 0.857931878599814) 2.1999999999999997px 0 0, rgba(253, 72, 253, 0.9484567518701348) -1.1999999999999997px 0 0;}
|
||||
77% {text-shadow: rgba(0, 255, 255, 0.8065303268761479) 2.15px 0 0, rgba(253, 72, 253, 0.9861716026231071) -1.15px 0 0;}
|
||||
78% {text-shadow: rgba(0, 255, 255, 0.8797138323979175) 2.0999999999999996px 0 0, rgba(253, 72, 253, 0.9327605217362944) -1.0999999999999996px 0 0;}
|
||||
79% {text-shadow: rgba(0, 255, 255, 0.8064192594575359) 2.05px 0 0, rgba(253, 72, 253, 0.9752686628797701) -1.0499999999999998px 0 0;}
|
||||
80% {text-shadow: rgba(0, 255, 255, 0.8578695134814783) 2.0px 0 0, rgba(253, 72, 253, 0.9597123400872934) -1.0px 0 0;}
|
||||
81% {text-shadow: rgba(0, 255, 255, 0.8465689527222937) 1.9500000000000002px 0 0, rgba(253, 72, 253, 0.9362544272288554) -0.9500000000000002px 0 0;}
|
||||
82% {text-shadow: rgba(0, 255, 255, 0.9600859379791397) 1.8999999999999995px 0 0, rgba(253, 72, 253, 0.9445462866232998) -0.8999999999999995px 0 0;}
|
||||
83% {text-shadow: rgba(0, 255, 255, 0.9741472675714937) 1.8499999999999996px 0 0, rgba(253, 72, 253, 0.9979583491429695) -0.8499999999999996px 0 0;}
|
||||
84% {text-shadow: rgba(0, 255, 255, 0.8522887397519873) 1.7999999999999998px 0 0, rgba(253, 72, 253, 0.8642087114124946) -0.7999999999999998px 0 0;}
|
||||
85% {text-shadow: rgba(0, 255, 255, 0.8935576284804552) 1.75px 0 0, rgba(253, 72, 253, 0.9995037786832606) -0.75px 0 0;}
|
||||
86% {text-shadow: rgba(0, 255, 255, 0.9591551924825271) 1.7000000000000002px 0 0, rgba(253, 72, 253, 0.8895031911672213) -0.7000000000000002px 0 0;}
|
||||
87% {text-shadow: rgba(0, 255, 255, 0.847221976351772) 1.6499999999999995px 0 0, rgba(253, 72, 253, 0.8897083979984515) -0.6499999999999995px 0 0;}
|
||||
88% {text-shadow: rgba(0, 255, 255, 0.982831513382503) 1.5999999999999996px 0 0, rgba(253, 72, 253, 0.8938824711432813) -0.5999999999999996px 0 0;}
|
||||
89% {text-shadow: rgba(0, 255, 255, 0.8543613277389507) 1.5499999999999998px 0 0, rgba(253, 72, 253, 0.9537350423795994) -0.5499999999999998px 0 0;}
|
||||
90% {text-shadow: rgba(0, 255, 255, 0.893017776047433) 1.5px 0 0, rgba(253, 72, 253, 0.9956040213561244) -0.5px 0 0;}
|
||||
91% {text-shadow: rgba(0, 255, 255, 0.9679434395903915) 1.4500000000000002px 0 0, rgba(253, 72, 253, 0.8782724557074016) -0.4500000000000002px 0 0;}
|
||||
92% {text-shadow: rgba(0, 255, 255, 0.8726705168167862) 1.3999999999999995px 0 0, rgba(253, 72, 253, 0.8826217500327997) -0.39999999999999947px 0 0;}
|
||||
93% {text-shadow: rgba(0, 255, 255, 0.9966087235235863) 1.3499999999999996px 0 0, rgba(253, 72, 253, 0.8018138119722085) -0.34999999999999964px 0 0;}
|
||||
94% {text-shadow: rgba(0, 255, 255, 0.9491747745601509) 1.2999999999999998px 0 0, rgba(253, 72, 253, 0.829179137123354) -0.2999999999999998px 0 0;}
|
||||
95% {text-shadow: rgba(0, 255, 255, 0.9127441956297994) 1.25px 0 0, rgba(253, 72, 253, 0.8577438912497287) -0.25px 0 0;}
|
||||
96% {text-shadow: rgba(0, 255, 255, 0.9301915605785521) 1.1999999999999993px 0 0, rgba(253, 72, 253, 0.8271745353254621) -0.1999999999999993px 0 0;}
|
||||
97% {text-shadow: rgba(0, 255, 255, 0.8860706345525079) 1.1499999999999995px 0 0, rgba(253, 72, 253, 0.9882951178389682) -0.14999999999999947px 0 0;}
|
||||
98% {text-shadow: rgba(0, 255, 255, 0.8376062092672816) 1.0999999999999996px 0 0, rgba(253, 72, 253, 0.9615234693412842) -0.09999999999999964px 0 0;}
|
||||
99% {text-shadow: rgba(0, 255, 255, 0.8046174532158784) 1.0499999999999998px 0 0, rgba(253, 72, 253, 0.8547331608286676) -0.04999999999999982px 0 0;}
|
||||
100% {
|
||||
text-shadow: rgba(0, 255, 255, 0.8492590734360018) 1.0px 0 0, rgba(253, 72, 253, 0.8562549485674519) -0.0px 0 0;
|
||||
color: rgba(253, 72, 253);
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: Unscii, monospace;
|
||||
font-size: 16px;
|
||||
background: #000;
|
||||
margin: 0;
|
||||
color: red;
|
||||
padding: 0;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: Unscii, monospace;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bgWrap {
|
||||
background: black;
|
||||
}
|
||||
|
||||
.yellow {
|
||||
color: #e3f44d;
|
||||
}
|
||||
|
||||
.logo, .trailerSlide {
|
||||
background: #1e992a;
|
||||
color: black;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.trailer {
|
||||
height: 64px;
|
||||
background: #1e992a;
|
||||
flex-grow: 1;
|
||||
}
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.syserror {
|
||||
color: red;
|
||||
animation: 1s blink step-start infinite;
|
||||
}
|
||||
|
||||
.display {
|
||||
height: 256px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#displayTable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cmdinput {
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.cmdinput > span:first-child {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
#input {
|
||||
flex-grow: 1;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
a, a:link, a:visited {
|
||||
color: #8686ff;
|
||||
text-decoration: none;
|
||||
text-underline: none;
|
||||
}
|
||||
|
||||
a:link:before {
|
||||
content: "▹";
|
||||
}
|
||||
|
||||
.padder {
|
||||
height: 230px;
|
||||
}
|
||||
|
||||
.from {
|
||||
color: #ff8f02;
|
||||
text-align: right;
|
||||
border-right: transparent 8px solid;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.compcon {
|
||||
animation: converge 1.4s ease-out forwards;
|
||||
}
|
||||
BIN
assets/unscii-16.woff
Normal file
BIN
assets/unscii-16.woff
Normal file
Binary file not shown.
338
centcom.go
Normal file
338
centcom.go
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
const CONFIGFILE = "./config.json"
|
||||
|
||||
type Client struct {
|
||||
Conn *websocket.Conn
|
||||
Name string
|
||||
Mu sync.Mutex
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Mu sync.Mutex
|
||||
Clients []*Client
|
||||
Db *sql.DB
|
||||
Cef *CEF
|
||||
}
|
||||
|
||||
type CEF struct {
|
||||
Conn *websocket.Conn
|
||||
FromServer chan string
|
||||
ToServer chan string
|
||||
Kill chan error
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Type string `json:"type"`
|
||||
User string `json:"user"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Listen string `json:"listen"`
|
||||
Webhook string `json:"webhook"`
|
||||
Cef struct {
|
||||
Channel string `json:"channel"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
} `json:"cef"`
|
||||
Users []string `json:"users"`
|
||||
}
|
||||
|
||||
var config Config
|
||||
var server = Server{
|
||||
Clients: make([]*Client, 0),
|
||||
}
|
||||
|
||||
func (s *Server) KillClient(client *Client) {
|
||||
s.Mu.Lock()
|
||||
defer s.Mu.Unlock()
|
||||
for i, _ := range s.Clients {
|
||||
if client == s.Clients[i] {
|
||||
s.Clients = append(s.Clients[:i], s.Clients[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Send(struc any) {
|
||||
c.Mu.Lock()
|
||||
defer c.Mu.Unlock()
|
||||
err := c.Conn.WriteJSON(struc)
|
||||
if err != nil {
|
||||
server.KillClient(c)
|
||||
}
|
||||
}
|
||||
|
||||
func Webhook(message Message) {
|
||||
jsonValue, _ := json.Marshal(struct {
|
||||
Content string `json:"content"`
|
||||
Username string `json:"username"`
|
||||
}{
|
||||
Content: message.Content,
|
||||
Username: message.User,
|
||||
})
|
||||
_, err := http.Post(config.Webhook, "application/json", bytes.NewBuffer(jsonValue))
|
||||
if err != nil {
|
||||
log.Println("webhook", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) BroadcastMessage(from string, content string) {
|
||||
message := Message{
|
||||
Type: "message",
|
||||
User: from,
|
||||
Content: content,
|
||||
}
|
||||
for _, c := range s.Clients {
|
||||
c.Send(message)
|
||||
}
|
||||
tx, err := s.Db.Begin()
|
||||
stmt, err := tx.Prepare("insert into messages(`type`, `user`, `content`) values(?, ?, ?)")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
_, err = stmt.Exec("message", from, content)
|
||||
if err != nil {
|
||||
log.Println(stmt)
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
go Webhook(message)
|
||||
go s.CEFSend(message)
|
||||
}
|
||||
|
||||
func (s *Server) CEFSend(message Message) {
|
||||
msg := fmt.Sprintf("NPC %s %s :%s", config.Cef.Channel, message.User, message.Content)
|
||||
select {
|
||||
case s.Cef.ToServer <- msg:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) SendHistory(client *Client) {
|
||||
// Blast
|
||||
rows, err := s.Db.Query("SELECT `user`, `content` FROM messages ORDER BY rowid ASC")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
var user, content string
|
||||
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&user, &content)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
client.Send(Message{
|
||||
Type: "history",
|
||||
User: user,
|
||||
Content: content,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
client.Send(Message{
|
||||
Type: "ready",
|
||||
User: strconv.Itoa(len(server.Clients)),
|
||||
Content: "Welcome back, " + client.Name,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func loadConfig() {
|
||||
data, err := os.ReadFile(CONFIGFILE)
|
||||
if err != nil {
|
||||
log.Panicln("Could not read config, ", err)
|
||||
}
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
log.Panicln("Could not load config, ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func watchConfig() {
|
||||
initialStat, _ := os.Stat(CONFIGFILE)
|
||||
|
||||
for {
|
||||
stat, _ := os.Stat(CONFIGFILE)
|
||||
|
||||
if stat.Size() != initialStat.Size() || stat.ModTime() != initialStat.ModTime() {
|
||||
loadConfig()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CEF) Send(m string) {
|
||||
log.Println("[CEF-O]", m)
|
||||
err := c.Conn.WriteMessage(websocket.TextMessage, []byte(m))
|
||||
if err != nil {
|
||||
log.Println("[CEF-S]", err)
|
||||
c.Kill <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CEF) Loop() {
|
||||
for {
|
||||
_, message, err := c.Conn.ReadMessage()
|
||||
log.Println("[CEF]", string(message), err)
|
||||
if err != nil {
|
||||
c.Kill <- err
|
||||
return
|
||||
}
|
||||
c.FromServer <- string(message)
|
||||
}
|
||||
}
|
||||
|
||||
func BasicCef() {
|
||||
c, _, err := websocket.DefaultDialer.Dial("wss://cef.icu/chat", nil)
|
||||
defer c.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cef := &CEF{
|
||||
Conn: c,
|
||||
FromServer: make(chan string),
|
||||
ToServer: make(chan string),
|
||||
Kill: make(chan error),
|
||||
}
|
||||
defer close(cef.FromServer)
|
||||
defer close(cef.ToServer)
|
||||
defer close(cef.Kill)
|
||||
server.Cef = cef
|
||||
cef.Send("CAP REQ :account-notify account-tag away-notify batch chghost cef/extended-names draft/chathistory draft/multiline draft/event-playback draft/relaymsg echo-message extended-join invite-notify labeled-response message-tags multi-prefix sasl server-time setname userhost-in-names")
|
||||
cef.Send("NICK " + config.Cef.Username)
|
||||
cef.Send("USER " + config.Cef.Username + " . . :cool dude")
|
||||
cef.Send("AUTHENTICATE PLAIN")
|
||||
auth := fmt.Sprintf("%s\000%s\000%s", config.Cef.Username, config.Cef.Username, config.Cef.Password)
|
||||
cef.Send("AUTHENTICATE " + base64.StdEncoding.EncodeToString([]byte(auth)))
|
||||
cef.Send("CAP END")
|
||||
// cef.Send("JOIN " + config.Cef.Channel)
|
||||
|
||||
go cef.Loop()
|
||||
for {
|
||||
select {
|
||||
case msg := <-cef.FromServer:
|
||||
split := strings.Split(msg, " ")
|
||||
if len(split) > 2 {
|
||||
if split[1] == "PING" {
|
||||
cef.Send("PONG " + split[2])
|
||||
}
|
||||
}
|
||||
case msg := <-cef.ToServer:
|
||||
cef.Send(msg)
|
||||
case killError := <-cef.Kill:
|
||||
log.Println("[CEF]", killError)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{} // use default options
|
||||
|
||||
func comm(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Print("upgrade:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
var client = &Client{
|
||||
Conn: c,
|
||||
Name: "",
|
||||
}
|
||||
setup := false
|
||||
var inbound struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
for {
|
||||
_, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("read:", err)
|
||||
break
|
||||
}
|
||||
err = json.Unmarshal(message, &inbound)
|
||||
if err != nil {
|
||||
log.Println("unmarshal", err)
|
||||
return
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
if !setup {
|
||||
if slices.Contains(config.Users, inbound.Msg) {
|
||||
server.Mu.Lock()
|
||||
server.Clients = append(server.Clients, client)
|
||||
server.Mu.Unlock()
|
||||
defer server.KillClient(client)
|
||||
setup = true
|
||||
client.Name = inbound.Msg
|
||||
server.SendHistory(client)
|
||||
} else {
|
||||
err := c.WriteJSON(Message{
|
||||
Type: "error",
|
||||
User: "",
|
||||
Content: "LANCER NOT FOUND",
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("writejson", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
server.BroadcastMessage(client.Name, inbound.Msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CefDaemon() {
|
||||
for {
|
||||
BasicCef()
|
||||
time.Sleep(60)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
loadConfig()
|
||||
go watchConfig()
|
||||
var addr = flag.String("addr", config.Listen, "http service address")
|
||||
db, err := sql.Open("sqlite3", "./messages.db")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
server.Db = db
|
||||
db.Exec("CREATE TABLE messages(`type` text, `user` text, `content` text)")
|
||||
defer db.Close()
|
||||
go CefDaemon()
|
||||
|
||||
flag.Parse()
|
||||
log.SetFlags(0)
|
||||
fs := http.FileServer(http.Dir("./assets"))
|
||||
http.Handle("/", fs)
|
||||
http.HandleFunc("/comm", comm)
|
||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||
}
|
||||
13
config.example.json
Normal file
13
config.example.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"listen": "0.0.0.0:7305",
|
||||
"webhook": "",
|
||||
"cef": {
|
||||
"channel": "#test",
|
||||
"username": "Centcom",
|
||||
"password": "Password"
|
||||
},
|
||||
"users": [
|
||||
"COMP/CON",
|
||||
"Guest"
|
||||
]
|
||||
}
|
||||
8
go.mod
Normal file
8
go.mod
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module CENTCOM
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/mattn/go-sqlite3 v1.14.33
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue