summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app.go307
-rw-r--r--cmd/irc/main.go256
-rw-r--r--config.go20
-rw-r--r--irc/states.go4
4 files changed, 324 insertions, 263 deletions
diff --git a/app.go b/app.go
new file mode 100644
index 0000000..72f0b79
--- /dev/null
+++ b/app.go
@@ -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()
}
diff --git a/config.go b/config.go
index 0634dfc..ee76473 100644
--- a/config.go
+++ b/config.go
@@ -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
}