circa/main.go

255 lines
5.8 KiB
Go

package main
import (
"bufio"
"fmt"
"io"
"log"
"strings"
"time"
"code.rocketnine.space/tslocum/cview"
"github.com/gdamore/tcell/v2"
"github.com/lrstanley/girc"
)
func main() {
cview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack.TrueColor()
cview.Styles.ContrastBackgroundColor = tcell.ColorBlack.TrueColor()
cview.Styles.MoreContrastBackgroundColor = tcell.ColorBlack.TrueColor()
app := cview.NewApplication()
buffer := cview.NewTextView()
buffer.SetScrollBarVisibility(cview.ScrollBarAlways)
buffer.SetDynamicColors(true)
drawBuffer := func() {
if len(state.channels) == 0 {
return
}
buffer.Clear()
for _, ch := range state.channels {
for i := range ch.buffer {
buffer.Write(ch.buffer[i])
buffer.Write([]byte("\n"))
}
break
}
app.Draw()
}
writeMsg := func(s string) {
buffer.Write(append(cview.EscapeBytes([]byte(s)), '\n'))
app.Draw()
}
writeDebugMsg := func(s string) {
var channel *Channel
for _, ch := range state.channels {
channel = ch
break
}
if channel == nil {
buffer.Write(append(cview.EscapeBytes([]byte("DEBUG: "+s)), '\n'))
app.Draw()
} else {
channel.buffer = append(channel.buffer, []byte("DEBUG: "+s))
drawBuffer()
}
}
_ = writeDebugMsg
bufR, bufW := io.Pipe()
go func() {
scanner := bufio.NewScanner(bufR)
for scanner.Scan() {
writeMsg(scanner.Text())
}
}()
client := girc.New(girc.Config{
Server: "irc.7chan.org",
Port: 6667,
Nick: "tee2",
User: "tee2",
Name: "cIRCa",
Version: "cIRCa https://code.rocket9labs.com/tslocum/circa",
Out: bufW,
RecoverFunc: func(c *girc.Client, e *girc.HandlerError) {
recover()
},
})
client.DisableTracking()
input := cview.NewInputField()
input.SetDoneFunc(func(_ tcell.Key) {
defer func() {
input.SetText("")
}()
split := strings.Split(input.GetText(), " ")
if len(split) == 0 {
return
}
cmd := split[0]
var params []string
if len(split) > 0 {
params = split[1:]
}
if cmd[0] == '/' {
cmd = strings.ToUpper(cmd[1:])
} else {
cmd = girc.PRIVMSG
var channel *Channel
for _, ch := range state.channels {
channel = ch
break
}
if channel == nil {
panic("TODO")
}
params = []string{channel.name, input.GetText()}
}
e := &girc.Event{Command: cmd, Params: params}
client.Send(e)
if e.Command == girc.PRIVMSG && len(params) > 0 {
for name, channel := range state.channels {
if strings.ToLower(name) == strings.ToLower(params[0]) {
channel.buffer = append(channel.buffer, []byte(fmt.Sprintf("<%s> %s", client.Config.Nick, strings.Join(params[1:], " "))))
break
}
}
} else {
// TODO write to status window instead
// writeMsg(fmt.Sprintf("%+v", &girc.Event{Command: cmd, Params: params}))
}
drawBuffer()
return
})
sideBar := cview.NewTextView()
sideBar.SetScrollBarVisibility(cview.ScrollBarAlways)
flexHorizontal := cview.NewFlex()
flexHorizontal.SetDirection(cview.FlexColumn)
flexHorizontal.AddItem(sideBar, 15, 0, false)
flexHorizontal.AddItem(cview.NewBox(), 1, 0, false)
flexHorizontal.AddItem(buffer, 0, 1, false)
statusLine := cview.NewTextView()
statusLine.SetTextColor(tcell.NewRGBColor(255, 255, 255))
statusLine.SetBackgroundColor(tcell.NewRGBColor(50, 50, 50))
statusLine.SetDynamicColors(true)
statusLine.Write([]byte("[::b]-- INSERT --[::-]"))
flex := cview.NewFlex()
flex.SetDirection(cview.FlexRow)
flex.AddItem(flexHorizontal, 0, 1, false)
flex.AddItem(statusLine, 1, 0, false)
flex.AddItem(input, 1, 0, true)
app.SetRoot(flex, true)
updateSideBar := func() {
sideBar.Clear()
var i int
for name, channel := range state.channels {
if i != 0 {
sideBar.Write([]byte("\n"))
}
sideBar.Write([]byte(name))
_ = channel
i++
}
app.Draw()
}
go func() {
client.Handlers.Add(girc.CONNECTED, func(c *girc.Client, e girc.Event) {
// TODO
})
client.Handlers.Add(girc.PRIVMSG, func(c *girc.Client, e girc.Event) {
if len(e.Params) == 0 {
return
}
paramChannel := e.Params[0]
paramMessage := e.Params[1:]
spaceIndex := strings.IndexRune(paramChannel, ' ')
if spaceIndex != -1 {
paramChannel = e.Params[0][:spaceIndex]
paramMessage = append([]string{e.Params[0][spaceIndex+1:]}, e.Params[1:]...)
}
writeDebugMsg(fmt.Sprintf("%s %+v %+v", paramChannel, paramMessage, e))
var channel *Channel
for name, ch := range state.channels {
if name == paramChannel {
channel = ch
break
}
}
if channel == nil {
// TODO this is a private privmsg, or external message
log.Fatal("no channel")
return
}
channel.buffer = append(channel.buffer, []byte(fmt.Sprintf("<%s> %s", e.Source.Name, strings.Join(paramMessage, " "))))
drawBuffer()
})
client.Handlers.Add(girc.JOIN, func(c *girc.Client, e girc.Event) {
if len(e.Params) == 0 {
return
}
if e.Source.Name == c.Config.Nick {
for _, channelName := range e.Params {
state.channels[channelName] = &Channel{
name: channelName,
}
}
updateSideBar()
} else {
// someone else joined a channel we are in
writeDebugMsg(fmt.Sprintf("other join %+v", e))
}
var channel *Channel
for name, ch := range state.channels {
if name == e.Params[0] {
channel = ch
break
}
}
if channel == nil {
// TODO this is a private privmsg, or external message
return
}
channel.buffer = append(channel.buffer, []byte(fmt.Sprintf("* %s joined %s", e.Source.Name, e.Params[1:])))
drawBuffer()
})
// An example of how you would add reconnect logic.
for {
if err := client.Connect(); err != nil {
writeMsg(fmt.Sprintf("error: %s", err))
writeMsg("reconnecting in 30 seconds...")
time.Sleep(30 * time.Second)
} else {
return
}
}
}()
err := app.Run()
if err != nil {
log.Fatal(err)
}
}