diff --git a/consts.go b/consts.go index b013377..b9a58cc 100644 --- a/consts.go +++ b/consts.go @@ -273,6 +273,10 @@ func GetVideotextCharByte(c byte) byte { return byte(strings.LastIndexByte(CharTable, c)) } -func IsValidChar(c byte) bool { +func IsByteAValidChar(c byte) bool { return c >= Sp && c <= Del } + +func IsUintAValidChar(u uint) bool { + return u >= Sp && u <= Del +} diff --git a/examples/chat/irc.go b/examples/chat/irc.go new file mode 100644 index 0000000..efa9fba --- /dev/null +++ b/examples/chat/irc.go @@ -0,0 +1,46 @@ +package main + +import ( + "crypto/tls" + "fmt" + + irc "github.com/thoj/go-ircevent" +) + +const channel = "#go-eventirc-test" +const serverssl = "irc.freenode.net:7000" + +func startIRC(envoi chan []byte, messageList *Messages) { + ircnick1 := "minitel" + irccon := irc.IRC(ircnick1, "IRCTestSSL") + irccon.UseTLS = true + irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} + irccon.AddCallback("001", func(e *irc.Event) { irccon.Join(channel) }) + irccon.AddCallback("366", func(e *irc.Event) {}) + + irccon.AddCallback("PRIVMSG", func(event *irc.Event) { + msg := event.Message() + nick := event.Nick + + messageList.AppendMessage(nick, msg) + fmt.Printf("%s: %s\n", nick, msg) + }) + + go func() { + for { + select { + case msg := <-envoi: + irccon.Privmsg(channel, string(msg)) + default: + continue + } + } + }() + + err := irccon.Connect(serverssl) + if err != nil { + fmt.Printf("Err %s", err) + return + } + irccon.Loop() +} diff --git a/examples/chat/main.go b/examples/chat/main.go new file mode 100644 index 0000000..053261e --- /dev/null +++ b/examples/chat/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "time" + + "github.com/NoelM/minigo" + "nhooyr.io/websocket" +) + +func main() { + fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c, err := websocket.Accept(w, r, &websocket.AcceptOptions{OriginPatterns: []string{"*"}}) + if err != nil { + log.Println(err) + return + } + defer c.Close(websocket.StatusInternalError, "the sky is falling") + + ctx, cancel := context.WithTimeout(r.Context(), time.Minute*10) + defer cancel() + + recvKey := make(chan uint) + go listenKeys(c, ctx, recvKey) + + envoi := make(chan []byte) + messageList := Messages{} + go startIRC(envoi, &messageList) + + chat(c, ctx, recvKey, envoi, &messageList) + }) + + err := http.ListenAndServe("192.168.1.34:3615", fn) + log.Fatal(err) +} + +func listenKeys(c *websocket.Conn, ctx context.Context, recvChan chan uint) { + fullRead := true + var keyBuffer []byte + var keyValue uint + var done bool + + for { + var err error + var wsMsg []byte + + if fullRead { + _, wsMsg, err = c.Read(ctx) + if err != nil { + continue + } + fullRead = false + } + + for id, b := range wsMsg { + keyBuffer = append(keyBuffer, b) + + done, keyValue, err = minigo.ReadKey(keyBuffer) + if done || err != nil { + keyBuffer = []byte{} + } + if done { + recvChan <- keyValue + } + + if id == len(wsMsg)-1 { + fullRead = true + } + } + + if ctx.Err() != nil { + return + } + } +} + +func chat(c *websocket.Conn, ctx context.Context, recvKey chan uint, envoi chan []byte, messagesList *Messages) { + userInput := []byte{} + + for { + select { + case key := <-recvKey: + if key == minigo.Envoi { + messagesList.AppendTeletelMessage("minitel", userInput) + envoi <- userInput + + clearInput(c, ctx) + updateScreen(c, ctx, messagesList) + userInput = []byte{} + + } else if key == minigo.Repetition { + updateScreen(c, ctx, messagesList) + updateInput(c, ctx, userInput) + + } else if key == minigo.Correction { + corrInput(c, ctx, len(userInput)) + userInput = userInput[:len(userInput)-2] + + } else if minigo.IsUintAValidChar(key) { + appendInput(c, ctx, len(userInput), byte(key)) + userInput = append(userInput, byte(key)) + + } else { + fmt.Printf("key: %d not supported", key) + } + default: + continue + } + + if ctx.Err() != nil { + return + } + } +} diff --git a/examples/chat/message.go b/examples/chat/message.go new file mode 100644 index 0000000..4a938af --- /dev/null +++ b/examples/chat/message.go @@ -0,0 +1,43 @@ +package main + +import "sync" + +type MessageType int64 + +const ( + Message_UTF8 MessageType = iota + Message_Teletel +) + +type Message struct { + Nick string + Text string + Type MessageType +} + +type Messages struct { + List []Message + Mtx sync.RWMutex +} + +func (m *Messages) AppendTeletelMessage(nick string, text []byte) { + m.Mtx.Lock() + defer m.Mtx.Unlock() + + m.List = append(m.List, Message{ + Nick: nick, + Text: string(text), + Type: Message_Teletel, + }) +} + +func (m *Messages) AppendMessage(nick string, text string) { + m.Mtx.Lock() + defer m.Mtx.Unlock() + + m.List = append(m.List, Message{ + Nick: nick, + Text: text, + Type: Message_UTF8, + }) +} diff --git a/examples/chat/screen.go b/examples/chat/screen.go new file mode 100644 index 0000000..44feaac --- /dev/null +++ b/examples/chat/screen.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + "fmt" + + "github.com/NoelM/minigo" + "nhooyr.io/websocket" +) + +func clearInput(c *websocket.Conn, ctx context.Context) { + buf := minigo.GetMoveCursorXY(1, 20) + buf = append(buf, minigo.GetCleanScreenFromCursor()...) + c.Write(ctx, websocket.MessageBinary, buf) +} + +func updateScreen(c *websocket.Conn, ctx context.Context, list *Messages) { + currentLine := 1 + + list.Mtx.RLock() + defer list.Mtx.RUnlock() + + for i := len(list.List) - 1; i >= 0; i -= 1 { + // 3 because the format is: "nick > text" + msgLen := len(list.List[i].Nick) + len(list.List[i].Text) + 3 + + // 2 because if msgLen < 40, the divide gives 0 and one break another line for readability + // nick > text + // + // nick > text2 + msgLines := msgLen/40 + 2 + + if currentLine+msgLines > 20 { + break + } + + buf := minigo.GetMoveCursorXY(0, currentLine) + buf = append(buf, minigo.EncodeMessage(fmt.Sprintf("%s > ", list.List[i].Nick))...) + + if list.List[i].Type == Message_Teletel { + buf = append(buf, list.List[i].Text...) + } else { + buf = append(buf, minigo.EncodeMessage(list.List[i].Text)...) + } + + buf = append(buf, minigo.GetCleanLineFromCursor()...) + buf = append(buf, minigo.GetMoveCursorReturn(1)...) + buf = append(buf, minigo.GetCleanLine()...) + c.Write(ctx, websocket.MessageBinary, buf) + + currentLine += msgLines + } +} + +func appendInput(c *websocket.Conn, ctx context.Context, inputLen int, key byte) { + y := inputLen / 40 + x := inputLen % 40 + + buf := minigo.GetMoveCursorXY(x+1, y+20) + buf = append(buf, key) + c.Write(ctx, websocket.MessageBinary, buf) +} + +func corrInput(c *websocket.Conn, ctx context.Context, inputLen int) { + y := (inputLen - 1) / 40 + x := (inputLen - 1) % 40 + + buf := minigo.GetMoveCursorXY(x+1, y+20) + buf = append(buf, minigo.GetCleanLineFromCursor()...) + c.Write(ctx, websocket.MessageBinary, buf) +} + +func updateInput(c *websocket.Conn, ctx context.Context, userInput []byte) { + buf := minigo.GetMoveCursorXY(1, 20) + buf = append(buf, userInput...) + c.Write(ctx, websocket.MessageBinary, buf) +} diff --git a/examples/irc/main.go b/examples/irc/main.go new file mode 100644 index 0000000..9bb04e0 --- /dev/null +++ b/examples/irc/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "crypto/tls" + "fmt" + + irc "github.com/thoj/go-ircevent" +) + +const channel = "#go-eventirc-test" +const serverssl = "irc.freenode.net:7000" + +func main() { + ircnick1 := "blatiblat" + irccon := irc.IRC(ircnick1, "IRCTestSSL") + //irccon.VerboseCallbackHandler = true + //irccon.Debug = true + irccon.UseTLS = true + irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} + irccon.AddCallback("001", func(e *irc.Event) { irccon.Join(channel) }) + irccon.AddCallback("366", func(e *irc.Event) {}) + irccon.AddCallback("PRIVMSG", func(event *irc.Event) { + msg := event.Message() + nick := event.Nick + fmt.Printf("%s: %s\n", nick, msg) + }) + err := irccon.Connect(serverssl) + if err != nil { + fmt.Printf("Err %s", err) + return + } + irccon.Loop() +} diff --git a/go.mod b/go.mod index 10f731f..4d3e397 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.17 require ( github.com/gobwas/ws v1.0.2 + github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 nhooyr.io/websocket v1.8.7 ) @@ -11,5 +12,8 @@ require ( github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect github.com/gobwas/pool v0.2.0 // indirect github.com/klauspost/compress v1.10.3 // indirect + golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.3.6 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/teletel.go b/teletel.go index 696eebe..d2cdd70 100644 --- a/teletel.go +++ b/teletel.go @@ -1,6 +1,7 @@ package minigo import ( + "encoding/binary" "errors" "fmt" "log" @@ -181,7 +182,7 @@ func GetCleanLineToCursor() (buf []byte) { func EncodeChar(c int32) (byte, error) { vdtByte := GetVideotextCharByte(byte(c)) - if IsValidChar(vdtByte) { + if IsByteAValidChar(vdtByte) { return vdtByte, nil } return 0, errors.New("invalid char byte") @@ -243,3 +244,61 @@ func GetCursorOn() byte { func AppendCursorOff() byte { return GetByteWithParity(CursorOff) } + +func ReadKey(keyBuffer []byte) (done bool, value uint, err error) { + if keyBuffer[0] == 0x19 { + if len(keyBuffer) == 1 { + return + } + + switch keyBuffer[1] { + case 0x23: + keyBuffer = []byte{0xA3} + case 0x27: + keyBuffer = []byte{0xA7} + case 0x30: + keyBuffer = []byte{0xB0} + case 0x31: + keyBuffer = []byte{0xB1} + case 0x38: + keyBuffer = []byte{0xF7} + case 0x7B: + keyBuffer = []byte{0xDF} + } + } else if keyBuffer[0] == 0x13 { + if len(keyBuffer) == 1 { + return + } + } else if keyBuffer[0] == 0x1B { + if len(keyBuffer) == 1 { + return + } + + if keyBuffer[1] == 0x5B { + if len(keyBuffer) == 2 { + return + } + + if keyBuffer[2] == 0x34 || keyBuffer[2] == 0x32 { + if len(keyBuffer) == 3 { + return + } + } + } + } + + done = true + + switch len(keyBuffer) { + case 1: + return done, uint(keyBuffer[0]), nil + case 2: + return done, uint(binary.BigEndian.Uint16(keyBuffer)), nil + case 3: + return done, uint(binary.BigEndian.Uint32(keyBuffer)), nil + case 4: + return done, uint(binary.BigEndian.Uint64(keyBuffer)), nil + default: + return done, 0, errors.New("unable to cast readbuffer") + } +}