diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ada7024 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +icon-*.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1909a06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,56 @@ +Copyright (c) 2015 Neale Pickett + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +The software is provided "as is", without warranty of any kind, express or +implied, including but not limited to the warranties of merchantability, +fitness for a particular purpose and noninfringement. In no event shall +the authors or copyright holders be liable for any claim, damages or +other liability, whether in an action of contract, tort or otherwise, +arising from, out of or in connection with the Software or the use or +other dealings in the Software. + + +------------------------- + + +Parts of this program are from circ, +which was obtained under the following license: + + +Copyright 2012, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Makefile b/Makefile index 6f90e66..433da62 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,18 @@ -ICONS += app/icon-16.png -ICONS += app/icon-32.png -ICONS += app/icon-48.png -ICONS += app/icon-128.png -ICONS += app/icon-256.png +ICONS += icon-16.png +ICONS += icon-32.png +ICONS += icon-48.png +ICONS += icon-128.png +ICONS += icon-256.png -all: icons serverside - -serverside: spongy spongy.cgi - -spongy: src/spongy/spongy.go - GOPATH=$(CURDIR) go build -v $@ - -spongy.cgi: src/spongy.cgi/spongy.cgi.go - GOPATH=$(CURDIR) go build -v $@ - chmod +s $@ +all: icons icons: $(ICONS) -app/icon-%.png: chat.svg +icon-%.png: icon.svg inkscape --export-png=$@ --export-width=$* $< package: icons - cd app && zip -ru ../package.zip . + git ls-files | zip -ru -@ /tmp/spongy-client-chrome.zip + +clean: + rm -f $(ICONS) diff --git a/README.md b/README.md index 9ff7579..32eddfc 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,13 @@ -Spongy IRC -========= +Spongy IRC Client for Chrome +=================== -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. +This is a Chrome client for the +[Spongy IRC Bouncer Thing](https://github.com/nealey/spongy). -It supports (currently) an JavaScript browser-based client, -and can also be worked from the command-line using Unix tools like "tail" and "echo". +The user interface is based heavily on +[circ](https://github.com/flackr/circ). +And when I say "heavily" here, +I mean that I lifted it almost verbatim and touched only what was required to have it work with my JavaScript. -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. - - -Other Documentation -------------------- - -* [Installation Instructions](INSTALL.md) -* [Todo list](TODO.md) +You probably want to install this from the Chrome Store. +I'll put a link to that when I have one. diff --git a/app/_locales/en/messages.json b/_locales/en/messages.json similarity index 100% rename from app/_locales/en/messages.json rename to _locales/en/messages.json diff --git a/app/background.js b/background.js similarity index 100% rename from app/background.js rename to background.js diff --git a/app/channel.js b/channel.js similarity index 100% rename from app/channel.js rename to channel.js diff --git a/doc/INSTALL.md b/doc/INSTALL.md deleted file mode 100644 index 586f481..0000000 --- a/doc/INSTALL.md +++ /dev/null @@ -1,109 +0,0 @@ -Spongy Server Installation -========================== - -You gotta make a base directory with an `authtok` file, -and a subdirectory for every server you want to connect to. - - BASE_DIRECTORY - +-- slashnet - | +-- handler - | +-- config/ - | | +-- server - | | +-- gecos - | | +-- nick - | +-- log/ - | | +-- 2015-01-29T19:56:27Z.log - | | +-- 2015-01-29T20:01:15Z.log - | | +-- 2015-01-29T20:41:40Z.log - | | +-- 2015-01-29T20:41:48Z.log - | | +-- 2015-01-29T20:41:56Z.log - | | +-- 2015-01-29T20:42:44Z.log - | +-- outq/ - +-- oftc - +-- server3 - +-- server4 - - -`config` directory ------------------- - -The `config` directory in a server directory must have certain files: - -* `server` is a list of servers to try and connect to, in the form `hostname:port` -* `gecos` is your "Real Name" -* `nick` is a list of nicknames you'd like to use - -The lists are gone through starting with the first entry until one sticks. - - -`outq` directory ----------------- - -The `outq` directory is monitored by spongy. -When a new file shows up, its contents are spit out verbatim -over the server connection. - -So if you want to send a message to a channel, -do something like this: - - $ echo 'PRIVMSG #channel :hello world' > outq/$$.$(date +%s) - - -Starting up ------------ - -Pretty easy: - - $ cd BASE_DIRECTORY; /path/to/spongy - -Spongy will go off and connect to every configured server in BASE_DIRECTORY. - - -Spongy CGI Configuration -======================== - -If you'd like to run `spongy.cgi`, -that's fine, -but you have to create a file -called `spongy.basedir` -in the same directory as the CGI. -You can do it like this: - - $ echo '/home/neale/BASE_DIRECTORY' > spongy.basedir - -And then, -in `BASE_DIRECTORY`, -you need a file called `auth` -with a sha256 checksum of the authorization token -you want to use in the client. - -You can make it like this: - - $ printf 'my fabulous token' | sha256sum | cut -d\ -f1 > BASE_DIRECTORY - - -Permissions ------------ - -There are a lot of different ways to set up permissions. -Here's what I suggest: -make `spongy.cgi` setuid to you. - - $ chmod +s spongy.cgi - -If it's setuid, -you don't need to make your config file -(or any other files) -readable by the user that -runs the web server. - -Sadly, -Apache has a whole bunch of weirdness in place -which prevents setuid CGI from working -without a lot of configuration twiddling. -But it also has its own mechanism for running CGI -as the user who owns it. -So if you're using Apache, -please send me a recipe for your solution, -and I'll add it to the distribution :) - diff --git a/doc/LICENSE.md b/doc/LICENSE.md deleted file mode 100644 index 853d0ff..0000000 --- a/doc/LICENSE.md +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Neale Pickett - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/doc/PROTOCOL.md b/doc/PROTOCOL.md deleted file mode 100644 index 5710812..0000000 --- a/doc/PROTOCOL.md +++ /dev/null @@ -1,64 +0,0 @@ -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/doc/TODO.md b/doc/TODO.md deleted file mode 100644 index 1fe5f39..0000000 --- a/doc/TODO.md +++ /dev/null @@ -1,7 +0,0 @@ -Todo list -========= - -* One server instance for all networks -* One cgi for all networks? -* Make README suck less -* nickname name= attribute set to uhost diff --git a/app/example.html b/example.html similarity index 100% rename from app/example.html rename to example.html diff --git a/res/chat.svg b/icon.svg similarity index 100% rename from res/chat.svg rename to icon.svg diff --git a/logfile/logfile.go b/logfile/logfile.go deleted file mode 100644 index 1a6e518..0000000 --- a/logfile/logfile.go +++ /dev/null @@ -1,87 +0,0 @@ -package logfile - -import ( - "fmt" - "os" - "time" -) - -type Logfile struct { - file *os.File - name string - nlines int - maxlines int -} - -func NewLogfile(maxlines int) (*Logfile) { - return &Logfile{nil, "", 0, maxlines} -} - -func (lf *Logfile) Close() { - if lf.file != nil { - lf.writeln("EXIT") - lf.file.Close() - } -} - -func (lf *Logfile) writeln(s string) error { - _, err := fmt.Fprintf(lf.file, "%d %s\n", time.Now().Unix(), s) - if err == nil { - lf.nlines += 1 - } - return err -} - -func (lf *Logfile) rotate() error { - fn := fmt.Sprintf("%s.log", time.Now().UTC().Format(time.RFC3339)) - newf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - return err - } - - if lf.file == nil { - // Set lf.file just so we can write out NEXTLOG. - // If this fails, that's okay - lf.file, _ = os.OpenFile("current", os.O_WRONLY|os.O_APPEND, 0666) - } - - if lf.file != nil { - // Note location of new log - logmsg := fmt.Sprintf(". NEXTLOG %s", fn) - lf.writeln(logmsg) - - // All done with the current log - lf.file.Close() - } - - // Point to new log file - lf.file = newf - - // Record symlink to new log - os.Remove("current") - os.Symlink(fn, "current") - - logmsg := fmt.Sprintf(". PREVLOG %s", lf.name) - lf.writeln(logmsg) - - lf.name = fn - - return nil -} - -func (lf *Logfile) Log(s string) error { - if lf.file == nil { - lf.rotate() - } - - err := lf.writeln(s) - if err == nil { - return err - } - - if lf.nlines >= lf.maxlines { - return lf.rotate() - } - - return nil -} diff --git a/app/manifest.json b/manifest.json similarity index 100% rename from app/manifest.json rename to manifest.json diff --git a/app/message_style.css b/message_style.css similarity index 100% rename from app/message_style.css rename to message_style.css diff --git a/app/network.js b/network.js similarity index 100% rename from app/network.js rename to network.js diff --git a/app/room.js b/room.js similarity index 100% rename from app/room.js rename to room.js diff --git a/spongy.cgi/spongy.cgi.go b/spongy.cgi/spongy.cgi.go deleted file mode 100644 index 580a51e..0000000 --- a/spongy.cgi/spongy.cgi.go +++ /dev/null @@ -1,169 +0,0 @@ -package main - -import ( - "fmt" - "github.com/go-fsnotify/fsnotify" - "io/ioutil" - "log" - "os" - "bufio" - "strconv" - "strings" - "net/http" - "net/http/cgi" - "time" - "path" -) - -type Handler struct { - cgi.Handler -} - -var NetworkDir string - -func ReadString(fn string) string { - octets, err := ioutil.ReadFile(fn) - if err != nil { - log.Fatal(err) - } - return strings.TrimSpace(string(octets)) -} - -func tail(w http.ResponseWriter, filename string, pos int64) { - var err error - - currentfn := path.Join(NetworkDir, "current") - if filename == "" { - filename, err = os.Readlink(currentfn) - if err != nil { - log.Fatal(err) - } - } - - filepath := path.Join(NetworkDir, filename) - - f, err := os.Open(filepath) - if err != nil { - log.Fatal(err) - } - defer f.Close() - - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - defer watcher.Close() - watcher.Add(filepath) - - for { - printid := false - - newpos, err := f.Seek(pos, 0) - if err != nil { - log.Fatal(err) - } - - if newpos != pos { - log.Fatal("Lost my position in the log, somehow (log truncated?)") - } - - bf := bufio.NewScanner(f) - for bf.Scan() { - t := bf.Text() - pos += int64(len(t)) + 1 // XXX: this breaks if we ever see \r\n - - parts := strings.Split(t, " ") - if (len(parts) >= 4) && (parts[2] == "NEXTLOG") { - watcher.Remove(filepath) - filename = parts[3] - filepath = path.Join(NetworkDir, filename) - f.Close() - f, err = os.Open(filepath) - if err != nil { - log.Fatal(err) - } - watcher.Add(filepath) - pos = 0 - } - fmt.Fprintf(w, "data: %s\n", t) - printid = true - } - if printid { - _, err = fmt.Fprintf(w, "id: %s/%d\n\n", filename, pos) - } - if err != nil { - break - } - w.(http.Flusher).Flush() - - select { - case _ = <-watcher.Events: - // Somethin' happened! - case err := <-watcher.Errors: - log.Fatal(err) - } - } -} - -func handleCommand(w http.ResponseWriter, text string, target string) { - fn := path.Join(NetworkDir, fmt.Sprintf("outq/cgi.%d", time.Now().Unix())) - f, err := os.Create(fn) - if err != nil { - fmt.Fprintln(w, "NO: Cannot create outq file") - fmt.Fprintln(w, err) - return - } - defer f.Close() - - switch { - case strings.HasPrefix(text, "/quote "): - fmt.Fprintln(f, text[7:]) - case strings.HasPrefix(text, "/me "): - fmt.Fprintf(f, "PRIVMSG %s :\001ACTION %s\001\n", target, text[4:]) - default: - fmt.Fprintf(f, "PRIVMSG %s :%s\n", target, text) - } - - fmt.Fprintln(w, "OK") -} - - -func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - BaseDir := "networks" - DefaultDir := path.Join(BaseDir, "default") - NetworkDir = path.Join(BaseDir, r.FormValue("network")) - - if path.Dir(DefaultDir) != path.Dir(NetworkDir) { - NetworkDir = DefaultDir - } - - authtok := ReadString(path.Join(NetworkDir, "authtok")) - if r.FormValue("auth") != authtok { - w.Header().Set("Content-Type", "text/plain") - fmt.Fprintln(w, "NO: Invalid authtok") - return - } - switch r.FormValue("type") { - case "command": - w.Header().Set("Content-Type", "text/plain") - handleCommand(w, r.Form.Get("text"), r.FormValue("target")) - default: - w.Header().Set("Content-Type", "text/event-stream") - parts := strings.Split(os.Getenv("HTTP_LAST_EVENT_ID"), "/") - if len(parts) == 2 { - filename := path.Base(parts[0]) - pos, _ := strconv.ParseInt(parts[1], 0, 64) - tail(w, filename, pos) - } else { - tail(w, "", 0) - } - } -} - -func main() { - h := Handler{} - if err := cgi.Serve(h); err != nil { - log.Fatal(err) - } -} - diff --git a/spongy/spongy.go b/spongy/spongy.go deleted file mode 100644 index 72b28d0..0000000 --- a/spongy/spongy.go +++ /dev/null @@ -1,282 +0,0 @@ -package main - -import ( - "bufio" - "crypto/tls" - "flag" - "fmt" - "log" - "logfile" - "net" - "os" - "strconv" - "strings" - "time" -) - -type Message struct { - Command string - FullSender string - Sender string - Forum string - Args []string - Text string -} - -var running bool = true -var nick string -var gecos string -var maxlogsize uint -var logq chan Message - -func isChannel(s string) bool { - if (s == "") { - return false - } - - switch s[0] { - case '#', '&', '!', '+', '.', '-': - return true - default: - return false - } -} - -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 := logfile.NewLogfile(int(maxlogsize)) - defer logf.Close() - - for m := range logq { - logf.Log(m.String()) - } -} - -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 { - m, _ := parse(v) - logq <- m - fmt.Fprintln(conn, v) - } -} - -func parse(v string) (Message, error) { - var m Message - var parts []string - var lhs string - - 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": - switch { - case isChannel(parts[1]): - m.Forum = parts[1] - case m.FullSender == ".": - m.Forum = parts[1] - default: - m.Forum = m.Sender - } - case "PART", "MODE", "TOPIC", "KICK": - m.Forum = parts[1] - m.Args = parts[2:] - 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": - if len(parts) > 1 { - m.Sender = parts[1] - m.Args = parts[2:] - } else { - m.Sender = m.Text - m.Text = "" - m.Args = parts[1:] - } - m.Forum = m.Sender - case "353": - m.Forum = parts[3] - default: - numeric, _ := strconv.Atoi(m.Command) - if numeric >= 300 { - if len(parts) > 2 { - m.Forum = parts[2] - } - } - m.Args = parts[1:] - } - - return m, nil -} - -func dispatch(outq chan<- string, m Message) { - logq <- m - switch m.Command { - case "PING": - outq <- "PONG :" + m.Text - case "433": - nick = nick + "_" - outq <- fmt.Sprintf("NICK %s", nick) - } -} - -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() { - txt := inf.Text() - outq <- txt - } -} - -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() { - dotls := flag.Bool("notls", true, "Disable TLS security") - outqdir := flag.String("outq", "outq", "Output queue directory") - flag.UintVar(&maxlogsize, "logsize", 1000, "Log entries before rotating") - flag.StringVar(&gecos, "gecos", "Bob The Merry Slug", "Gecos entry (full name)") - - flag.Parse() - if flag.NArg() != 2 { - fmt.Fprintln(os.Stderr, "Error: must specify nickname and host") - os.Exit(69) - } - - dir, err := os.Open(*outqdir) - if err != nil { - log.Fatal(err) - } - defer dir.Close() - - nick := flag.Arg(0) - host := flag.Arg(1) - - conn, err := connect(host, *dotls) - if err != nil { - log.Fatal(err) - } - - 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) - - 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 { - continue - } - dispatch(outq, p) - } - - running = false - - close(outq) - close(logq) -} diff --git a/app/style.css b/style.css similarity index 100% rename from app/style.css rename to style.css diff --git a/app/topbar.css b/topbar.css similarity index 100% rename from app/topbar.css rename to topbar.css diff --git a/app/wirc.html b/wirc.html similarity index 100% rename from app/wirc.html rename to wirc.html diff --git a/app/wirc.js b/wirc.js similarity index 100% rename from app/wirc.js rename to wirc.js