mirror of https://github.com/nealey/spongy
329 lines
6.2 KiB
Go
329 lines
6.2 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"crypto/tls"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net"
|
||
"os"
|
||
"os/user"
|
||
"os/exec"
|
||
"path"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// This gets called every time the data's needed.
|
||
// That makes it so you can change stuff while running.
|
||
|
||
func ReadLines(fn string) ([]string, error) {
|
||
lines := make([]string, 0)
|
||
|
||
f, err := os.Open(fn)
|
||
if err != nil {
|
||
return lines, err
|
||
}
|
||
defer f.Close()
|
||
|
||
scanner := bufio.NewScanner(f)
|
||
for scanner.Scan() {
|
||
line := strings.TrimSpace(scanner.Text())
|
||
switch {
|
||
case line == "":
|
||
default:
|
||
lines = append(lines, line)
|
||
}
|
||
}
|
||
|
||
return lines, nil
|
||
}
|
||
|
||
type Network struct {
|
||
running bool
|
||
|
||
Nick string
|
||
|
||
basePath string
|
||
serverIndex int
|
||
|
||
conn io.ReadWriteCloser
|
||
|
||
logf *Logfile
|
||
|
||
inq chan string
|
||
outq chan string
|
||
}
|
||
|
||
func NewNetwork(basePath string) *Network {
|
||
nw := Network{
|
||
running: true,
|
||
basePath: basePath,
|
||
}
|
||
nw.logf = NewLogfile(nw.basePath, int(maxlogsize))
|
||
|
||
return &nw
|
||
}
|
||
|
||
func (nw *Network) Close() {
|
||
nw.running = false
|
||
if nw.conn != nil {
|
||
nw.conn.Close()
|
||
}
|
||
nw.logf.Close()
|
||
}
|
||
|
||
func (nw *Network) watchOutqDirectory() {
|
||
outqDirname := path.Join(nw.basePath, "outq")
|
||
|
||
dir, err := os.Open(outqDirname)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer dir.Close()
|
||
|
||
// XXX: Do this with fsnotify
|
||
for nw.running {
|
||
entities, _ := dir.Readdirnames(0)
|
||
for _, fn := range entities {
|
||
pathname := path.Join(outqDirname, fn)
|
||
nw.HandleInfile(pathname)
|
||
}
|
||
_, _ = dir.Seek(0, 0)
|
||
time.Sleep(500 * time.Millisecond)
|
||
}
|
||
}
|
||
|
||
func (nw *Network) HandleInfile(fn string) {
|
||
f, err := os.Open(fn)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer f.Close()
|
||
|
||
// Do this after Open attempt.
|
||
// If Open fails, the file will stick around.
|
||
// Hopefully this is helpful for debugging.
|
||
os.Remove(fn)
|
||
|
||
inf := bufio.NewScanner(f)
|
||
for inf.Scan() {
|
||
txt := inf.Text()
|
||
nw.outq <- txt
|
||
}
|
||
}
|
||
|
||
func (nw *Network) serverWriteLoop() {
|
||
for v := range nw.outq {
|
||
debug("» %s", v)
|
||
nw.logf.Log(v)
|
||
fmt.Fprintln(nw.conn, v)
|
||
time.Sleep(500 * time.Millisecond)
|
||
}
|
||
}
|
||
|
||
func (nw *Network) NextNick() {
|
||
nicks, err := ReadLines(path.Join(nw.basePath, "nick"))
|
||
if err != nil {
|
||
log.Print(err)
|
||
return
|
||
}
|
||
|
||
// Make up some alternates if they weren't provided
|
||
if len(nicks) == 1 {
|
||
nicks = append(nicks, nicks[0] + "_")
|
||
nicks = append(nicks, nicks[0] + "__")
|
||
nicks = append(nicks, nicks[0] + "___")
|
||
}
|
||
|
||
nextidx := 0
|
||
for idx, n := range nicks {
|
||
if n == nw.Nick {
|
||
nextidx = idx + 1
|
||
}
|
||
}
|
||
|
||
nw.Nick = nicks[nextidx % len(nicks)]
|
||
nw.outq <- "NICK " + nw.Nick
|
||
}
|
||
|
||
func (nw *Network) JoinChannels() {
|
||
chans, err := ReadLines(path.Join(nw.basePath, "channels"))
|
||
if err != nil {
|
||
log.Print(err)
|
||
return
|
||
}
|
||
|
||
for _, ch := range chans {
|
||
debug("Joining %s", ch)
|
||
nw.outq <- "JOIN " + ch
|
||
}
|
||
}
|
||
|
||
func (nw *Network) messageDispatchLoop() {
|
||
for line := range nw.inq {
|
||
nw.logf.Log(line)
|
||
|
||
m, err := NewMessage(line)
|
||
if err != nil {
|
||
log.Print(err)
|
||
continue
|
||
}
|
||
|
||
switch m.Command {
|
||
case "PING":
|
||
nw.outq <- "PONG :" + m.Text
|
||
continue
|
||
case "001":
|
||
nw.JoinChannels()
|
||
case "433":
|
||
nw.NextNick()
|
||
case "PRIVMSG":
|
||
if m.Text == "\001VERSION\001" {
|
||
//nw.outq <- "NOTICE " + m.Sender + " :\001VERSION Spongy v8294.003.1R6pl58₄SEσ\001"
|
||
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION begin 644 version.txt\001"
|
||
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION F4W!O;F=Y('9E<G-I;VX@.#(Y-\"XP,#,N,5(V<&PU..*\"A%-%SX,`\001"
|
||
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION `\001"
|
||
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION end\001"
|
||
}
|
||
}
|
||
|
||
handlerPath := path.Join(nw.basePath, "handler")
|
||
cmd := exec.Command(handlerPath, m.Args...)
|
||
cmd.Env = os.Environ()
|
||
cmd.Env = append(cmd.Env, "command=" + m.Command)
|
||
cmd.Env = append(cmd.Env, "fullsender=" + m.FullSender)
|
||
cmd.Env = append(cmd.Env, "sender=" + m.Sender)
|
||
cmd.Env = append(cmd.Env, "forum=" + m.Forum)
|
||
cmd.Env = append(cmd.Env, "text=" + m.Text)
|
||
cmd.Env = append(cmd.Env, "raw=" + line)
|
||
cmd.Stderr = os.Stderr
|
||
out, err := cmd.Output()
|
||
if err != nil {
|
||
log.Print(err)
|
||
continue
|
||
}
|
||
|
||
if len(out) > 0 {
|
||
outlines := strings.Split(string(out), "\n")
|
||
for _, line := range outlines {
|
||
if len(line) > 0 {
|
||
nw.outq <- line
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (nw *Network) ConnectToNextServer() bool {
|
||
servers, err := ReadLines(path.Join(nw.basePath, "server"))
|
||
if err != nil {
|
||
log.Printf("Couldn't find any servers to connect to in %s", nw.basePath)
|
||
return false
|
||
}
|
||
|
||
if nw.serverIndex > len(servers) {
|
||
nw.serverIndex = 0
|
||
}
|
||
server := servers[nw.serverIndex]
|
||
|
||
debug("Connecting to %s", server)
|
||
switch (server[0]) {
|
||
case '|':
|
||
parts := strings.Split(server[1:], " ")
|
||
nw.conn, err = StartStdioProcess(parts[0], parts[1:])
|
||
case '^':
|
||
nw.conn, err = net.Dial("tcp", server[1:])
|
||
default:
|
||
log.Print("Not validating server certificate!")
|
||
config := &tls.Config{
|
||
InsecureSkipVerify: true,
|
||
}
|
||
nw.conn, err = tls.Dial("tcp", server, config)
|
||
}
|
||
|
||
if err != nil {
|
||
log.Print(err)
|
||
return false
|
||
}
|
||
debug("Connected")
|
||
|
||
return true
|
||
}
|
||
|
||
func (nw *Network) login() {
|
||
var name string
|
||
var username string
|
||
|
||
usernames, err := ReadLines(path.Join(nw.basePath, "username"))
|
||
if err == nil {
|
||
username = usernames[0]
|
||
} else {
|
||
username = "sponge"
|
||
}
|
||
|
||
passwd, err := ReadLines(path.Join(nw.basePath, "passwd"))
|
||
if err == nil {
|
||
nw.outq <- "PASS " + passwd[0]
|
||
}
|
||
|
||
|
||
names, err := ReadLines(path.Join(nw.basePath, "name"))
|
||
if err == nil {
|
||
name = names[0]
|
||
}
|
||
|
||
if name == "" {
|
||
me, err := user.Current()
|
||
if err == nil {
|
||
name = me.Name
|
||
}
|
||
}
|
||
|
||
if name == "" {
|
||
// Rogue used "Rodney" if you didn't give it a name.
|
||
// This one works for the ladies, too.
|
||
name = "Ronnie"
|
||
}
|
||
|
||
nw.outq <- "USER " + username + " g g :" + name
|
||
nw.NextNick()
|
||
}
|
||
|
||
func (nw *Network) keepaliveLoop() {
|
||
for nw.running {
|
||
time.Sleep(1 * time.Minute)
|
||
nw.outq <- "PING :keepalive"
|
||
}
|
||
}
|
||
|
||
|
||
func (nw *Network) Connect() {
|
||
for nw.running {
|
||
if ! nw.ConnectToNextServer() {
|
||
time.Sleep(8 * time.Second)
|
||
continue
|
||
}
|
||
|
||
nw.inq = make(chan string, 20)
|
||
nw.outq = make(chan string, 20)
|
||
|
||
go nw.serverWriteLoop()
|
||
go nw.messageDispatchLoop()
|
||
go nw.watchOutqDirectory()
|
||
go nw.keepaliveLoop()
|
||
|
||
nw.login()
|
||
|
||
scanner := bufio.NewScanner(nw.conn)
|
||
for scanner.Scan() {
|
||
nw.inq <- scanner.Text()
|
||
}
|
||
|
||
close(nw.inq)
|
||
close(nw.outq)
|
||
}
|
||
}
|
||
|