diff --git a/.gitea/workflows/go-audit.yaml b/.gitea/workflows/go-audit.yaml index 19095f2..70c5e4b 100644 --- a/.gitea/workflows/go-audit.yaml +++ b/.gitea/workflows/go-audit.yaml @@ -1,8 +1,6 @@ name: Go audit -run-name: ${{ gitea.actor }} pushed to main! 🚀 +run-name: ${{ gitea.actor }} pulled to main! 🚀 on: - push: - branches: [main] pull_request: branches: [main] jobs: diff --git a/README.md b/README.md index 50c99e6..25d6b01 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,13 @@ Tiny IRC-like chat server written in Go -## Usage examples +
+ +
+## Usage examples ### Starting the server: -./mini-chat server --port 1337 +./mini-chat server --port 1337 --history chat.log ### Connecting to the server: -./mini-chat client --ip 192.168.0.100 --port 1337 +./mini-chat client --ip 192.168.0.100 --port 1337 \ No newline at end of file diff --git a/cmd/clientFunc.go b/cmd/clientFunc.go index cb98a58..f8d2de8 100644 --- a/cmd/clientFunc.go +++ b/cmd/clientFunc.go @@ -22,6 +22,12 @@ type Message struct { Server net.Conn } +type ProfileData struct { + Username string +} + +var Profile ProfileData + func (m Message) toSend() { m.Server.Write([]byte(m.Contents)) } @@ -93,6 +99,8 @@ func sendName(conn net.Conn) { log.Fatalf("Error occurred sending message to server: %v\n", err) } + Profile.Username = strings.TrimRight(message, "\n") + if _, err := conn.Write([]byte(message)); err != nil { log.Fatalf("Error occurred writing to server: %v\n", err) } @@ -143,50 +151,91 @@ func sendToServer(g *gocui.Gui, v *gocui.View) error { return nil } +func scrollView(v *gocui.View, dy int) error { + if v != nil { + v.Autoscroll = false + ox, oy := v.Origin() + if err := v.SetOrigin(ox, oy+dy); err != nil { + return err + } + } + return nil +} + +func autoscroll(g *gocui.Gui, v *gocui.View) error { + chatbox, err := g.View("chatbox") + if err != nil { + log.Panicf("Error happened setting autoscroll: %v\n", err) + } + chatbox.Autoscroll = true + return nil +} + func initKeybindings(g *gocui.Gui) error { if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { log.Panicln(err) } - if err := g.SetKeybinding("button", gocui.MouseLeft, gocui.ModNone, sendToServer); err != nil { + // if err := g.SetKeybinding("button", gocui.MouseLeft, gocui.ModNone, sendToServer); err != nil { + // log.Panicln(err) + // } + + if err := g.SetKeybinding("textarea", gocui.KeyCtrlA, gocui.ModNone, autoscroll); err != nil { log.Panicln(err) } if err := g.SetKeybinding("textarea", gocui.KeyEnter, gocui.ModNone, sendToServer); err != nil { log.Panicln(err) } + + if err := g.SetKeybinding("chatbox", gocui.MouseWheelUp, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + scrollView(v, -1) + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("chatbox", gocui.MouseWheelDown, gocui.ModNone, + func(g *gocui.Gui, v *gocui.View) error { + scrollView(v, 1) + return nil + }); err != nil { + log.Panicln(err) + } + return nil } func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if chatbox, err := g.SetView("chatbox", 2, 1, maxX/2+40, maxY-5); err != nil { + if chatbox, err := g.SetView("chatbox", 2, 1, maxX-2, maxY-5); err != nil { if err != gocui.ErrUnknownView { return err } chatbox.Autoscroll = true - chatbox.Title = "Chat Box" + chatbox.Title = "Chat Box (Find source at https://git.bulgariu.xyz/raul/mini-chat!)" } - if button, err := g.SetView("button", maxX/2+32, maxY-4, maxX/2+40, maxY-2); err != nil { - if err != gocui.ErrUnknownView { - return err - } + // if button, err := g.SetView("button", maxX/2+32, maxY-4, maxX-28, maxY-2); err != nil { + // if err != gocui.ErrUnknownView { + // return err + // } + // + // button.Wrap = true + // button.Frame = true + // fmt.Fprintln(button, "Send it") + // } - button.Wrap = true - button.Frame = true - fmt.Fprintln(button, "Send it") - } - - if textarea, err := g.SetView("textarea", 2, maxY-4, maxX/2+28, maxY-2); err != nil { + if textarea, err := g.SetView("textarea", 2, maxY-4, maxX-2, maxY-2); err != nil { if err != gocui.ErrUnknownView { return err } if _, err := g.SetCurrentView("textarea"); err != nil { log.Panicln(err) } - textarea.Title = "Send message" + textarea.Title = "(" + Profile.Username + ") " + "Send message" + " (Ctrl+A: Autoscroll)" textarea.Wrap = true textarea.Editable = true } diff --git a/cmd/server.go b/cmd/server.go index 71f43cf..2b13d0a 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -37,6 +37,7 @@ func init() { // and all subcommands, e.g.: // serverCmd.PersistentFlags().String("foo", "", "A help for foo") serverCmd.PersistentFlags().String("port", "1302", "port to use for listening") + serverCmd.PersistentFlags().String("history", "", "File to store and recover chat history from") // Cobra supports local flags which will only run when this command // is called directly, e.g.: @@ -51,5 +52,13 @@ func setServerParameters(cmd *cobra.Command) error { if parameterPort != "" { listenPort = parameterPort } + parameterHistory, err := cmd.Flags().GetString("history") + if err != nil { + return err + } + if parameterHistory != "" { + logLocation = parameterHistory + isLogging = true + } return nil } diff --git a/cmd/serverFunc.go b/cmd/serverFunc.go index 05049cd..97d9a10 100644 --- a/cmd/serverFunc.go +++ b/cmd/serverFunc.go @@ -9,17 +9,17 @@ import ( "fmt" "log" "net" + "os" "strings" ) var ( - listenPort string = "1302" + listenPort string = "1302" + isLogging bool = false + logLocation string + listenerList []chan string ) -type Creator interface { - CreateUser() -} - type User struct { Username string IP string @@ -49,8 +49,6 @@ func Server() { } } -var listenerList []chan string - func getUsername(conn net.Conn) (s string, err error) { conn.Write([]byte("What's your name?\nChoice: ")) name, err := bufio.NewReader(conn).ReadString('\n') @@ -78,6 +76,22 @@ func removeFromList(chatChan chan string) { } } +func populateChat(conn net.Conn) { + if isLogging == false { + return + } + file, err := os.Open(logLocation) + if err != nil { + log.Printf("Error opening file for populating: %v\n", err) + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + conn.Write([]byte(fmt.Sprintln(scanner.Text()))) + } + +} + func handleConn(conn net.Conn, chatChan chan string) { defer conn.Close() @@ -94,11 +108,13 @@ func handleConn(conn net.Conn, chatChan chan string) { } userIP := getIP(conn) ////////////////////////////////// + populateChat(conn) newUserTemplate := new(User) newUser := newUserTemplate.CreateUser(userName, userIP) joinMessage := fmt.Sprintf("%v has joined the chat!", newUser.Username) fmt.Println(joinMessage) + addToLog(fmt.Sprintln(joinMessage)) //conn.Write([]byte(joinMessage)) sendMessage(joinMessage) @@ -108,6 +124,7 @@ func handleConn(conn net.Conn, chatChan chan string) { if err != nil { quitMessage := fmt.Sprintf("%v has disconnected!", newUser.Username) fmt.Println(quitMessage) + addToLog(fmt.Sprintln(quitMessage)) sendMessage(quitMessage) //removeFromList(chatChan) // if _, err := conn.Write([]byte(quitMessage)); err != nil { @@ -116,7 +133,9 @@ func handleConn(conn net.Conn, chatChan chan string) { return } finalMessage := fmt.Sprintf("[%v] %v: %v", newUser.IP, newUser.Username, strings.TrimRight(message, "\n")) - fmt.Printf("%v\n", finalMessage) + fm := fmt.Sprintf("%v\n", finalMessage) + fmt.Print(fm) + addToLog(fm) sendMessage(finalMessage) //chatChan <- finalMessage @@ -148,3 +167,15 @@ func getIP(conn net.Conn) (IP string) { } return IP } + +func addToLog(s string) { + if isLogging == false { + return + } + file, err := os.OpenFile(logLocation, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0640) + if err != nil { + log.Printf("Error occurred: %v\n", err) + } + defer file.Close() + file.WriteString(s) +} diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..07ca0e3 Binary files /dev/null and b/demo.gif differ