From da63df8e4e393963d8e11a62e5034c69013ad627 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 15 Jul 2014 01:30:47 +0000 Subject: [PATCH 01/23] A working version! --- chunktail.cgi.go | 73 ++++++++++++++++++++++++++++++++ index.html | 107 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 chunktail.cgi.go create mode 100644 index.html diff --git a/chunktail.cgi.go b/chunktail.cgi.go new file mode 100644 index 0000000..65d59f6 --- /dev/null +++ b/chunktail.cgi.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "log" + "os" + "bufio" + "strconv" + "net/http" + "net/http/cgi" + "time" +) + +type Handler struct { + cgi.Handler +} + + +func tail(w http.ResponseWriter, pos int) { + f, err := os.Open("/home/neale/bot/neale/log/log") + if err != nil { + log.Fatal(err) + } + defer f.Close() + + + _, err = f.Seek(int64(pos), 0) + if err != nil { + log.Fatal(err) + } + bf := bufio.NewScanner(f) + for bf.Scan() { + t := bf.Text() + pos += len(t) + 1 // XXX: this breaks if we ever see \r\n + fmt.Fprintf(w, "data: %s\n", t) + } + fmt.Fprintf(w, "id: %d\n\n", pos) +} + +func handleCommand(w http.ResponseWriter, text string) { + fn := fmt.Sprintf("/home/neale/bot/neale/outq/cgi.%d", time.Now().Unix()) + f, err := os.Create(fn) + if err != nil { + fmt.Fprintln(w, "NO") + fmt.Fprintln(w, err) + return + } + defer f.Close() + fmt.Fprintf(f, "PRIVMSG #tron :%s\n", text) + + fmt.Fprintln(w, "OK") +} + + +func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.FormValue("type") { + case "command": + w.Header().Set("Content-Type", "text/plain") + handleCommand(w, r.Form.Get("text")) + default: + w.Header().Set("Content-Type", "text/event-stream") + id, _ := strconv.Atoi(os.Getenv("HTTP_LAST_EVENT_ID")) + tail(w, id) + } +} + +func main() { + h := Handler{} + if err := cgi.Serve(h); err != nil { + log.Fatal(err) + } +} + diff --git a/index.html b/index.html new file mode 100644 index 0000000..4cea215 --- /dev/null +++ b/index.html @@ -0,0 +1,107 @@ + + + + LOL + + + + +
+
+ + + +
+ + From 3ef3cf3b0b355eb4206c4b292fce8729df9db5b7 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sat, 19 Jul 2014 00:09:59 +0000 Subject: [PATCH 02/23] Welp. Primitive IRC client in go. --- irc.go | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 irc.go diff --git a/irc.go b/irc.go new file mode 100644 index 0000000..b3ec4b0 --- /dev/null +++ b/irc.go @@ -0,0 +1,176 @@ +package main + +import ( + "bufio" + "crypto/tls" + "flag" + "fmt" + "log" + "net" + "os" + "strings" +) + +type Message struct { + Command string + FullSender string + Sender string + Forum string + Args []string + Text string +} + +func (m Message) String() string { + a := append([]string{m.FullSender}, m.Args...) + args :=strings.Join(a, " ") + return fmt.Sprintf("%s %s %s %s %s", m.Command, m.Sender, m.Forum, args, m.Text) +} + +func nuhost(s string) (string, string, string) { + var parts []string + + parts = strings.SplitN(s, "!", 2) + if len(parts) == 1 { + return s, "", "" + } + n := parts[0] + parts = strings.SplitN(parts[1], "@", 2) + if len(parts) == 1 { + return s, "", "" + } + return n, parts[0], parts[1] +} + +func connect(host string, dotls bool) (net.Conn, error) { + if dotls { + config := &tls.Config{ + InsecureSkipVerify: true, + } + return tls.Dial("tcp", host, config) + } else { + return net.Dial("tcp", host) + } +} + +func readLoop(conn net.Conn, inq chan<- string) { + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + inq <- scanner.Text() + } + close(inq) +} + +func writeLoop(conn net.Conn, outq <-chan string) { + for v := range outq { + fmt.Println(v) + fmt.Fprintln(conn, v) + } +} + +func parse(v string) (Message, error) { + var m Message + var parts []string + var lhs string + + fmt.Println(v) + parts = strings.SplitN(v, " :", 2) + if len(parts) == 2 { + lhs = parts[0] + m.Text = parts[1] + } else { + lhs = v + m.Text = "" + } + + m.FullSender = "." + m.Forum = "." + m.Sender = "." + + parts = strings.Split(lhs, " ") + if parts[0][0] == ':' { + m.FullSender = parts[0][1:] + parts = parts[1:] + + n, u, _ := nuhost(m.FullSender) + if u != "" { + m.Sender = n + } + } + + m.Command = strings.ToUpper(parts[0]) + switch (m.Command) { + case "PRIVMSG", "NOTICE": + n, u, _ := nuhost(parts[1]) + if u == "" { + m.Forum = m.Sender + } else { + m.Forum = n + } + case "PART", "MODE", "TOPIC", "KICK": + m.Forum = parts[1] + case "JOIN": + if len(parts) == 1 { + m.Forum = m.Text + m.Text = "" + } else { + m.Forum = parts[1] + } + case "INVITE": + if m.Text != "" { + m.Forum = m.Text + m.Text = "" + } else { + m.Forum = parts[2] + } + case "NICK": + m.FullSender = parts[1] + m.Forum = m.FullSender + } + + return m, nil +} + +func dispatch(outq chan<- string, m Message) { + log.Print(m.String()) + switch (m.Command) { + case "PING": + outq <- "PONG :" + m.Text + } +} + +func usage() { + fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] HOST:PORT\n", os.Args[0]) + flag.PrintDefaults() +} + +func main() { + dotls := flag.Bool("notls", true, "Disable TLS security") + + flag.Parse() + if flag.NArg() != 1 { + fmt.Fprintln(os.Stderr, "Error: must specify host") + os.Exit(69) + } + + conn, err := connect(flag.Arg(0), *dotls) + if err != nil { + log.Fatal(err) + } + + inq := make(chan string) + outq := make(chan string) + go readLoop(conn, inq) + go writeLoop(conn, outq) + + outq <- "NICK neale" + outq <- "USER neale neale neale :neale" + for v := range inq { + p, err := parse(v) + if err != nil { + continue + } + dispatch(outq, p) + } + + close(outq) +} From 5c178b38f9d9df0ce92450cdc8663d57de59542e Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 21 Jul 2014 05:03:53 +0000 Subject: [PATCH 03/23] Logging and reading outq --- irc.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/irc.go b/irc.go index b3ec4b0..1eee9b4 100644 --- a/irc.go +++ b/irc.go @@ -9,23 +9,30 @@ import ( "net" "os" "strings" + "time" ) +var running bool = true + type Message struct { - Command string + Command string FullSender string - Sender string - Forum string - Args []string - Text string + Sender string + Forum string + Args []string + Text string } func (m Message) String() string { a := append([]string{m.FullSender}, m.Args...) - args :=strings.Join(a, " ") + args := strings.Join(a, " ") return fmt.Sprintf("%s %s %s %s %s", m.Command, m.Sender, m.Forum, args, m.Text) } +func Log(m Message) { + fmt.Printf("%d %s\n", time.Now().Unix(), m.String()) +} + func nuhost(s string) (string, string, string) { var parts []string @@ -81,7 +88,7 @@ func parse(v string) (Message, error) { lhs = v m.Text = "" } - + m.FullSender = "." m.Forum = "." m.Sender = "." @@ -90,15 +97,15 @@ func parse(v string) (Message, error) { if parts[0][0] == ':' { m.FullSender = parts[0][1:] parts = parts[1:] - + n, u, _ := nuhost(m.FullSender) if u != "" { m.Sender = n } } - + m.Command = strings.ToUpper(parts[0]) - switch (m.Command) { + switch m.Command { case "PRIVMSG", "NOTICE": n, u, _ := nuhost(parts[1]) if u == "" { @@ -126,32 +133,74 @@ func parse(v string) (Message, error) { m.FullSender = parts[1] m.Forum = m.FullSender } - + return m, nil } func dispatch(outq chan<- string, m Message) { - log.Print(m.String()) - switch (m.Command) { + Log(m) + switch m.Command { case "PING": outq <- "PONG :" + m.Text } } +func handleInfile(path string, outq chan<- string) { + f, err := os.Open(path) + if (err != nil) { + return + } + defer f.Close() + os.Remove(path) + inf := bufio.NewScanner(f) + for inf.Scan() { + outq <- inf.Text() + } +} + +func monitorDirectory(dirname string, dir *os.File, outq chan<- string) { + latest := time.Unix(0, 0) + for running { + fi, err := dir.Stat() + if err != nil { + break + } + current := fi.ModTime() + if current.After(latest) { + latest = current + dn, _ := dir.Readdirnames(0) + for _, fn := range dn { + path := dirname + string(os.PathSeparator) + fn + handleInfile(path, outq) + } + _, _ = dir.Seek(0, 0) + } + time.Sleep(500 * time.Millisecond) + } +} + func usage() { fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] HOST:PORT\n", os.Args[0]) flag.PrintDefaults() } func main() { + log.SetFlags(log.LstdFlags | log.Lshortfile) dotls := flag.Bool("notls", true, "Disable TLS security") + outqdir := flag.String("outq", "outq", "Output queue directory") flag.Parse() if flag.NArg() != 1 { fmt.Fprintln(os.Stderr, "Error: must specify host") os.Exit(69) } - + + dir, err := os.Open(*outqdir) + if err != nil { + log.Fatal(err) + } + defer dir.Close() + conn, err := connect(flag.Arg(0), *dotls) if err != nil { log.Fatal(err) @@ -161,6 +210,7 @@ func main() { outq := make(chan string) go readLoop(conn, inq) go writeLoop(conn, outq) + go monitorDirectory(*outqdir, dir, outq) outq <- "NICK neale" outq <- "USER neale neale neale :neale" @@ -171,6 +221,8 @@ func main() { } dispatch(outq, p) } + + running = false close(outq) } From fa18efdad9e3da172e73521d7f44377fcaf338ca Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 24 Jul 2014 03:15:04 +0000 Subject: [PATCH 04/23] working, needs auth --- index.html | 90 +++++++--------------------------- chunktail.cgi.go => irc.cgi.go | 4 +- irc.go | 71 ++++++++++++++++++--------- irc.js | 83 +++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 98 deletions(-) rename chunktail.cgi.go => irc.cgi.go (89%) create mode 100644 irc.js diff --git a/index.html b/index.html index 4cea215..44481f0 100644 --- a/index.html +++ b/index.html @@ -2,81 +2,20 @@ LOL - @@ -100,8 +42,10 @@ window.onload = init;
+
+
diff --git a/chunktail.cgi.go b/irc.cgi.go similarity index 89% rename from chunktail.cgi.go rename to irc.cgi.go index 65d59f6..667e410 100644 --- a/chunktail.cgi.go +++ b/irc.cgi.go @@ -17,7 +17,7 @@ type Handler struct { func tail(w http.ResponseWriter, pos int) { - f, err := os.Open("/home/neale/bot/neale/log/log") + f, err := os.Open("/home/neale/public_html/irc/log") if err != nil { log.Fatal(err) } @@ -38,7 +38,7 @@ func tail(w http.ResponseWriter, pos int) { } func handleCommand(w http.ResponseWriter, text string) { - fn := fmt.Sprintf("/home/neale/bot/neale/outq/cgi.%d", time.Now().Unix()) + fn := fmt.Sprintf("/home/neale/public_html/irc/outq/cgi.%d", time.Now().Unix()) f, err := os.Create(fn) if err != nil { fmt.Fprintln(w, "NO") diff --git a/irc.go b/irc.go index 1eee9b4..63c1aa9 100644 --- a/irc.go +++ b/irc.go @@ -12,8 +12,6 @@ import ( "time" ) -var running bool = true - type Message struct { Command string FullSender string @@ -23,14 +21,32 @@ type Message struct { Text string } -func (m Message) String() string { - a := append([]string{m.FullSender}, m.Args...) - args := strings.Join(a, " ") - return fmt.Sprintf("%s %s %s %s %s", m.Command, m.Sender, m.Forum, args, m.Text) +var running bool = true +var logq chan Message + +func isChannel(s string) bool { + switch s[0] { + case '#', '&', '!', '+', '.', '-': + return true + default: + return false + } } -func Log(m Message) { - fmt.Printf("%d %s\n", time.Now().Unix(), m.String()) +func (m Message) String() string { + args := strings.Join(m.Args, " ") + return fmt.Sprintf("%s %s %s %s %s :%s", m.FullSender, m.Command, m.Sender, m.Forum, args, m.Text) +} + +func logLoop() { + logf, err := os.OpenFile("log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) + if err != nil { + log.Fatal(err) + } + defer logf.Close() + for m := range logq { + fmt.Fprintf(logf, "%d %s\n", time.Now().Unix(), m.String()) + } } func nuhost(s string) (string, string, string) { @@ -69,7 +85,8 @@ func readLoop(conn net.Conn, inq chan<- string) { func writeLoop(conn net.Conn, outq <-chan string) { for v := range outq { - fmt.Println(v) + m, _ := parse(v) + logq <- m fmt.Fprintln(conn, v) } } @@ -79,7 +96,6 @@ func parse(v string) (Message, error) { var parts []string var lhs string - fmt.Println(v) parts = strings.SplitN(v, " :", 2) if len(parts) == 2 { lhs = parts[0] @@ -107,11 +123,10 @@ func parse(v string) (Message, error) { m.Command = strings.ToUpper(parts[0]) switch m.Command { case "PRIVMSG", "NOTICE": - n, u, _ := nuhost(parts[1]) - if u == "" { - m.Forum = m.Sender + if isChannel(parts[1]) { + m.Forum = parts[1] } else { - m.Forum = n + m.Forum = m.Sender } case "PART", "MODE", "TOPIC", "KICK": m.Forum = parts[1] @@ -130,15 +145,21 @@ func parse(v string) (Message, error) { m.Forum = parts[2] } case "NICK": - m.FullSender = parts[1] - m.Forum = m.FullSender + log.Print(v) + if len(parts) > 1 { + m.Sender = parts[1] + } else { + m.Sender = m.Text + m.Text = "" + } + m.Forum = m.Sender } return m, nil } func dispatch(outq chan<- string, m Message) { - Log(m) + logq <- m switch m.Command { case "PING": outq <- "PONG :" + m.Text @@ -147,14 +168,15 @@ func dispatch(outq chan<- string, m Message) { func handleInfile(path string, outq chan<- string) { f, err := os.Open(path) - if (err != nil) { + if err != nil { return } defer f.Close() os.Remove(path) inf := bufio.NewScanner(f) for inf.Scan() { - outq <- inf.Text() + txt := inf.Text() + outq <- txt } } @@ -185,7 +207,6 @@ func usage() { } func main() { - log.SetFlags(log.LstdFlags | log.Lshortfile) dotls := flag.Bool("notls", true, "Disable TLS security") outqdir := flag.String("outq", "outq", "Output queue directory") @@ -194,13 +215,13 @@ func main() { fmt.Fprintln(os.Stderr, "Error: must specify host") os.Exit(69) } - + dir, err := os.Open(*outqdir) if err != nil { log.Fatal(err) } defer dir.Close() - + conn, err := connect(flag.Arg(0), *dotls) if err != nil { log.Fatal(err) @@ -208,6 +229,8 @@ func main() { inq := make(chan string) outq := make(chan string) + logq = make(chan Message) + go logLoop() go readLoop(conn, inq) go writeLoop(conn, outq) go monitorDirectory(*outqdir, dir, outq) @@ -221,8 +244,10 @@ func main() { } dispatch(outq, p) } - + running = false close(outq) + close(logq) + close(inq) } diff --git a/irc.js b/irc.js new file mode 100644 index 0000000..3be77e5 --- /dev/null +++ b/irc.js @@ -0,0 +1,83 @@ +var msgRe = /([^ ]+) (<[^>]+>) (.*)/; +var kibozeRe = "neal"; + +function addMessagePart(p, className, text) { + var e = document.createElement("span"); + e.className = className; + e.appendChild(document.createTextNode(text)); + p.appendChild(e); + p.appendChild(document.createTextNode(" ")); +} + + +function addMessage(txt) { + var lhs = txt.split(" :", 1)[0] + var parts = lhs.split(' ') + var ts = new Date(parts[0] * 1000); + var fullSender = parts[1]; + var command = parts[2]; + var sender = parts[3]; + var forum = parts[4]; + var args = parts.slice(5); + var msg = txt.substr(lhs.length + 2) + + var a = document.getElementById("a"); + var p = document.createElement("p"); + + addMessagePart(p, "timestamp", ts.toLocaleTimeString()); + + switch (command) { + case "PING": + case "PONG": + return; + break; + case "PRIVMSG": + addMessagePart(p, "forum", forum); + addMessagePart(p, "sender", sender); + addMessagePart(p, "text", msg); + if (-1 != msg.search(kibozeRe)) { + var k = document.getElementById("kiboze"); + var p2 = p.cloneNode(true); + k.insertBefore(p2, k.firstChild); + } + break; + default: + addMessagePart(p, "forum", forum); + addMessagePart(p, "sender", sender); + addMessagePart(p, "raw", command + " " + args + " " + msg); + break; + } + a.appendChild(p); + p.scrollIntoView(false); +} + +function newmsg(event) { + msgs = event.data.split("\n"); + + for (var i = 0; i < msgs.length; i += 1) { + addMessage(msgs[i]); + } +} + +function handleCommand(event) { + window.evt = event; + var oReq = new XMLHttpRequest(); + function reqListener() { + } + oReq.onload = reqListener; + oReq.open("POST", "chunktail.cgi?post=1", true); + oReq.send(new FormData(event.target)); + + event.target.reset(); + + return false; +} + +function init() { + var source = new EventSource("chunktail.cgi"); + source.onmessage = newmsg; + + document.getElementById("command").onsubmit = handleCommand; +} + +window.onload = init; From 8e9ce192726f1912e0be0db579a4e2345944a6a3 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 24 Jul 2014 03:41:55 +0000 Subject: [PATCH 05/23] Back on track with authentication wotzit --- index.html | 25 +++++++++---------------- irc.cgi.go | 14 ++++++++++++++ irc.js | 18 ++++++++---------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/index.html b/index.html index 44481f0..df455cb 100644 --- a/index.html +++ b/index.html @@ -5,17 +5,14 @@
+ -
-
diff --git a/irc.cgi.go b/irc.cgi.go index 667e410..ea4ddc1 100644 --- a/irc.cgi.go +++ b/irc.cgi.go @@ -2,10 +2,12 @@ package main import ( "fmt" + "io/ioutil" "log" "os" "bufio" "strconv" + "strings" "net/http" "net/http/cgi" "time" @@ -15,6 +17,7 @@ type Handler struct { cgi.Handler } +var authtok string func tail(w http.ResponseWriter, pos int) { f, err := os.Open("/home/neale/public_html/irc/log") @@ -53,6 +56,11 @@ func handleCommand(w http.ResponseWriter, text string) { func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.FormValue("auth") != authtok { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(w, "NO") + return + } switch r.FormValue("type") { case "command": w.Header().Set("Content-Type", "text/plain") @@ -65,6 +73,12 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func main() { + authtokbytes, err := ioutil.ReadFile("authtok") + if err != nil { + log.Fatal("Cannot read authtok") + } + authtok = strings.TrimSpace(string(authtokbytes)) + h := Handler{} if err := cgi.Serve(h); err != nil { log.Fatal(err) diff --git a/irc.js b/irc.js index 3be77e5..93a9369 100644 --- a/irc.js +++ b/irc.js @@ -1,5 +1,5 @@ var msgRe = /([^ ]+) (<[^>]+>) (.*)/; -var kibozeRe = "neal"; +var authtok; function addMessagePart(p, className, text) { var e = document.createElement("span"); @@ -32,18 +32,13 @@ function addMessage(txt) { return; break; case "PRIVMSG": - addMessagePart(p, "forum", forum); addMessagePart(p, "sender", sender); + addMessagePart(p, "forum", forum); addMessagePart(p, "text", msg); - if (-1 != msg.search(kibozeRe)) { - var k = document.getElementById("kiboze"); - var p2 = p.cloneNode(true); - k.insertBefore(p2, k.firstChild); - } break; default: - addMessagePart(p, "forum", forum); addMessagePart(p, "sender", sender); + addMessagePart(p, "forum", forum); addMessagePart(p, "raw", command + " " + args + " " + msg); break; } @@ -65,7 +60,7 @@ function handleCommand(event) { function reqListener() { } oReq.onload = reqListener; - oReq.open("POST", "chunktail.cgi?post=1", true); + oReq.open("POST", "irc.cgi", true); oReq.send(new FormData(event.target)); event.target.reset(); @@ -74,7 +69,10 @@ function handleCommand(event) { } function init() { - var source = new EventSource("chunktail.cgi"); + var authtok = prompt("Auth token", ""); + document.getElementById("authtok").value = authtok; + + var source = new EventSource("irc.cgi?auth=" + authtok); source.onmessage = newmsg; document.getElementById("command").onsubmit = handleCommand; From a21005323e2842542f2987da79747f88c4a80100 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 24 Jul 2014 16:34:02 +0000 Subject: [PATCH 06/23] Kibozing --- index.html | 18 +++++++++++++----- irc.js | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index df455cb..07489bb 100644 --- a/index.html +++ b/index.html @@ -5,15 +5,18 @@ @@ -37,8 +43,10 @@
+
+
diff --git a/irc.js b/irc.js index 93a9369..a1d3670 100644 --- a/irc.js +++ b/irc.js @@ -1,5 +1,9 @@ var msgRe = /([^ ]+) (<[^>]+>) (.*)/; -var authtok; +var kibozeRe = "neal"; + +function isinView(oObject) { + return (oObject.offsetParent.clientHeight <= oObject.offsetTop); +} function addMessagePart(p, className, text) { var e = document.createElement("span"); @@ -32,13 +36,18 @@ function addMessage(txt) { return; break; case "PRIVMSG": - addMessagePart(p, "sender", sender); addMessagePart(p, "forum", forum); + addMessagePart(p, "sender", sender); addMessagePart(p, "text", msg); + if (-1 != msg.search(kibozeRe)) { + var k = document.getElementById("kiboze"); + var p2 = p.cloneNode(true); + k.insertBefore(p2, k.firstChild); + } break; default: - addMessagePart(p, "sender", sender); addMessagePart(p, "forum", forum); + addMessagePart(p, "sender", sender); addMessagePart(p, "raw", command + " " + args + " " + msg); break; } @@ -71,7 +80,7 @@ function handleCommand(event) { function init() { var authtok = prompt("Auth token", ""); document.getElementById("authtok").value = authtok; - + var source = new EventSource("irc.cgi?auth=" + authtok); source.onmessage = newmsg; From 692380c876166358e28b8845a721fd36a8dbf16e Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 24 Jul 2014 16:56:37 +0000 Subject: [PATCH 07/23] Pulse kibozed lines when clicked --- irc.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/irc.js b/irc.js index a1d3670..110d2bb 100644 --- a/irc.js +++ b/irc.js @@ -13,6 +13,22 @@ function addMessagePart(p, className, text) { p.appendChild(document.createTextNode(" ")); } +function focus(e) { + var pct = 1; + var timeout; + + e.scrollIntoView(false); + e.style.backgroundColor = "yellow"; + + timeout = setInterval(function() { + pct = pct - 0.1; + e.style.backgroundColor = "rgba(255, 255, 0, " + pct + ")"; + if (pct <= 0) { + e.style.backgroundColor = "inherit"; + clearInterval(timeout); + } + }, 50) +} function addMessage(txt) { var lhs = txt.split(" :", 1)[0] @@ -43,6 +59,7 @@ function addMessage(txt) { var k = document.getElementById("kiboze"); var p2 = p.cloneNode(true); k.insertBefore(p2, k.firstChild); + p2.onclick = function() { focus(p); } } break; default: From d9d2c2e84017f47fec19c459f693aea27b16a6aa Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 24 Jul 2014 17:11:34 +0000 Subject: [PATCH 08/23] Change page title, finally --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 07489bb..e5db65b 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - LOL + #tron diff --git a/irc.cgi.go b/irc.cgi.go index 0e555ac..3dbafed 100644 --- a/irc.cgi.go +++ b/irc.cgi.go @@ -35,18 +35,29 @@ func tail(w http.ResponseWriter, pos int) { } defer f.Close() - - _, err = f.Seek(int64(pos), 0) - if err != nil { - log.Fatal(err) + for { + printid := false + + _, err = f.Seek(int64(pos), 0) + if err != nil { + log.Fatal(err) + } + bf := bufio.NewScanner(f) + for bf.Scan() { + t := bf.Text() + pos += len(t) + 1 // XXX: this breaks if we ever see \r\n + fmt.Fprintf(w, "data: %s\n", t) + printid = true + } + if printid { + _, err = fmt.Fprintf(w, "id: %d\n\n", pos) + } + if err != nil { + break + } + w.(http.Flusher).Flush() + time.Sleep(350 * time.Millisecond) } - bf := bufio.NewScanner(f) - for bf.Scan() { - t := bf.Text() - pos += len(t) + 1 // XXX: this breaks if we ever see \r\n - fmt.Fprintf(w, "data: %s\n", t) - } - fmt.Fprintf(w, "id: %d\n\n", pos) } func handleCommand(w http.ResponseWriter, text string, target string) { From 8273e03b72d11e1d634b745dcfeaf7ebe94b7671 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 12 Aug 2014 16:55:12 -0600 Subject: [PATCH 11/23] A mostly usable client, with fora tabs and activity indication --- README | 29 ++++++++++++++ index.html | 52 +++---------------------- irc.css | 58 ++++++++++++++++++++++++++++ irc.go | 7 +++- irc.js | 109 ++++++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 192 insertions(+), 63 deletions(-) create mode 100644 README create mode 100644 irc.css diff --git a/README b/README new file mode 100644 index 0000000..2fa3013 --- /dev/null +++ b/README @@ -0,0 +1,29 @@ +Woozle IRC +========= + +This is a sort of bouncer for clients with transient network connections, +like cell phones and laptops. +It's a lot like [tapchat](https://github.com/tapchat/tapchat) but is a whole lot simpler +while being (at time of writing) much more feature-complete. + +It supports (currently) an JavaScript browser-based client, +and can also be worked from the command-line using Unix tools like "tail" and "echo". + +Ironically, it doesn't currently work with any existing IRC clients, +although we are kicking around ideas for such a thing. +Honestly, though, if you want a bouncer for a traditional IRC client, +you are better off using something like znc. + +We have an [architectural diagram](https://docs.google.com/drawings/d/1am_RTUh89kul-318GoYK73AbjOE_jMYi4vI4NyEgKrY/edit?usp=sharing) if you care about such things. + + +Features +-------- + +* Gracefully handles clients with transient networking, such as cell phones and laptops. + + +Todo +----- + +I need to make this document suck less. diff --git a/index.html b/index.html index 323399b..7d42f5a 100644 --- a/index.html +++ b/index.html @@ -3,58 +3,16 @@ #tron - - + + -
+
+
- +
diff --git a/irc.css b/irc.css new file mode 100644 index 0000000..4e2e243 --- /dev/null +++ b/irc.css @@ -0,0 +1,58 @@ + +#foraText, #kiboze { + max-height: 20em; + overflow: scroll; +} +#foraText p, #kiboze p { + margin: 0em 0em 0em 4em; + text-indent: -4em; +} +#kiboze { + max-height: 7em; + background-color: #eee; +} +.timestamp { + color: silver; +} +.forum { + color: darkblue; + display: none; +} +.sender { + color: green; +} +.sender:before { + content: "<"; +} +.sender:after { + content: ">"; +} +.sender.notice { + color: olive; +} +.sender.notice:before { + content: "-"; +} +.sender.notice:after { + content: "-"; +} +.raw { + color: purple; +} + +.active { + background-color: yellow; +} +.current { + background-color: aquamarine; +} + +input[name~=target] { + width: 7em; +} +input[name~=text] { + width: 75%; +} +body { + height: 100%; +} \ No newline at end of file diff --git a/irc.go b/irc.go index 7d7ef5e..ceba86b 100644 --- a/irc.go +++ b/irc.go @@ -123,9 +123,12 @@ func parse(v string) (Message, error) { m.Command = strings.ToUpper(parts[0]) switch m.Command { case "PRIVMSG", "NOTICE": - if isChannel(parts[1]) { + switch { + case isChannel(parts[1]): m.Forum = parts[1] - } else { + case m.FullSender == ".": + m.Forum = parts[1] + default: m.Forum = m.Sender } case "PART", "MODE", "TOPIC", "KICK": diff --git a/irc.js b/irc.js index 133ab01..6ef0ab7 100644 --- a/irc.js +++ b/irc.js @@ -1,10 +1,59 @@ var msgRe = /([^ ]+) (<[^>]+>) (.*)/; -var kibozeRe = "[Nn]eal"; +var kibozeRe = /[Nn]eal/; +var urlRe = /[a-z]+:\/\/[^ ]*/; + +var nick = "Mme. M"; function isinView(oObject) { return (oObject.offsetParent.clientHeight <= oObject.offsetTop); } +function selectForum(fe) { + var kids = document.getElementById("foraText").childNodes; + + for (i = 0; i < kids.length; i += 1) { + e = kids[i]; + console.log(i, e); + if (e == fe) { + e.style.display = "block"; + } else { + e.style.display = "none"; + if (e.button.className == "current") { + e.button.className = ""; + } + } + } + + fe.button.className = "current"; + if (fe.lastChild) { + fe.lastChild.scrollIntoView(false); + } + document.getElementById("target").value = fe.forum; +} + +function getForumElement(forum) { + var id = "a:" + forum; + var fe = document.getElementById(id); + + if (! fe) { + var button = document.createElement("button"); + button.appendChild(document.createTextNode(forum)); + button.onclick = function() { selectForum(fe); } + document.getElementById("foraButtons").appendChild(button); + + fe = document.createElement("div"); + fe.id = id + fe.forum = forum + fe.button = button + document.getElementById("foraText").appendChild(fe); + } + + if (fe.button.className != "current") { + fe.button.className = "active"; + } + return fe; +} + function addMessagePart(p, className, text) { var e = document.createElement("span"); e.className = className; @@ -12,11 +61,48 @@ function addMessagePart(p, className, text) { p.appendChild(e); p.appendChild(document.createTextNode(" ")); } + +function addText(p, text, kiboze) { + // Look for a URL + var txtElement = document.createElement("span"); + txtElement.className = "text"; + var rhs = text; + var match; + + while ((match = urlRe.exec(rhs)) != null) { + var before = rhs.substr(0, match.index); + var a = document.createElement("a"); + var href = match[0]; + + if (href.indexOf("hxx") == 0) { + href = "htt" + href.substr(3); + } + a.href = href + a.target = "_blank"; + a.appendChild(document.createTextNode(match[0])); + txtElement.appendChild(document.createTextNode(before)); + txtElement.appendChild(a); + rhs = rhs.substr(match.index + match[0].length); + } + txtElement.appendChild(document.createTextNode(rhs)); + p.appendChild(txtElement); + + if ((kiboze) || (-1 != text.search(kibozeRe))) { + var k = document.getElementById("kiboze"); + var p2 = p.cloneNode(true); + k.insertBefore(p2, k.firstChild); + p2.onclick = function() { focus(p); } + + // Setting title makes the tab flash sorta + document.title = document.title; + } +} function focus(e) { var pct = 1; var timeout; + selectForum(e.parentNode); e.scrollIntoView(false); e.style.backgroundColor = "yellow"; @@ -41,7 +127,7 @@ function addMessage(txt) { var args = parts.slice(5); var msg = txt.substr(lhs.length + 2) - var a = document.getElementById("a"); + var forumElement = getForumElement(forum); var p = document.createElement("p"); addMessagePart(p, "timestamp", ts.toLocaleTimeString()); @@ -54,17 +140,12 @@ function addMessage(txt) { case "PRIVMSG": addMessagePart(p, "forum", forum); addMessagePart(p, "sender", sender); - addMessagePart(p, "text", msg); - if ((sender == forum) || (-1 != msg.search(kibozeRe))) { - var k = document.getElementById("kiboze"); - var p2 = p.cloneNode(true); - k.insertBefore(p2, k.firstChild); - p2.onclick = function() { focus(p); } - // Supposedly changing title makes the tab flash sorta - t = document.title - document.title = "!" - document.title = t - } + addText(p, msg, (sender == forum)); + break; + case "NOTICE": + addMessagePart(p, "forum", forum); + addMessagePart(p, "sender notice", sender); + addText(p, msg, (sender == forum)); break; default: addMessagePart(p, "forum", forum); @@ -72,7 +153,7 @@ function addMessage(txt) { addMessagePart(p, "raw", command + " " + args + " " + msg); break; } - a.appendChild(p); + forumElement.appendChild(p); p.scrollIntoView(false); } From 8d165d1a07ea00a9a1a64dcd50f5a4105a220b80 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 12 Aug 2014 23:32:59 +0000 Subject: [PATCH 12/23] Rename things, document protocol --- PROTOCOL | 64 +++++++++++++++++++++++++++++++++++++++ irc.cgi.go => wirc.cgi.go | 0 irc.css => wirc.css | 0 irc.go => wirc.go | 0 irc.js => wirc.js | 0 5 files changed, 64 insertions(+) create mode 100644 PROTOCOL rename irc.cgi.go => wirc.cgi.go (100%) rename irc.css => wirc.css (100%) rename irc.go => wirc.go (100%) rename irc.js => wirc.js (100%) diff --git a/PROTOCOL b/PROTOCOL new file mode 100644 index 0000000..5710812 --- /dev/null +++ b/PROTOCOL @@ -0,0 +1,64 @@ +wirc protocol +=========== + +This document attempts to describe the wirc protocol. +The source code will always be authoritative, +but this protocol has been around for a while now so should be changed often, if ever. + + +Out Queue +--------- + + +Any files that appear in the directory outq/ are written verbatim to the IRC server. + +You may put multiple lines in a single file. + +Filenames beginning with "." are ignored. + +You are advised to create files beginning with ".", +then rename them on completion of the write, +to avoid race conditions. + + +Log +--- + +### Log Filenames + +TBD + + +### Log Messages + +IRC messages are written to the log, one message per line. +Messages are translated to an easier-to-parse format: + + timestamp fullname command sender forum [args...] :text + +Where: + +* `timestamp` is in Unix epoch time. +* `fullname` is the full name of the message origin (typically `nick!user@host.name`) +* `command` is the IRC command +* `sender` is the IRC name of the entity that sent the message +* `forum` is the IRC name of the audience of the message +* `args` are any additional arguments not otherwise covered +* `text` is the text of the message + +`sender` and `forum` are provided in every message, for the convenience of the client. +A PRIVMSG to `sender` will make it back to whomever sent the message, +a PRIVMSG to `forum` will be seen by everyone in the audience. + +For example, a "private message" will have `sender` equal to `forum`. +But a "channel message" will have `forum` set to the channel. + +See `wirc.go` for details of each message type. + + +### Initial Messages + +Each log file will contain the following initial messages, +to facilitate stateful clients: + +TBD diff --git a/irc.cgi.go b/wirc.cgi.go similarity index 100% rename from irc.cgi.go rename to wirc.cgi.go diff --git a/irc.css b/wirc.css similarity index 100% rename from irc.css rename to wirc.css diff --git a/irc.go b/wirc.go similarity index 100% rename from irc.go rename to wirc.go diff --git a/irc.js b/wirc.js similarity index 100% rename from irc.js rename to wirc.js From d40627874c8b11da86b51d3d9ce7f409815c2c1a Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 12 Aug 2014 19:27:08 -0600 Subject: [PATCH 13/23] Restructure for Go --- Makefile | 13 +++++++------ wirc.cgi.go => src/wirc.cgi/wirc.cgi.go | 0 wirc.go => src/wirc/wirc.go | 0 3 files changed, 7 insertions(+), 6 deletions(-) rename wirc.cgi.go => src/wirc.cgi/wirc.cgi.go (100%) rename wirc.go => src/wirc/wirc.go (100%) diff --git a/Makefile b/Makefile index 60b62a3..9072141 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ -all: irc.cgi irc +all: wirc.cgi wirc -%: %.go - go build $< +export GOPATH = $(CURDIR) -irc.cgi: irc.cgi.go - go build irc.cgi.go - chmod +s irc.cgi +%: + go build $@ + +wirc.cgi: + go build $@ diff --git a/wirc.cgi.go b/src/wirc.cgi/wirc.cgi.go similarity index 100% rename from wirc.cgi.go rename to src/wirc.cgi/wirc.cgi.go diff --git a/wirc.go b/src/wirc/wirc.go similarity index 100% rename from wirc.go rename to src/wirc/wirc.go From a2bafa2e46d15ed9d3732474029ae66daf78cf61 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 12 Aug 2014 21:41:54 -0600 Subject: [PATCH 14/23] Maybe handle multiple servers? --- src/wirc.cgi/wirc.cgi.go | 17 ++++++++++++----- src/wirc/wirc.go | 20 ++++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/wirc.cgi/wirc.cgi.go b/src/wirc.cgi/wirc.cgi.go index 3dbafed..b86efed 100644 --- a/src/wirc.cgi/wirc.cgi.go +++ b/src/wirc.cgi/wirc.cgi.go @@ -18,7 +18,7 @@ type Handler struct { cgi.Handler } -var BaseDir string +var ServerDir string func ReadString(fn string) string { octets, err := ioutil.ReadFile(fn) @@ -29,7 +29,7 @@ func ReadString(fn string) string { } func tail(w http.ResponseWriter, pos int) { - f, err := os.Open(path.Join(BaseDir, "log")) + f, err := os.Open(path.Join(ServerDir, "log")) if err != nil { log.Fatal(err) } @@ -61,7 +61,7 @@ func tail(w http.ResponseWriter, pos int) { } func handleCommand(w http.ResponseWriter, text string, target string) { - fn := path.Join(BaseDir, fmt.Sprintf("outq/cgi.%d", time.Now().Unix())) + fn := path.Join(ServerDir, fmt.Sprintf("outq/cgi.%d", time.Now().Unix())) f, err := os.Create(fn) if err != nil { fmt.Fprintln(w, "NO") @@ -84,7 +84,15 @@ func handleCommand(w http.ResponseWriter, text string, target string) { func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - authtok := ReadString(path.Join(BaseDir, "authtok")) + // XXX: I'm not happy with this irc.basedir file + BaseDir := ReadString("irc.basedir") + ServerDir = path.Join(BaseDir, r.FormValue("server")) + + if m, _ := path.Match(path.Join(BaseDir, "*"), ServerDir); ! m { + ServerDir = path.Join(BaseDir, "default") + } + + authtok := ReadString(path.Join(ServerDir, "authtok")) if r.FormValue("auth") != authtok { w.Header().Set("Content-Type", "text/plain") fmt.Fprintln(w, "NO") @@ -102,7 +110,6 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func main() { - BaseDir = ReadString("irc.basedir") h := Handler{} if err := cgi.Serve(h); err != nil { log.Fatal(err) diff --git a/src/wirc/wirc.go b/src/wirc/wirc.go index ceba86b..f61b979 100644 --- a/src/wirc/wirc.go +++ b/src/wirc/wirc.go @@ -22,6 +22,8 @@ type Message struct { } var running bool = true +var nick string +var gecos string var logq chan Message func isChannel(s string) bool { @@ -165,6 +167,9 @@ func dispatch(outq chan<- string, m Message) { switch m.Command { case "PING": outq <- "PONG :" + m.Text + case "433": + nick = nick + "_" + outq <- fmt.Sprintf("NICK %s", nick) } } @@ -211,10 +216,11 @@ func usage() { func main() { dotls := flag.Bool("notls", true, "Disable TLS security") outqdir := flag.String("outq", "outq", "Output queue directory") + flag.StringVar(&gecos, "gecos", "Bob The Merry Slug", "Gecos entry (full name)") flag.Parse() - if flag.NArg() != 1 { - fmt.Fprintln(os.Stderr, "Error: must specify host") + if flag.NArg() != 2 { + fmt.Fprintln(os.Stderr, "Error: must specify nickname and host") os.Exit(69) } @@ -223,8 +229,11 @@ func main() { log.Fatal(err) } defer dir.Close() + + nick := flag.Arg(0) + host := flag.Arg(1) - conn, err := connect(flag.Arg(0), *dotls) + conn, err := connect(host, *dotls) if err != nil { log.Fatal(err) } @@ -237,8 +246,8 @@ func main() { go writeLoop(conn, outq) go monitorDirectory(*outqdir, dir, outq) - outq <- "NICK neale" - outq <- "USER neale neale neale :neale" + outq <- fmt.Sprintf("NICK %s", nick) + outq <- fmt.Sprintf("USER %s %s %s: %s", nick, nick, nick, gecos) for v := range inq { p, err := parse(v) if err != nil { @@ -251,5 +260,4 @@ func main() { close(outq) close(logq) - close(inq) } From a908df5550380be54f5ade659ca461b9aec0783e Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 13 Aug 2014 03:42:47 +0000 Subject: [PATCH 15/23] rename to wirc --- index.html | 4 ++-- wirc.css | 2 +- wirc.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 7d42f5a..5ddc3b0 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,8 @@ #tron - - + +
diff --git a/wirc.css b/wirc.css index 4e2e243..6f730ab 100644 --- a/wirc.css +++ b/wirc.css @@ -44,7 +44,7 @@ background-color: yellow; } .current { - background-color: aquamarine; + background-color: cornsilk; } input[name~=target] { diff --git a/wirc.js b/wirc.js index 6ef0ab7..4a847c6 100644 --- a/wirc.js +++ b/wirc.js @@ -171,7 +171,7 @@ function handleCommand(event) { function reqListener() { } oReq.onload = reqListener; - oReq.open("POST", "irc.cgi", true); + oReq.open("POST", "wirc.cgi", true); oReq.send(new FormData(event.target)); event.target.reset(); @@ -183,7 +183,7 @@ function init() { var authtok = prompt("Auth token", ""); document.getElementById("authtok").value = authtok; - var source = new EventSource("irc.cgi?auth=" + authtok); + var source = new EventSource("wirc.cgi?auth=" + authtok); source.onmessage = newmsg; document.getElementById("command").onsubmit = handleCommand; From f983f3517bf637590aef245bdadf55ddcfbf2c6d Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 12 Aug 2014 22:30:47 -0600 Subject: [PATCH 16/23] Skeleton makefile --- Makefile | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 9072141..98d0a4c 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,2 @@ -all: wirc.cgi wirc - -export GOPATH = $(CURDIR) - -%: - go build $@ - -wirc.cgi: - go build $@ +all: + GOPATH=$(CURDIR) go build -v all From 027929559d60a0f90d0056ae7108a099b84c0787 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 13 Aug 2014 04:57:39 +0000 Subject: [PATCH 17/23] multi-server fixes --- index.html | 1 + src/wirc.cgi/wirc.cgi.go | 8 ++++---- wirc.js | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 5ddc3b0..51912bf 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@
+ diff --git a/src/wirc.cgi/wirc.cgi.go b/src/wirc.cgi/wirc.cgi.go index b86efed..89ea9cb 100644 --- a/src/wirc.cgi/wirc.cgi.go +++ b/src/wirc.cgi/wirc.cgi.go @@ -84,12 +84,12 @@ func handleCommand(w http.ResponseWriter, text string, target string) { func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // XXX: I'm not happy with this irc.basedir file - BaseDir := ReadString("irc.basedir") + BaseDir := "servers" + DefaultDir := path.Join(BaseDir, "default") ServerDir = path.Join(BaseDir, r.FormValue("server")) - if m, _ := path.Match(path.Join(BaseDir, "*"), ServerDir); ! m { - ServerDir = path.Join(BaseDir, "default") + if path.Dir(DefaultDir) != path.Dir(ServerDir) { + ServerDir = DefaultDir } authtok := ReadString(path.Join(ServerDir, "authtok")) diff --git a/wirc.js b/wirc.js index 4a847c6..927e2fe 100644 --- a/wirc.js +++ b/wirc.js @@ -180,10 +180,11 @@ function handleCommand(event) { } function init() { + var server = document.getElementById("server").value; var authtok = prompt("Auth token", ""); document.getElementById("authtok").value = authtok; - var source = new EventSource("wirc.cgi?auth=" + authtok); + var source = new EventSource("wirc.cgi?server=" + server + "&auth=" + authtok); source.onmessage = newmsg; document.getElementById("command").onsubmit = handleCommand; From e88261c81a5cfb325efe93655890f0b6d656820b Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 12 Aug 2014 22:59:18 -0600 Subject: [PATCH 18/23] Maybe inotify? --- src/wirc.cgi/wirc.cgi.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/wirc.cgi/wirc.cgi.go b/src/wirc.cgi/wirc.cgi.go index b86efed..3504dff 100644 --- a/src/wirc.cgi/wirc.cgi.go +++ b/src/wirc.cgi/wirc.cgi.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/go-fsnotify/fsnotify" "io/ioutil" "log" "os" @@ -28,24 +29,37 @@ func ReadString(fn string) string { return strings.TrimSpace(string(octets)) } -func tail(w http.ResponseWriter, pos int) { +func tail(w http.ResponseWriter, pos int64) { f, err := os.Open(path.Join(ServerDir, "log")) if err != nil { log.Fatal(err) } defer f.Close() + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + for { printid := false - _, err = f.Seek(int64(pos), 0) + newpos, err := f.Seek(pos, 0) if err != nil { log.Fatal(err) } + + if newpos < pos { + // File has been truncated! + pos = 0 + f.Seek(0, 0) + } + bf := bufio.NewScanner(f) for bf.Scan() { t := bf.Text() - pos += len(t) + 1 // XXX: this breaks if we ever see \r\n + pos += int64(len(t)) + 1 // XXX: this breaks if we ever see \r\n fmt.Fprintf(w, "data: %s\n", t) printid = true } @@ -56,7 +70,13 @@ func tail(w http.ResponseWriter, pos int) { break } w.(http.Flusher).Flush() - time.Sleep(350 * time.Millisecond) + + select { + case _ = <-watcher.Events: + // Somethin' happened! + case err := <-watcher.Errors: + log.Fatal(err) + } } } @@ -104,7 +124,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { handleCommand(w, r.Form.Get("text"), r.FormValue("target")) default: w.Header().Set("Content-Type", "text/event-stream") - id, _ := strconv.Atoi(os.Getenv("HTTP_LAST_EVENT_ID")) + id, _ := strconv.ParseInt(os.Getenv("HTTP_LAST_EVENT_ID"), 0, 64) tail(w, id) } } From 2a0c9231d7af5d32fdf3af0a3e68d2f567a1869f Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 13 Aug 2014 05:21:19 +0000 Subject: [PATCH 19/23] inotify working, more responsive --- src/wirc.cgi/wirc.cgi.go | 5 ++++- src/wirc/wirc.go | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/wirc.cgi/wirc.cgi.go b/src/wirc.cgi/wirc.cgi.go index 5318556..1bc04a4 100644 --- a/src/wirc.cgi/wirc.cgi.go +++ b/src/wirc.cgi/wirc.cgi.go @@ -30,7 +30,9 @@ func ReadString(fn string) string { } func tail(w http.ResponseWriter, pos int64) { - f, err := os.Open(path.Join(ServerDir, "log")) + logfn := path.Join(ServerDir, "log") + + f, err := os.Open(logfn) if err != nil { log.Fatal(err) } @@ -41,6 +43,7 @@ func tail(w http.ResponseWriter, pos int64) { log.Fatal(err) } defer watcher.Close() + watcher.Add(logfn) for { printid := false diff --git a/src/wirc/wirc.go b/src/wirc/wirc.go index f61b979..8ae72ff 100644 --- a/src/wirc/wirc.go +++ b/src/wirc/wirc.go @@ -27,6 +27,10 @@ var gecos string var logq chan Message func isChannel(s string) bool { + if (s == "") { + return false + } + switch s[0] { case '#', '&', '!', '+', '.', '-': return true From 3c698cf678e5e058f3cb32ad53e20cbed6604602 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 23 Oct 2014 21:21:54 -0600 Subject: [PATCH 20/23] Now a Chrome app --- app/_locales/en/messages.json | 15 +++++++++++++++ app/background.js | 5 +++++ app/manifest.json | 21 +++++++++++++++++++++ wirc.css => app/wirc.css | 0 index.html => app/wirc.html | 8 ++++---- wirc.js => app/wirc.js | 34 +++++++++++++++++++++++++--------- chat.png => app/wirc.png | Bin 7 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 app/_locales/en/messages.json create mode 100644 app/background.js create mode 100644 app/manifest.json rename wirc.css => app/wirc.css (100%) rename index.html => app/wirc.html (68%) rename wirc.js => app/wirc.js (87%) rename chat.png => app/wirc.png (100%) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json new file mode 100644 index 0000000..c0c2c24 --- /dev/null +++ b/app/_locales/en/messages.json @@ -0,0 +1,15 @@ +{ + "appName": { + "message": "wIRC Chat Client", + "description": "Application name" + }, + "appShortName": { + "message": "wIRC", + "description": "Short application name" + }, + "appDesc": { + "message": "Chat client for the wIRC bouncer thingamajiggy", + "description": "Application description for app store listing" + } +} + diff --git a/app/background.js b/app/background.js new file mode 100644 index 0000000..31c2421 --- /dev/null +++ b/app/background.js @@ -0,0 +1,5 @@ +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('wirc.html', { + 'state': 'normal' + }) +}) diff --git a/app/manifest.json b/app/manifest.json new file mode 100644 index 0000000..e29cd47 --- /dev/null +++ b/app/manifest.json @@ -0,0 +1,21 @@ +{ + "manifest_version": 2, + "version": "1.0.0", + + "name": "__MSG_appName__", + "short_name": "__MSG_appShortName__", + "description": "__MSG_appDesc__", + "author": "Neale Pickett ", + "icons": {"128": "wirc.png"}, + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "permissions": [ + "storage", + "fileSystem", + "https://woozle.org/" + ], + "default_locale": "en" +} diff --git a/wirc.css b/app/wirc.css similarity index 100% rename from wirc.css rename to app/wirc.css diff --git a/index.html b/app/wirc.html similarity index 68% rename from index.html rename to app/wirc.html index 51912bf..62cd9e5 100644 --- a/index.html +++ b/app/wirc.html @@ -2,9 +2,9 @@ #tron - - - + + +
@@ -14,7 +14,7 @@ - +
diff --git a/wirc.js b/app/wirc.js similarity index 87% rename from wirc.js rename to app/wirc.js index 927e2fe..8569e1e 100644 --- a/wirc.js +++ b/app/wirc.js @@ -4,6 +4,12 @@ var urlRe = /[a-z]+:\/\/[^ ]*/; var nick = "Mme. M"; +if (String.prototype.startsWith == null) { + String.prototype.startsWith = function(needle) { + return this.lastIndexOf(needle, 0) == 0; + } +} + function isinView(oObject) { return (oObject.offsetParent.clientHeight <= oObject.offsetTop); } @@ -13,7 +19,6 @@ function selectForum(fe) { for (i = 0; i < kids.length; i += 1) { e = kids[i]; - console.log(i, e); if (e == fe) { e.style.display = "block"; } else { @@ -166,12 +171,21 @@ function newmsg(event) { } function handleCommand(event) { - window.evt = event; var oReq = new XMLHttpRequest(); function reqListener() { } + + var txt = document.getElementById("text").value; + if (txt.startsWith("/connect ")) { + // XXX: should allow tokens with spaces + var parts = txt.split(" "); + + connect(parts[1], parts[2], parts[3]); + return; + } + oReq.onload = reqListener; - oReq.open("POST", "wirc.cgi", true); + oReq.open("POST", window.postURL, true); oReq.send(new FormData(event.target)); event.target.reset(); @@ -179,14 +193,16 @@ function handleCommand(event) { return false; } -function init() { - var server = document.getElementById("server").value; - var authtok = prompt("Auth token", ""); +function connect(url, server, authtok) { + window.postURL = url; + document.getElementById("server").value = server; document.getElementById("authtok").value = authtok; - - var source = new EventSource("wirc.cgi?server=" + server + "&auth=" + authtok); + var pullURL = url + "?server=" + server + "&auth=" + authtok + var source = new EventSource(pullURL); source.onmessage = newmsg; - +} + +function init() { document.getElementById("command").onsubmit = handleCommand; } diff --git a/chat.png b/app/wirc.png similarity index 100% rename from chat.png rename to app/wirc.png From 576f7b7bceec74b6373f75e159e97c815769eeb5 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Fri, 24 Oct 2014 12:29:38 -0600 Subject: [PATCH 21/23] Remember previous settings --- app/manifest.json | 2 +- app/wirc.js | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index e29cd47..227be33 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,6 +1,6 @@ { "manifest_version": 2, - "version": "1.0.0", + "version": "1.0", "name": "__MSG_appName__", "short_name": "__MSG_appShortName__", diff --git a/app/wirc.js b/app/wirc.js index 8569e1e..031755c 100644 --- a/app/wirc.js +++ b/app/wirc.js @@ -158,6 +158,9 @@ function addMessage(txt) { addMessagePart(p, "raw", command + " " + args + " " + msg); break; } + while (forumElement.childNodes.length > 500) { + forumElement.removeChild(forumElement.firstChild) + } forumElement.appendChild(p); p.scrollIntoView(false); } @@ -181,12 +184,11 @@ function handleCommand(event) { var parts = txt.split(" "); connect(parts[1], parts[2], parts[3]); - return; + } else { + oReq.onload = reqListener; + oReq.open("POST", window.postURL, true); + oReq.send(new FormData(event.target)); } - - oReq.onload = reqListener; - oReq.open("POST", window.postURL, true); - oReq.send(new FormData(event.target)); event.target.reset(); @@ -198,12 +200,23 @@ function connect(url, server, authtok) { document.getElementById("server").value = server; document.getElementById("authtok").value = authtok; var pullURL = url + "?server=" + server + "&auth=" + authtok - var source = new EventSource(pullURL); - source.onmessage = newmsg; + + if (window.source != null) { + window.source.close(); + } + window.source = new EventSource(pullURL); + window.source.onmessage = newmsg; + + chrome.storage.sync.set({"url": url, "server": server, "authtok": authtok}); +} + +function restore(items) { + connect(items["url"], items["server"], items["authtok"]); } function init() { document.getElementById("command").onsubmit = handleCommand; + chrome.storage.sync.get(["url", "server", "authtok"], restore); } window.onload = init; From cd7826527230ab09f37ae4526d85ca8aa6bcd075 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Fri, 24 Oct 2014 15:51:53 -0600 Subject: [PATCH 22/23] Moving to circ GUI --- app/example.html | 361 ++++++++++++++++++++++++++++++ app/message_style.css | 172 ++++++++++++++ app/style.css | 510 ++++++++++++++++++++++++++++++++++++++++++ app/topbar.css | 43 ++++ app/wirc.css | 58 ----- app/wirc.html | 85 +++++-- app/wirc.js | 163 ++++++++------ 7 files changed, 1246 insertions(+), 146 deletions(-) create mode 100644 app/example.html create mode 100644 app/message_style.css create mode 100644 app/style.css create mode 100644 app/topbar.css delete mode 100644 app/wirc.css diff --git a/app/example.html b/app/example.html new file mode 100644 index 0000000..0c07b8a --- /dev/null +++ b/app/example.html @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +CIRC undefined + + + +
+ +
+
+
+
-
+
+
    +
    + +
  • +
    +
    -
    +
  • + +
      + +
    • +
      +
    • + +
        + +
      • +
        +
        +
        +
        +
        +
      • + +
        + + +
        +
        +
        +
        +

        rooms

        +
        +
        +
        us.slashnet.org
        +
        -
        +
        +
        • +
          #tron
          +
          -
          +
        +
        + +
          +
          +
          +
          +

          members

          +
          • +
            also
            +
          • +
            atob
            +
          • +
            Bermuda
            +
          • +
            bit
            +
          • +
            bk
            +
          • +
            bz2
            +
          • +
            caycos
            +
          • +
            ckape
            +
          • +
            clavicle
            +
          • +
            CrackMonkey
            +
          • +
            Dumont
            +
          • +
            dzho
            +
          • +
            emad
            +
          • +
            eythian
            +
          • +
            felixc
            +
          • +
            fo0bar
            +
          • +
            fuzzie
            +
          • +
            GodEater
            +
          • +
            hollow
            +
          • +
            hoylemd
            +
          • +
            jhewl
            +
          • +
            jv
            +
          • +
            kees
            +
          • +
            khmer
            +
          • +
            lamont
            +
          • +
            lexicondal
            +
          • +
            neale
            +
          • +
            nemo
            +
          • +
            nerdtron3000
            +
          • +
            nornagon
            +
          • +
            Octal
            +
          • +
            pdx6
            +
          • +
            pedro
            +
          • +
            Randall
            +
          • +
            sarah
            +
          • +
            Sciri
            +
          • +
            scorche
            +
          • +
            scorche|sh
            +
          • +
            Screwtape
            +
          • +
            sneakums
            +
          • +
            squinky
            +
          • +
            stat
            +
          • +
            teferi
            +
          • +
            tiaz
            +
          • +
            watson
            +
          • +
            wcarss
            +
          • +
            wombat
            +
          • +
            X11R5
            +
          • +
            Zen
            +
          +
          + +
          + +
          +
          + +
          Spel werkt, vertel het door! | hype mismatch error | ERG DRUK | <ginnie> everything is awesome! | Save The Date: 20150212 is Dumont's 15th Birthday | sparrows form Voltron | Deksels! | sysadmin establishment "hanged" by lunatic devop | Levis dehydrates unisexual cleavage. | A fanfare for locked traffic --
          +
          +
          +
          + + + +
          +
          • +
            3:46:12 PM
            +
            +
            +
            Awesome, you've connected to #tron.
            +
            +
          • +
            3:46:12 PM
            +
            +
            +
            If you're ever stuck, type /help to see a list of all commands.
            +
            +
          • +
            3:46:12 PM
            +
            +
            +
            You can switch windows with alt+[0-9] or click in the channel list on the left.
            +
            +
          • +
            3:46:12 PM
            +
            +
            +
            (You joined the channel)
            +
            +
          • +
            3:46:12 PM
            +
            +
            +
            The topic is: Spel werkt, vertel het door! | hype mismatch error | ERG DRUK | <ginnie> everything is awesome! | Save The Date: 20150212 is Dumont's 15th Birthday | sparrows form Voltron | Deksels! | sysadmin establishment "hanged" by lunatic devop | Levis dehydrates unisexual cleavage. | A fanfare for locked traffic --
            +
            +
          • +
            3:46:12 PM
            +
            +
            +
            Topic set by sneakums on Fri Oct 17 2014 16:05:00 GMT-0600 (MDT).
            +
            +
          • +
            3:46:12 PM
            +
            +
            +
            Received a CTCP VERSION from squinky.
            +
            +
          • +
            3:47:13 PM
            +
            +
            nerdtron3000
            +
            merf
            +
            +
          • +
            3:47:53 PM
            +
            +
            nerdtron3000
            +
            X11R5: say something to me
            +
            +
          • +
            3:47:55 PM
            +
            +
            X11R5
            +
            nerdtron3000: Get me some of those things where 99 times out on to something.
            +
            +
          • +
            3:48:01 PM
            +
            +
            nerdtron3000
            +
            ...
            +
            +
          • +
            3:48:02 PM
            +
            +
            Dumont
            +
            [You have a sad feeling for a moment, then it passes.]
            +
            +
          +
          +
          nerdtron3000
          +
          +
          +
          + + + + \ No newline at end of file diff --git a/app/message_style.css b/app/message_style.css new file mode 100644 index 0000000..5da3f16 --- /dev/null +++ b/app/message_style.css @@ -0,0 +1,172 @@ +.message.system { + color: #505053; +} + +.message.notice { + color: #97008B; +} + +.message.error { + color: #B33B2D; +} + +.message.update { + color: #00068F; +} + +.message.update.self { + color: #005816; +} + +.message.update.privmsg { + color: #505053; +} + +.message.update.privmsg.self { + color: gray; +} + +.message.update.privmsg.mention { + color: darkred; +} + +.message.update.privmsg.mention .source { + font-weight: bold; +} + +.message.update.privmsg.direct .source { + color: #488AA8; +} + +.message.update.privmsg.notice .source { + color: #97008B; +} + +/* Nickname Colors. Taken from Textual */ + +.message.update.privmsg.self .source-content-container .source { + color: #ea0d68; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='0'] { + color: #0080ff; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='1'] { + color: #059005; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='2'] { + color: #a80054; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='3'] { + color: #9b0db1; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='4'] { + color: #108860; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='5'] { + color: #7F4FFF; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='6'] { + color: #58701a; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='7'] { + color: #620a8e; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='8'] { + color: #BB0008; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='9'] { + color: #44345f; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='10'] { + color: #2f5353; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='11'] { + color: #904000; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='12'] { + color: #808000; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='13'] { + color: #57797e; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='14'] { + color: #3333dd; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='15'] { + color: #5f4d22; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='16'] { + color: #706616; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='17'] { + color: #46799c; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='18'] { + color: #80372e; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='19'] { + color: #8F478E; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='20'] { + color: #5b9e4c; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='21'] { + color: #13826c; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='22'] { + color: #b13637; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='23'] { + color: #e45d59; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='24'] { + color: #1b51ae; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='25'] { + color: #4855ac; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='26'] { + color: #7f1d86; +} +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='27'] { + color: #73643f; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='28'] { + color: #0b9578; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='29'] { + color: #569c96; +} + +.message.update.privmsg:not(.self) .source-content-container .source[colornumber='30'] { + color: #08465f; +} diff --git a/app/style.css b/app/style.css new file mode 100644 index 0000000..7ece3ca --- /dev/null +++ b/app/style.css @@ -0,0 +1,510 @@ +html, body { + margin: 0; + padding: 0 +} + +*, *:before, *:after { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +body { + color: #505053; + font-family: sans-serif; + font-size: 14px; +} + +.hidden { + display: none; +} + +#templates { + display: none; +} + +ul { + list-style: none; + margin: 0; + padding: 0; +} + +.footer { + display: none; + font-style: italic; + color: #9493A2; +} + +.help-command { + display: inline-block; + width: 7em; +} + +.content-item { + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; +} + +#main { + display: -webkit-box; + -webkit-box-orient: horizontal; + width: 100%; + height: 100%; + position: absolute; + border-top: 1px solid rgba(0,0,0,0.15); +} + +#main-top-border { + position: absolute; + top: 0; + z-index: 100; + width: 100%; + height: 1px; + background-color: rgba(0,0,0,0.05); + pointer-events: none; +} + +#rooms-and-nicks { + background-color: #F7F5E4; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-box-flex: 0; + width: 150px; + height: inherit; + padding: 4px 0px; + border-right: 1px solid rgba(0, 0, 0, .15); + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +#rooms-and-nicks h1 { + color: #406698; + text-transform: uppercase; + font-size: 12px; + padding: 8px 0px 2px 8px; + margin: 0px; +} + +#rooms-and-nicks .nick, +#rooms-and-nicks .room { + padding: 0 8px; + line-height: 26px; +} + +#rooms-and-nicks .room { + cursor: pointer; +} + +#rooms-and-nicks .server.footer { + display: block; +} + +.dragbar:hover { + width: 6px; + transition: width .1s; +} + +.dragbar { + height: 100%; + width: 1px; + cursor: col-resize; +} + +#ghostbar{ + width:3px; + background-color:#000; + opacity:0.8; + position:absolute; + cursor: col-resize; + z-index:999 +} + +#rooms-container { + -webkit-box-flex: 0; + border-bottom: 1px solid #CCC; + padding-bottom: 10px; +} + +.no-nicks #rooms-container { + border-bottom-style: none; + padding-bottom: 0px; +} + +#rooms-and-nicks.hidden { + display: none; +} + +#rooms-and-nicks .room.server { + background-color: #F2EFD3; + position: relative; +} + +#rooms-and-nicks:hover .room.server .content-item { + width: 130px; +} + +#rooms-and-nicks .add-channel { + position: absolute; + right: 2px; + top: -1px; + cursor: pointer; + font-size: 18px; + display: none; +} + +#rooms-and-nicks:hover .add-channel { + display: block; +} + +#rooms-and-nicks .add-channel:hover { + color: #888; +} + +#rooms-and-nicks .room.channel:first-child { + padding-top: 3px; +} + +#rooms-and-nicks .room.channel:first-child .content-item { + padding-bottom: 3px; + line-height: 20px; +} + +#rooms-and-nicks not(.current-server) + .channels .room.channel:nth-last-child(2) { + padding-bottom: 3px; +} +#rooms-and-nicks .room.channel:last-child { + padding-bottom: 3px; +} + +#rooms-and-nicks not(.current-server) + .channels .room.channel:nth-last-child(2) .content-item { + padding-top: 3px; + line-height: 20px; +} +#rooms-and-nicks .room.channel:last-child .content-item { + padding-top: 3px; + line-height: 20px; +} + +#rooms-and-nicks not(.current-server) + .channels .room.channel:nth-last-child(2):first-child .content-item { + line-height: 14px; +} +#rooms-and-nicks .room.channel:last-child:first-child .content-item { + line-height: 14px; +} + +#rooms-and-nicks .current-server:not(.always-empty) + .channels .footer { + display: block; +} + +#rooms-and-nicks .room.channel .content-item { + border-left: 1px solid rgba(0, 0, 0, .5); + padding-left: 7px; +} + +#rooms-and-nicks .room.activity { + font-weight: bold; +} + +#rooms-and-nicks .room.mention { + color: darkred; +} + +#rooms-and-nicks .room.disconnected .content-item { + color: #9493A2; + font-style: italic; +} + +#rooms-and-nicks .room.channel.disconnected .content-item { + border-left: 1px solid #9493A2; +} + +#rooms-and-nicks .room.selected { + background-color: #F0E798; +} + +#rooms-and-nicks .room { + position: relative; +} + +#rooms-and-nicks .room .remove-button { + position: absolute; + right: 6px; + top: -1px; + cursor: pointer; + font-size: 30px; + color: rgba(0, 0, 0, .25); + display: none; +} + +#rooms-and-nicks .room .remove-button:hover { + color: rgba(0, 0, 0, .5); +} + +#rooms-and-nicks .room.selected:not(.footer):not(.always-empty) .content-item { + width: 123px; +} +#rooms-and-nicks .room:hover:not(.footer):not(.always-empty) .content-item { + width: 123px; +} + +#rooms-and-nicks .room.selected:not(.footer):not(.always-empty) .remove-button { + display: inline-block; +} +#rooms-and-nicks .room:hover:not(.footer):not(.always-empty) .remove-button { + display: inline-block; +} + +#nicks-container { + border-top: 1px solid #FFF; + -webkit-box-flex: 1; +} + +.no-nicks #nicks-container { + display: none; +} + +.rooms, +.nicks { + padding-top: 5px; +} + +.nicks li:nth-child(odd) { + background-color: #F2EFD3; +} + +#notice { + background-color: #406698; + box-shadow: 0px 1px 4px #888; + color: #FFF; + position: absolute; + width: 100%; + -webkit-transition: 150ms; + top: 0; + padding: 2px 0; +} + +#notice.hide { + top: -38px; +} + +#notice .content { + display: inline; + padding-left: 14px; + text-overflow: ellipsis; + white-space: nowrap; + overflow-x: hidden; + position: absolute; + top: 50%; + margin-top: -.5em; +} + +#notice button { + float: right; + height: 22px; + font-size: 14px; + padding: 0px 4px; + margin: 4px; + border: none; + background-color: #ECECEC; + color: #505053; + cursor: pointer; + outline: none; +} + +#notice button.close { + margin-right: 8px; + font-size: 12px; + border-radius: 42px; + width: 21px; + height: 21px; +} + +#messages-and-input { + -webkit-box-flex: 1; + display: -webkit-box; + -webkit-box-orient: vertical; + box-shadow: 0px 0px 8px #CCC; + position: relative; +} + +#messages-container { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + -webkit-box-flex: 1; + border-bottom: 1px solid #CCC; +} + +.messages { + display: table; +} + +.message { + display: table-row; + -webkit-user-select: initial; +} + +.message.activity-marker .source-content-container { + border-top: 1px solid rgb(224, 179, 179); +} + +.message .timestamp { + color: #6060C0; + font-style: italic; + font-size: smaller; + white-space: nowrap; + display: table-cell; + text-align: right; + padding: 0px 10px 0px 10px; + border-right: 1px solid rgba(0,0,0,0.15); + cursor: text; +} + +.source-content-container { + display: table-cell; + padding: 4px 10px 4px 15px; + width: 100%; +} + +.message .sender { + font-weight: bold; + padding-right: 5px; + margin-left: -5px; + white-space: nowrap; + display: inline; + text-align: right; + cursor: text; +} + +.message .sender.empty { + padding-right: 0; +} + +.message .text { + display: inline; + white-space: pre-wrap; + cursor: text; +} + +#messages-container .messages .message:first-child .source-content-container { + padding-top: 10px; +} + +#messages-container .messages .message:last-child .source-content-container { + padding-bottom: 8px; +} + +.message:not(.privmsg) + .message.privmsg .source-content-container { + padding-top: 4px; + padding-bottom: 1px; +} +.message.privmsg + .message.privmsg .source-content-container { + padding-top: 1px; + padding-bottom: 1px; +} + +.message:not(.group) + .message.group .source-content-container { + padding-top: 4px; + padding-bottom: 0px; +} +.message.group + .message.group .source-content-container { + padding-top: 0px; + padding-bottom: 0px; +} + +.message:not(.notice-group) + .message.notice-group .source-content-container { + padding-top: 4px; + padding-bottom: 0px; +} +.message.notice-group + .message.notice-group .source-content-container { + padding-top: 0px; + padding-bottom: 0px; +} + +.message.list { + /* empty style for now */ +} + +.longword { word-break: break-all; } + +#nick-and-input { + border-top: 1px solid #F9F9F9; + -webkit-box-flex: 0; + display: -webkit-box; + background-color: #ECECEC; + padding: 5px 10px 5px 10px; +} + +#nick { + padding-top: 5px; +} + +#nick > span { + padding-right: 10px; +} + +#nick > .name:before { + font-weight: bold; + content: "[ " +} +#nick > .name:after { + font-weight: bold; + content: " ]" +} + +#nick .away:before { + content: "(" +} +#nick .away:after { + content: ")" +} + +#input-bar { + -webkit-box-flex: 1; + display: -webkit-box; +} + +#input { + background-color: #F9F9F9; + display: block; + -webkit-box-flex: 1; + width: 100%; + height: 30px; + border: 1px; + border-radius: 5px; + -webkit-box-shadow: 0px 0px 3px #888; + color: #505053; + font-size: 14px; + padding: 0px 8px; + outline: 0; +} + +#input.blink { + -webkit-box-shadow: 0px 0px 6px #406698; +} + +::-webkit-scrollbar { + width: 9px; + height: 9px; +} +::-webkit-scrollbar-button:start:decrement, +::-webkit-scrollbar-button:end:increment { + display: block; + height: 0; +} +::-webkit-scrollbar-track-piece { + background-color: rgba(0,0,0,0.1); +} +::-webkit-scrollbar-thumb:vertical { + height: 50px; + background-color: #999; + border-radius: 8px; +} +::-webkit-scrollbar-thumb:vertical:hover { + background-color: #888; +} +::-webkit-scrollbar-thumb:horizontal { + width: 50px; + background-color: #999; + border-radius: 8px; +} diff --git a/app/topbar.css b/app/topbar.css new file mode 100644 index 0000000..8db1003 --- /dev/null +++ b/app/topbar.css @@ -0,0 +1,43 @@ +#hide-channels { + display: inline-block; + font-size: 12px; +} + +#topic-container { + border-bottom: 1px solid #CCC; + -webkit-box-flex: 0; + display: -webkit-box; + background-color: #ECECEC; + padding: 4px 6px; +} + +#status { + -webkit-box-flex: 1; + padding: 4px 5px; +} + +#status .topic { + font-style: italic; +} + +.topbar-button { + background: rgba(0, 0, 0, .08); + border: 0 transparent; + text-decoration: none; + cursor: pointer; + border-radius: 2px; + -webkit-transition: .1s linear -webkit-box-shadow; + margin-right: 4px; +} + +.topbar-button:active { + box-shadow: 0 0 0 1px rgba(0 ,0 ,0 ,.15) inset, 0 0 6px rgba(0, 0, 0, .2) inset; +} + +.topbar-button:hover { + background: rgba(0, 0, 0, .13); +} + +.topbar-button:focus { + outline: none; +} diff --git a/app/wirc.css b/app/wirc.css deleted file mode 100644 index 6f730ab..0000000 --- a/app/wirc.css +++ /dev/null @@ -1,58 +0,0 @@ - -#foraText, #kiboze { - max-height: 20em; - overflow: scroll; -} -#foraText p, #kiboze p { - margin: 0em 0em 0em 4em; - text-indent: -4em; -} -#kiboze { - max-height: 7em; - background-color: #eee; -} -.timestamp { - color: silver; -} -.forum { - color: darkblue; - display: none; -} -.sender { - color: green; -} -.sender:before { - content: "<"; -} -.sender:after { - content: ">"; -} -.sender.notice { - color: olive; -} -.sender.notice:before { - content: "-"; -} -.sender.notice:after { - content: "-"; -} -.raw { - color: purple; -} - -.active { - background-color: yellow; -} -.current { - background-color: cornsilk; -} - -input[name~=target] { - width: 7em; -} -input[name~=text] { - width: 75%; -} -body { - height: 100%; -} \ No newline at end of file diff --git a/app/wirc.html b/app/wirc.html index 62cd9e5..27dd1db 100644 --- a/app/wirc.html +++ b/app/wirc.html @@ -1,22 +1,79 @@ - #tron - - + + + + + -
          -
          -
          - - - - - - -
          -
          + +
          + +
          +
          +
          +
          -
          +
          +
            +
            + +
          • +
            +
            -
            +
          • + +
              + +
            • +
              +
            • + +
                + +
              • +
                +
                +
                +
                +
                +
              • + +
                + + +
                +
                +
                +
                +

                rooms

                +
                +
                +
                +

                members

                +
                +
                + +
                + +
                +
                + +
                +
                +
                +
                + + + +
                +
                +
                +
                +
                +
                +
                diff --git a/app/wirc.js b/app/wirc.js index 031755c..349e824 100644 --- a/app/wirc.js +++ b/app/wirc.js @@ -4,60 +4,63 @@ var urlRe = /[a-z]+:\/\/[^ ]*/; var nick = "Mme. M"; +var scrollbackLength = 500; + if (String.prototype.startsWith == null) { String.prototype.startsWith = function(needle) { return this.lastIndexOf(needle, 0) == 0; } } +function getTemplate(className) { + return document.templates.getElementsByClassName(className)[0].cloneNode(true); +} + function isinView(oObject) { return (oObject.offsetParent.clientHeight <= oObject.offsetTop); } -function selectForum(fe) { - var kids = document.getElementById("foraText").childNodes; - +function selectForum(room) { + var kids = document.rooms_list.childNodes; + for (i = 0; i < kids.length; i += 1) { e = kids[i]; - if (e == fe) { - e.style.display = "block"; + if (e == room) { + e.className = "room selected"; + e.messages.display = "block"; } else { - e.style.display = "none"; - if (e.button.className == "current") { - e.button.className = ""; - } + e.className = "room"; + e.messages.display = "none"; } } - - fe.button.className = "current"; - if (fe.lastChild) { - fe.lastChild.scrollIntoView(false); - } - document.getElementById("target").value = fe.forum; -} + if (room.lastChild) { + room.lastChild.scrollIntoView(false); + } +} + +fora = {} function getForumElement(forum) { - var id = "a:" + forum; - var fe = document.getElementById(id); - + var fe = fora[forum]; + if (! fe) { - var button = document.createElement("button"); - button.appendChild(document.createTextNode(forum)); - button.onclick = function() { selectForum(fe); } - document.getElementById("foraButtons").appendChild(button); - - fe = document.createElement("div"); - fe.id = id - fe.forum = forum - fe.button = button - document.getElementById("foraText").appendChild(fe); - } - - if (fe.button.className != "current") { - fe.button.className = "active"; + var room = getTemplate("channel room"); + room.textContent = forum; + document.rooms_list.appendChild(room); + + fe = getTemplate("messages"); + fe.room = room; + + room.messages = fe; + // XXX: split out into non-anon function + room.addEventListener("click", function() {selectForum(fe)}); + + fora[forum] = fe; + document.getElementById("messages-container").appendChild(fe); } + return fe; -} +} function addMessagePart(p, className, text) { var e = document.createElement("span"); @@ -73,12 +76,12 @@ function addText(p, text, kiboze) { txtElement.className = "text"; var rhs = text; var match; - + while ((match = urlRe.exec(rhs)) != null) { var before = rhs.substr(0, match.index); var a = document.createElement("a"); var href = match[0]; - + if (href.indexOf("hxx") == 0) { href = "htt" + href.substr(3); } @@ -91,31 +94,34 @@ function addText(p, text, kiboze) { } txtElement.appendChild(document.createTextNode(rhs)); p.appendChild(txtElement); - + if ((kiboze) || (-1 != text.search(kibozeRe))) { var k = document.getElementById("kiboze"); var p2 = p.cloneNode(true); - k.insertBefore(p2, k.firstChild); - p2.onclick = function() { focus(p); } - - // Setting title makes the tab flash sorta - document.title = document.title; + + if (k) { + k.insertBefore(p2, k.firstChild); + p2.onclick = function() { focus(p); } + + // Setting title makes the tab flash sorta + document.title = document.title; + } } } - + function focus(e) { var pct = 1; var timeout; - - selectForum(e.parentNode); + + selectForum(e.parentNode); e.scrollIntoView(false); e.style.backgroundColor = "yellow"; - + timeout = setInterval(function() { pct = pct - 0.1; e.style.backgroundColor = "rgba(255, 255, 0, " + pct + ")"; if (pct <= 0) { - e.style.backgroundColor = "inherit"; + e.style.backgroundColor = "inherit"; clearInterval(timeout); } }, 50) @@ -131,12 +137,12 @@ function addMessage(txt) { var forum = parts[4]; var args = parts.slice(5); var msg = txt.substr(lhs.length + 2) - + var forumElement = getForumElement(forum); - var p = document.createElement("p"); - + var p = getTemplate("message"); + addMessagePart(p, "timestamp", ts.toLocaleTimeString()); - + switch (command) { case "PING": case "PONG": @@ -158,27 +164,29 @@ function addMessage(txt) { addMessagePart(p, "raw", command + " " + args + " " + msg); break; } - while (forumElement.childNodes.length > 500) { + while (forumElement.childNodes.length > scrollbackLength) { forumElement.removeChild(forumElement.firstChild) } forumElement.appendChild(p); p.scrollIntoView(false); } -function newmsg(event) { - msgs = event.data.split("\n"); - - for (var i = 0; i < msgs.length; i += 1) { +function newmsg(oEvent) { + msgs = oEvent.data.split("\n"); + + var first = Math.max(0, msgs.length - scrollbackLength); + for (var i = first; i < msgs.length; i += 1) { addMessage(msgs[i]); } } - -function handleCommand(event) { + +function handleInput(oEvent) { + console.log(oEvent); var oReq = new XMLHttpRequest(); function reqListener() { } - - var txt = document.getElementById("text").value; + + var txt = oEvent.target.value; if (txt.startsWith("/connect ")) { // XXX: should allow tokens with spaces var parts = txt.split(" "); @@ -189,34 +197,41 @@ function handleCommand(event) { oReq.open("POST", window.postURL, true); oReq.send(new FormData(event.target)); } - - event.target.reset(); + + oEvent.target.value = ""; return false; } function connect(url, server, authtok) { - window.postURL = url; - document.getElementById("server").value = server; - document.getElementById("authtok").value = authtok; + document.postURL = url; var pullURL = url + "?server=" + server + "&auth=" + authtok - if (window.source != null) { - window.source.close(); + if (document.source != null) { + document.source.close(); } - window.source = new EventSource(pullURL); - window.source.onmessage = newmsg; + document.source = new EventSource(pullURL); + document.source.onmessage = newmsg; - chrome.storage.sync.set({"url": url, "server": server, "authtok": authtok}); + chrome.storage.sync.set({"connections": [[url, server, authtok]]}); } function restore(items) { - connect(items["url"], items["server"], items["authtok"]); + var connections = items["connections"]; + + for (var k = 0; k < connections.length; k += 1) { + var conn = connections[k]; + + connect(conn[0], conn[1], conn[2]); + } } function init() { - document.getElementById("command").onsubmit = handleCommand; - chrome.storage.sync.get(["url", "server", "authtok"], restore); + chrome.storage.sync.get("connections", restore); + document.getElementById("input").addEventListener("change", handleInput); + + document.templates = document.getElementById("templates"); + document.rooms_list = document.getElementById("rooms-container").getElementsByClassName("rooms")[0]; } -window.onload = init; +window.addEventListener("load", init); From 6edcce182e6d55a1367a428a26fb2732dfc3aa32 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Fri, 24 Oct 2014 16:56:40 -0600 Subject: [PATCH 23/23] Start organizing --- app/server.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 app/server.js diff --git a/app/server.js b/app/server.js new file mode 100644 index 0000000..4749e41 --- /dev/null +++ b/app/server.js @@ -0,0 +1,38 @@ +// Functionality dealing with server-level things + +var maxScrollback = 500; + +function Server(baseURL, network, authtok, messageHandler) { + function handleEventSourceLine(line) { + var lhs = line.split(" :", 1)[0] + var parts = lhs.split(' ') + var timestamp = new Date(parts[0] * 1000); + var fullSender = parts[1]; + var command = parts[2]; + var sender = parts[3]; + var forum = parts[4]; + var args = parts.slice(5); + var txt = line.substr(lhs.length + 2); + + messageHandler(timestamp, fullSender, command, sender, forum, args, txt); + } + + function handleEventSourceMessage(oEvent) { + msgs = oEvent.data.split("\n"); + + var first = Math.min(0, msgs.length - maxScrollback); + for (var i = first; i < msgs.length; i += 1) { + handleEeventSourceLine(msgs[i]); + } + } + + function handleEventSourceError(oEvent) { + timestamp = new Date(); + messageHandler(timestamp, null, "ERROR", null, null, [], null); + } + + var pullURL = baseURL + "?network=" + encodeURIComponent(network) + "&authtok=" + encodeURIComponent(authtok); + this.eventSource = new EventSource(baseURL); + this.eventSource.addEventListener("message", handleEventSourceMessage); + this.eventSource.addEventListener("error", handleEventSourceError); +}