diff options
| -rw-r--r-- | app.go | 307 | ||||
| -rw-r--r-- | cmd/irc/main.go | 256 | ||||
| -rw-r--r-- | config.go | 20 | ||||
| -rw-r--r-- | irc/states.go | 4 |
4 files changed, 324 insertions, 263 deletions
@@ -0,0 +1,307 @@ +package senpai + +import ( + "crypto/tls" + "fmt" + "log" + "strings" + "time" + + "git.sr.ht/~taiite/senpai/irc" + "git.sr.ht/~taiite/senpai/ui" + "github.com/gdamore/tcell" +) + +type App struct { + win *ui.UI + s irc.Session + + cfg Config + highlights []string +} + +func NewApp(cfg Config) (app *App, err error) { + app = &App{} + + app.win, err = ui.New() + if err != nil { + return + } + + var conn *tls.Conn + app.win.AddLine(ui.Home, ui.NewLineNow("--", fmt.Sprintf("Connecting to %s...", cfg.Addr)), false) + conn, err = tls.Dial("tcp", cfg.Addr, nil) + if err != nil { + return + } + + var auth irc.SASLClient + if cfg.Password != nil { + auth = &irc.SASLPlain{Username: cfg.User, Password: *cfg.Password} + } + app.s, err = irc.NewSession(conn, irc.SessionParams{ + Nickname: cfg.Nick, + Username: cfg.User, + RealName: cfg.Real, + Auth: auth, + }) + if err != nil { + return + } + + if cfg.Highlights != nil { + app.highlights = cfg.Highlights + for i := range app.highlights { + app.highlights[i] = strings.ToLower(app.highlights[i]) + } + } else { + app.highlights = []string{app.s.LNick()} + } + + return +} + +func (app *App) Close() { + app.win.Close() + app.s.Stop() +} + +func (app *App) Run() { + for !app.win.ShouldExit() { + select { + case ev := <-app.s.Poll(): + app.handleIRCEvent(ev) + case ev := <-app.win.Events: + app.handleUIEvent(ev) + } + } +} + +func (app *App) handleIRCEvent(ev irc.Event) { + switch ev := ev.(type) { + case irc.RegisteredEvent: + app.win.AddLine("", ui.NewLineNow("--", "Connected to the server"), false) + if app.cfg.Highlights == nil { + app.highlights[0] = app.s.LNick() + } + case irc.SelfJoinEvent: + app.win.AddBuffer(ev.Channel) + case irc.UserJoinEvent: + line := fmt.Sprintf("\x033+\x0314%s", ev.Nick) + app.win.AddLine(ev.Channel, ui.NewLine(ev.Time, "--", line, true), false) + case irc.SelfPartEvent: + app.win.RemoveBuffer(ev.Channel) + case irc.UserPartEvent: + line := fmt.Sprintf("\x034-\x0314%s", ev.Nick) + app.win.AddLine(ev.Channel, ui.NewLine(ev.Time, "--", line, true), false) + case irc.QueryMessageEvent: + if ev.Command == "PRIVMSG" { + l := ui.LineFromIRCMessage(ev.Time, ev.Nick, ev.Content, false) + app.win.AddLine(ui.Home, l, true) + app.win.TypingStop(ui.Home, ev.Nick) + } else if ev.Command == "NOTICE" { + l := ui.LineFromIRCMessage(ev.Time, ev.Nick, ev.Content, true) + app.win.AddLine("", l, true) + app.win.TypingStop("", ev.Nick) + } else { + log.Panicf("received unknown command for query event: %q\n", ev.Command) + } + case irc.ChannelMessageEvent: + l := ui.LineFromIRCMessage(ev.Time, ev.Nick, ev.Content, ev.Command == "NOTICE") + + lContent := strings.ToLower(ev.Content) + isHighlight := false + for _, h := range app.highlights { + if strings.Contains(lContent, h) { + isHighlight = true + break + } + } + + app.win.AddLine(ev.Channel, l, isHighlight) + app.win.TypingStop(ev.Channel, ev.Nick) + case irc.QueryTypingEvent: + if ev.State == 1 || ev.State == 2 { + app.win.TypingStart(ui.Home, ev.Nick) + } else { + app.win.TypingStop(ui.Home, ev.Nick) + } + case irc.ChannelTypingEvent: + if ev.State == 1 || ev.State == 2 { + app.win.TypingStart(ev.Channel, ev.Nick) + } else { + app.win.TypingStop(ev.Channel, ev.Nick) + } + case irc.HistoryEvent: + var lines []ui.Line + for _, m := range ev.Messages { + switch m := m.(type) { + case irc.ChannelMessageEvent: + l := ui.LineFromIRCMessage(m.Time, m.Nick, m.Content, m.Command == "NOTICE") + lines = append(lines, l) + default: + panic("TODO") + } + } + app.win.AddLines(ev.Target, lines) + case error: + log.Panicln(ev) + } +} + +func (app *App) handleUIEvent(ev tcell.Event) { + switch ev := ev.(type) { + case *tcell.EventResize: + app.win.Resize() + case *tcell.EventKey: + switch ev.Key() { + case tcell.KeyCtrlC: + app.win.Exit() + case tcell.KeyCtrlL: + app.win.Resize() + case tcell.KeyCtrlU, tcell.KeyPgUp: + app.win.ScrollUp() + if app.win.IsAtTop() { + buffer := app.win.CurrentBuffer() + at := time.Now() + if t := app.win.CurrentBufferOldestTime(); t != nil { + at = *t + } + app.s.RequestHistory(buffer, at) + } + case tcell.KeyCtrlD, tcell.KeyPgDn: + app.win.ScrollDown() + case tcell.KeyCtrlN: + app.win.NextBuffer() + if app.win.IsAtTop() { + buffer := app.win.CurrentBuffer() + at := time.Now() + if t := app.win.CurrentBufferOldestTime(); t != nil { + at = *t + } + app.s.RequestHistory(buffer, at) + } + case tcell.KeyCtrlP: + app.win.PreviousBuffer() + if app.win.IsAtTop() { + buffer := app.win.CurrentBuffer() + at := time.Now() + if t := app.win.CurrentBufferOldestTime(); t != nil { + at = *t + } + app.s.RequestHistory(buffer, at) + } + case tcell.KeyRight: + if ev.Modifiers() == tcell.ModAlt { + app.win.NextBuffer() + if app.win.IsAtTop() { + buffer := app.win.CurrentBuffer() + at := time.Now() + if t := app.win.CurrentBufferOldestTime(); t != nil { + at = *t + } + app.s.RequestHistory(buffer, at) + } + } else { + app.win.InputRight() + } + case tcell.KeyLeft: + if ev.Modifiers() == tcell.ModAlt { + app.win.PreviousBuffer() + if app.win.IsAtTop() { + buffer := app.win.CurrentBuffer() + at := time.Now() + if t := app.win.CurrentBufferOldestTime(); t != nil { + at = *t + } + app.s.RequestHistory(buffer, at) + } + } else { + app.win.InputLeft() + } + case tcell.KeyBackspace2: + ok := app.win.InputBackspace() + if ok && app.win.InputLen() == 0 { + app.s.TypingStop(app.win.CurrentBuffer()) + } + case tcell.KeyEnter: + buffer := app.win.CurrentBuffer() + input := app.win.InputEnter() + app.handleInput(buffer, input) + case tcell.KeyRune: + app.win.InputRune(ev.Rune()) + if app.win.CurrentBuffer() != ui.Home && !app.win.InputIsCommand() { + app.s.Typing(app.win.CurrentBuffer()) + } + } + } +} + +func parseCommand(s string) (command, args string) { + if s == "" { + return + } + + if s[0] != '/' { + args = s + return + } + + i := strings.IndexByte(s, ' ') + if i < 0 { + i = len(s) + } + + command = strings.ToUpper(s[1:i]) + args = strings.TrimLeft(s[i:], " ") + + return +} + +func (app *App) handleInput(buffer, content string) { + cmd, args := parseCommand(content) + + switch cmd { + case "": + if buffer == ui.Home || len(strings.TrimSpace(args)) == 0 { + return + } + + app.s.PrivMsg(buffer, args) + if !app.s.HasCapability("echo-message") { + app.win.AddLine(buffer, ui.NewLineNow(app.s.Nick(), args), false) + } + case "QUOTE": + app.s.SendRaw(args) + case "J", "JOIN": + app.s.Join(args) + case "PART": + if buffer == ui.Home { + return + } + + if args == "" { + args = buffer + } + + app.s.Part(args) + case "ME": + if buffer == ui.Home { + return + } + + line := fmt.Sprintf("\x01ACTION %s\x01", args) + app.s.PrivMsg(buffer, line) + // TODO echo message + case "MSG": + split := strings.SplitN(args, " ", 2) + if len(split) < 2 { + return + } + + target := split[0] + content := split[1] + app.s.PrivMsg(target, content) + // TODO echo mssage + } +} diff --git a/cmd/irc/main.go b/cmd/irc/main.go index b2b0705..cd9c263 100644 --- a/cmd/irc/main.go +++ b/cmd/irc/main.go @@ -1,18 +1,13 @@ package main import ( - "crypto/tls" "flag" - "fmt" "log" "math/rand" "os" - "strings" "time" "git.sr.ht/~taiite/senpai" - "git.sr.ht/~taiite/senpai/irc" - "git.sr.ht/~taiite/senpai/ui" "github.com/gdamore/tcell" ) @@ -40,258 +35,11 @@ func main() { log.Panicln(err) } - app, err := ui.New() + app, err := senpai.NewApp(cfg) if err != nil { log.Panicln(err) } defer app.Close() - addr := cfg.Addr - app.AddLine(ui.Home, ui.NewLineNow("--", fmt.Sprintf("Connecting to %s...", addr)), false) - - conn, err := tls.Dial("tcp", addr, nil) - if err != nil { - log.Panicln(err) - } - - var auth irc.SASLClient - if cfg.Password != "" { - auth = &irc.SASLPlain{Username: cfg.User, Password: cfg.Password} - } - s, err := irc.NewSession(conn, irc.SessionParams{ - Nickname: cfg.Nick, - Username: cfg.User, - RealName: cfg.Real, - Auth: auth, - }) - if err != nil { - log.Panicln(err) - } - defer s.Stop() - - for !app.ShouldExit() { - select { - case ev := <-s.Poll(): - handleIRCEvent(app, &s, ev) - case ev := <-app.Events: - handleUIEvent(app, &s, ev) - } - } -} - -func handleIRCEvent(app *ui.UI, s *irc.Session, ev irc.Event) { - switch ev := ev.(type) { - case irc.RegisteredEvent: - app.AddLine("", ui.NewLineNow("--", "Connected to the server"), false) - case irc.SelfJoinEvent: - app.AddBuffer(ev.Channel) - case irc.UserJoinEvent: - line := fmt.Sprintf("\x033+\x0314%s", ev.Nick) - app.AddLine(ev.Channel, ui.NewLine(ev.Time, "--", line, true), false) - case irc.SelfPartEvent: - app.RemoveBuffer(ev.Channel) - case irc.UserPartEvent: - line := fmt.Sprintf("\x034-\x0314%s", ev.Nick) - app.AddLine(ev.Channel, ui.NewLine(ev.Time, "--", line, true), false) - case irc.QueryMessageEvent: - if ev.Command == "PRIVMSG" { - l := ui.LineFromIRCMessage(ev.Time, ev.Nick, ev.Content, false) - app.AddLine(ui.Home, l, true) - app.TypingStop(ui.Home, ev.Nick) - } else if ev.Command == "NOTICE" { - l := ui.LineFromIRCMessage(ev.Time, ev.Nick, ev.Content, true) - app.AddLine("", l, true) - app.TypingStop("", ev.Nick) - } else { - panic("unknown command") - } - case irc.ChannelMessageEvent: - l := ui.LineFromIRCMessage(ev.Time, ev.Nick, ev.Content, ev.Command == "NOTICE") - isHighlight := strings.Contains(strings.ToLower(ev.Content), strings.ToLower(s.Nick())) - app.AddLine(ev.Channel, l, isHighlight) - app.TypingStop(ev.Channel, ev.Nick) - case irc.QueryTypingEvent: - if ev.State == 1 || ev.State == 2 { - app.TypingStart(ui.Home, ev.Nick) - } else { - app.TypingStop(ui.Home, ev.Nick) - } - case irc.ChannelTypingEvent: - if ev.State == 1 || ev.State == 2 { - app.TypingStart(ev.Channel, ev.Nick) - } else { - app.TypingStop(ev.Channel, ev.Nick) - } - case irc.HistoryEvent: - var lines []ui.Line - for _, m := range ev.Messages { - switch m := m.(type) { - case irc.ChannelMessageEvent: - l := ui.LineFromIRCMessage(m.Time, m.Nick, m.Content, m.Command == "NOTICE") - lines = append(lines, l) - default: - panic("TODO") - } - } - app.AddLines(ev.Target, lines) - case error: - log.Panicln(ev) - } -} - -func handleUIEvent(app *ui.UI, s *irc.Session, ev tcell.Event) { - switch ev := ev.(type) { - case *tcell.EventResize: - app.Resize() - case *tcell.EventKey: - switch ev.Key() { - case tcell.KeyCtrlC: - app.Exit() - case tcell.KeyCtrlL: - app.Resize() - case tcell.KeyCtrlU, tcell.KeyPgUp: - app.ScrollUp() - if app.IsAtTop() { - buffer := app.CurrentBuffer() - at := time.Now() - if t := app.CurrentBufferOldestTime(); t != nil { - at = *t - } - s.RequestHistory(buffer, at) - } - case tcell.KeyCtrlD, tcell.KeyPgDn: - app.ScrollDown() - case tcell.KeyCtrlN: - app.NextBuffer() - if app.IsAtTop() { - buffer := app.CurrentBuffer() - at := time.Now() - if t := app.CurrentBufferOldestTime(); t != nil { - at = *t - } - s.RequestHistory(buffer, at) - } - case tcell.KeyCtrlP: - app.PreviousBuffer() - if app.IsAtTop() { - buffer := app.CurrentBuffer() - at := time.Now() - if t := app.CurrentBufferOldestTime(); t != nil { - at = *t - } - s.RequestHistory(buffer, at) - } - case tcell.KeyRight: - if ev.Modifiers() == tcell.ModAlt { - app.NextBuffer() - if app.IsAtTop() { - buffer := app.CurrentBuffer() - at := time.Now() - if t := app.CurrentBufferOldestTime(); t != nil { - at = *t - } - s.RequestHistory(buffer, at) - } - } else { - app.InputRight() - } - case tcell.KeyLeft: - if ev.Modifiers() == tcell.ModAlt { - app.PreviousBuffer() - if app.IsAtTop() { - buffer := app.CurrentBuffer() - at := time.Now() - if t := app.CurrentBufferOldestTime(); t != nil { - at = *t - } - s.RequestHistory(buffer, at) - } - } else { - app.InputLeft() - } - case tcell.KeyBackspace2: - ok := app.InputBackspace() - if ok && app.InputLen() == 0 { - s.TypingStop(app.CurrentBuffer()) - } - case tcell.KeyEnter: - buffer := app.CurrentBuffer() - input := app.InputEnter() - handleInput(app, s, buffer, input) - case tcell.KeyRune: - app.InputRune(ev.Rune()) - if app.CurrentBuffer() != ui.Home && !app.InputIsCommand() { - s.Typing(app.CurrentBuffer()) - } - } - } -} - -func parseCommand(s string) (command, args string) { - if s == "" { - return - } - - if s[0] != '/' { - args = s - return - } - - i := strings.IndexByte(s, ' ') - if i < 0 { - i = len(s) - } - - command = strings.ToUpper(s[1:i]) - args = strings.TrimLeft(s[i:], " ") - - return -} - -func handleInput(app *ui.UI, s *irc.Session, buffer, content string) { - cmd, args := parseCommand(content) - - switch cmd { - case "": - if buffer == ui.Home || len(strings.TrimSpace(args)) == 0 { - return - } - - s.PrivMsg(buffer, args) - if !s.HasCapability("echo-message") { - app.AddLine(buffer, ui.NewLineNow(s.Nick(), args), false) - } - case "QUOTE": - s.SendRaw(args) - case "J", "JOIN": - s.Join(args) - case "PART": - if buffer == ui.Home { - return - } - - if args == "" { - args = buffer - } - - s.Part(args) - case "ME": - if buffer == ui.Home { - return - } - - line := fmt.Sprintf("\x01ACTION %s\x01", args) - s.PrivMsg(buffer, line) - // TODO echo message - case "MSG": - split := strings.SplitN(args, " ", 2) - if len(split) < 2 { - return - } - - target := split[0] - content := split[1] - s.PrivMsg(target, content) - // TODO echo mssage - } + app.Run() } @@ -1,16 +1,18 @@ package senpai import ( - "gopkg.in/yaml.v2" "io/ioutil" + + "gopkg.in/yaml.v2" ) type Config struct { - Addr string - Nick string - Real string - User string - Password string + Addr string + Nick string + Real string + User string + Password *string + Highlights []string } func ParseConfig(buf []byte) (cfg Config, err error) { @@ -20,12 +22,12 @@ func ParseConfig(buf []byte) (cfg Config, err error) { func LoadConfigFile(filename string) (cfg Config, err error) { var buf []byte - + buf, err = ioutil.ReadFile(filename) if err != nil { - return + return } - + cfg, err = ParseConfig(buf) return diff --git a/irc/states.go b/irc/states.go index 1817882..5585990 100644 --- a/irc/states.go +++ b/irc/states.go @@ -229,6 +229,10 @@ func (s *Session) Nick() string { return s.nick } +func (s *Session) LNick() string { + return s.lNick +} + func (s *Session) IsChannel(name string) bool { return strings.IndexAny(name, "#&") == 0 // TODO compute CHANTYPES } |
