mirror of https://github.com/nealey/microchat.git
It works.
This commit is contained in:
commit
52356a0036
|
@ -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.
|
|
@ -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))
|
||||||
|
}
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in New Issue