It works.

This commit is contained in:
Neale Pickett 2019-11-23 16:46:29 -08:00
commit 52356a0036
5 changed files with 269 additions and 0 deletions

12
README.md Normal file
View File

@ -0,0 +1,12 @@
μChat
=====
It's an itty-bitty web chat,
for people who aren't trying to screw each other over.
It doesn't do a lot of checks, announcements, etc.
because it presumes nobody will be using it who is trying to make life hard on anyone else.
I wrote this at Cyber Fire Foundry 15
because we needed a minimal chat client,
but the only machine we had to run it on was our router.

105
microchat.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"net/http"
"log"
"time"
"strconv"
"encoding/json"
)
type Message struct {
When int64
Who string
Text string
}
type Forum struct {
Name string
Log []Message
}
var foraByName map[string]*Forum
func newForum(name string) *Forum {
f := &Forum{
Name: name,
}
foraByName[name] = f
return f
}
func sayHandler(w http.ResponseWriter, r *http.Request) {
message := Message{
When: time.Now().Unix(),
Who: r.FormValue("who"),
Text: r.FormValue("text"),
}
log.Println(message)
forumName := r.FormValue("forum")
forum, ok := foraByName[forumName]
if !ok {
forum = newForum(forumName)
}
forum.Log = append(forum.Log, message)
w.WriteHeader(http.StatusOK)
}
func readHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
forumName := r.FormValue("forum")
entriesStr := r.FormValue("entries")
if entriesStr == "" {
entriesStr = "0"
}
entries, err := strconv.Atoi(entriesStr)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if entries <= 0 {
entries = 20
} else if entries > 500 {
entries = 500
}
forum, ok := foraByName[forumName]
if !ok {
http.NotFound(w, r)
return
}
pos := len(forum.Log) - entries
if pos < 0 {
pos = 0
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
e := json.NewEncoder(w)
e.Encode(forum.Log[pos:])
}
func main() {
http.HandleFunc("/say", sayHandler)
http.HandleFunc("/read", readHandler)
http.Handle("/", http.FileServer(http.Dir("static/")))
foraByName = map[string]*Forum{}
f := newForum("")
f.Log = []Message{
{
When: time.Now().Unix(),
Who: "(system)",
Text: "Welcome to μChat",
},
}
bind := ":8080"
log.Printf("Listening on %s", bind)
log.Fatal(http.ListenAndServe(bind, nil))
}

19
static/index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>μChat</title>
<script src="microchat.js" async></script>
<link rel="stylesheet" href="microchat.css">
</head>
<body>
<h1>μChat</h1>
<div id="chatlog"></div>
<form>
<input name="forum" value="" type="hidden">
<input name="who" placeholder="name">
<input name="text" placeholder="Message here...">
<input type="submit" value="send">
</form>
<div id="chattoast"></div>
</body>
</html>

27
static/microchat.css Normal file
View File

@ -0,0 +1,27 @@
body {
font-family: Roboto, sans-serif;
}
#chatlog {
height: 15em;
max-width: 80em;
overflow: scroll;
}
input[name="who"] {
width: 6em;
}
.when {
color: #888;
min-width: 10em;
font-size: small;
}
.who {
margin: auto 0.8em;
color: blue;
min-width: 5em;
display: inline-block;
text-align: right;
}

106
static/microchat.js Normal file
View File

@ -0,0 +1,106 @@
// jshint asi:true
function μchatInit(initEvent) {
let updateInterval
let lastMsg
function toast(message, timeout=5000) {
let p = document.createElement("p")
p.innerText = message
document.querySelector("#chattoast").appendChild(p)
setTimeout(e => { p.remove() }, timeout)
}
function input(event) {
event.preventDefault()
let form = event.target
let inp = form.elements.text
let body = new FormData(form)
console.log(form, body)
fetch("say", {
method: "POST",
body: body,
})
.then(resp => {
if (resp.ok) {
// Yay it was okay, reset input
inp.value = ""
} else {
toast("ERROR: DOES NOT COMPUTE")
console.log(resp)
}
})
.catch(err => {
toast("ERROR: DOES NOT COMPUTE")
console.log(err)
})
window.localStorage["who"] = form.elements.who.value
}
function updateLog(log) {
let lastLogMsg = log[log.length - 1]
if (
lastMsg &&
(lastLogMsg.When == lastMsg.When) &&
(lastLogMsg.Who == lastMsg.Who) &&
(lastLogMsg.Text == lastMsg.Text)
) {
return
}
lastMsg = lastLogMsg
let chatlog = document.querySelector("#chatlog")
while (chatlog.firstChild) {
chatlog.firstChild.remove()
}
for (let ll of log) {
let line = chatlog.appendChild(document.createElement("div"))
line.classList.add("line")
let when = line.appendChild(document.createElement("span"))
when.classList.add("when")
when.innerText = (new Date(ll.When * 1000)).toISOString()
let who = line.appendChild(document.createElement("span"))
who.classList.add("who")
who.innerText = ll.Who
let text = line.appendChild(document.createElement("span"))
text.classList.add("text")
text.innerText = ll.Text
}
chatlog.lastChild.scrollIntoView()
// trigger a fetch of chat log so the user gets some feedback
update()
}
function update(event) {
fetch("read")
.then(resp => {
if (resp.ok) {
resp.json()
.then(updateLog)
}
})
}
for (let f of document.forms) {
let who = window.localStorage["who"]
f.addEventListener("submit", input)
if (who) {
f.elements.who.value = who
}
}
updateInterval = setInterval(update, 3000)
update()
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", μchatInit)
} else {
μchatInit()
}