spongy/irc.go

256 lines
4.4 KiB
Go
Raw Normal View History

2014-07-18 18:09:59 -06:00
package main
import (
"bufio"
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"os"
"strings"
2014-07-20 23:03:53 -06:00
"time"
2014-07-18 18:09:59 -06:00
)
type Message struct {
2014-07-20 23:03:53 -06:00
Command string
2014-07-18 18:09:59 -06:00
FullSender string
2014-07-20 23:03:53 -06:00
Sender string
Forum string
Args []string
Text string
2014-07-18 18:09:59 -06:00
}
2014-07-23 21:15:04 -06:00
var running bool = true
var logq chan Message
func isChannel(s string) bool {
switch s[0] {
case '#', '&', '!', '+', '.', '-':
return true
default:
return false
}
}
2014-07-18 18:09:59 -06:00
func (m Message) String() string {
2014-07-23 21:15:04 -06:00
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)
2014-07-18 18:09:59 -06:00
}
2014-07-23 21:15:04 -06:00
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())
}
2014-07-20 23:03:53 -06:00
}
2014-07-18 18:09:59 -06:00
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 {
2014-07-23 21:15:04 -06:00
m, _ := parse(v)
logq <- m
2014-07-18 18:09:59 -06:00
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 = ""
}
2014-07-20 23:03:53 -06:00
2014-07-18 18:09:59 -06:00
m.FullSender = "."
m.Forum = "."
m.Sender = "."
parts = strings.Split(lhs, " ")
if parts[0][0] == ':' {
m.FullSender = parts[0][1:]
parts = parts[1:]
2014-07-20 23:03:53 -06:00
2014-07-18 18:09:59 -06:00
n, u, _ := nuhost(m.FullSender)
if u != "" {
m.Sender = n
}
}
2014-07-20 23:03:53 -06:00
2014-07-18 18:09:59 -06:00
m.Command = strings.ToUpper(parts[0])
2014-07-20 23:03:53 -06:00
switch m.Command {
2014-07-18 18:09:59 -06:00
case "PRIVMSG", "NOTICE":
switch {
case isChannel(parts[1]):
2014-07-23 21:15:04 -06:00
m.Forum = parts[1]
case m.FullSender == ".":
m.Forum = parts[1]
default:
2014-07-23 21:15:04 -06:00
m.Forum = m.Sender
2014-07-18 18:09:59 -06:00
}
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":
2014-07-23 21:15:04 -06:00
if len(parts) > 1 {
m.Sender = parts[1]
} else {
m.Sender = m.Text
m.Text = ""
}
m.Forum = m.Sender
2014-07-18 18:09:59 -06:00
}
2014-07-20 23:03:53 -06:00
2014-07-18 18:09:59 -06:00
return m, nil
}
func dispatch(outq chan<- string, m Message) {
2014-07-23 21:15:04 -06:00
logq <- m
2014-07-20 23:03:53 -06:00
switch m.Command {
2014-07-18 18:09:59 -06:00
case "PING":
outq <- "PONG :" + m.Text
}
}
2014-07-20 23:03:53 -06:00
func handleInfile(path string, outq chan<- string) {
f, err := os.Open(path)
2014-07-23 21:15:04 -06:00
if err != nil {
2014-07-20 23:03:53 -06:00
return
}
defer f.Close()
os.Remove(path)
inf := bufio.NewScanner(f)
for inf.Scan() {
2014-07-23 21:15:04 -06:00
txt := inf.Text()
outq <- txt
2014-07-20 23:03:53 -06:00
}
}
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)
}
}
2014-07-18 18:09:59 -06:00
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")
2014-07-20 23:03:53 -06:00
outqdir := flag.String("outq", "outq", "Output queue directory")
2014-07-18 18:09:59 -06:00
flag.Parse()
if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "Error: must specify host")
os.Exit(69)
}
2014-07-23 21:15:04 -06:00
2014-07-20 23:03:53 -06:00
dir, err := os.Open(*outqdir)
if err != nil {
log.Fatal(err)
}
defer dir.Close()
2014-07-23 21:15:04 -06:00
2014-07-18 18:09:59 -06:00
conn, err := connect(flag.Arg(0), *dotls)
if err != nil {
log.Fatal(err)
}
inq := make(chan string)
outq := make(chan string)
2014-07-23 21:15:04 -06:00
logq = make(chan Message)
go logLoop()
2014-07-18 18:09:59 -06:00
go readLoop(conn, inq)
go writeLoop(conn, outq)
2014-07-20 23:03:53 -06:00
go monitorDirectory(*outqdir, dir, outq)
2014-07-18 18:09:59 -06:00
outq <- "NICK neale"
outq <- "USER neale neale neale :neale"
for v := range inq {
p, err := parse(v)
if err != nil {
continue
}
dispatch(outq, p)
}
2014-07-23 21:15:04 -06:00
2014-07-20 23:03:53 -06:00
running = false
2014-07-18 18:09:59 -06:00
close(outq)
2014-07-23 21:15:04 -06:00
close(logq)
close(inq)
2014-07-18 18:09:59 -06:00
}