gotel

simple terminal chat program
git clone git://git.mdnr.space/gotel
Log | Files | Refs | README | LICENSE

client.go (5767B)


      1 package main
      2 
      3 import (
      4 	"fmt"
      5 	"github.com/jroimartin/gocui"
      6 	"log"
      7 	"net"
      8 	"strings"
      9 )
     10 
     11 type Command struct {
     12 	Name        string
     13 	Description string
     14 	Signature   string
     15 	Function    func(*gocui.View, []string) error
     16 }
     17 
     18 const commandCnt = 5
     19 
     20 var commands [commandCnt]Command
     21 var gui *gocui.Gui
     22 var serverConn net.Conn
     23 
     24 const serverAddr = "127.0.0.1:6969"
     25 const initMsg = `This is a client for connecting to GoTel chat server.
     26 =======================================================`
     27 
     28 func main() {
     29 	initCommands()
     30 	gui, _ = gocui.NewGui(gocui.OutputNormal)
     31 	// if err != nil {
     32 	// 	log.Panicln(err)
     33 	// }
     34 	defer gui.Close()
     35 
     36 	gui.Cursor = true
     37 	gui.Mouse = true
     38 	gui.SetManagerFunc(layout)
     39 
     40 	if err := gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
     41 		log.Panicln(err)
     42 	}
     43 
     44 	if err := gui.SetKeybinding("prompt", gocui.KeyEnter, gocui.ModNone, getInput); err != nil {
     45 		log.Panicln(err)
     46 	}
     47 
     48 	if err := gui.MainLoop(); err != nil && err != gocui.ErrQuit {
     49 		log.Panicln(err)
     50 	}
     51 }
     52 
     53 func layout(g *gocui.Gui) error {
     54 	maxX, maxY := g.Size()
     55 	chatLogW := maxX - 2
     56 	chatLogH := maxY - 10
     57 	promptX1 := 1
     58 	promptY1 := chatLogH + 1
     59 	promptX2 := maxX - 2
     60 	promptY2 := maxY - 2
     61 	if chatLog, err := g.SetView("chatLog", 1, 1, chatLogW, chatLogH); err != nil {
     62 		if err != gocui.ErrUnknownView {
     63 			return err
     64 		}
     65 		chatLog.Wrap = true
     66 		chatLog.Autoscroll = true
     67 		fmt.Fprintln(chatLog, initMsg)
     68 	}
     69 	if prompt, promptErr := g.SetView("prompt", promptX1, promptY1, promptX2, promptY2); promptErr != nil {
     70 		if promptErr != gocui.ErrUnknownView {
     71 			return promptErr
     72 		}
     73 		prompt.Editable = true
     74 		prompt.Wrap = true
     75 		if _, err := g.SetCurrentView("prompt"); err != nil {
     76 			return err
     77 		}
     78 	}
     79 	return nil
     80 }
     81 
     82 func getInput(g *gocui.Gui, v *gocui.View) error {
     83 	input := strings.TrimRight(v.Buffer(), "\r\n")
     84 	items := strings.Split(input, " ")
     85 	v.Clear()
     86 	v.SetCursor(0, 0)
     87 	chatLog, _ := g.View("chatLog")
     88 	if strings.HasPrefix(items[0], "/") {
     89 		for _, cmd := range commands {
     90 			if strings.HasPrefix(cmd.Signature, items[0]) {
     91 				cmd.Function(chatLog, items)
     92 				return nil
     93 			}
     94 		}
     95 		fmt.Fprintf(chatLog, "Invalid command: %s\n", items[0])
     96 		return nil
     97 	}
     98 	if serverConn != nil {
     99 		serverConn.Write([]byte(input))
    100 	}
    101 	return nil
    102 }
    103 
    104 func initCommands() {
    105 	commands = [commandCnt]Command{
    106 
    107 		Command{
    108 			Name:        "help",
    109 			Description: "Print help menu",
    110 			Signature:   "/help [command]",
    111 			Function:    printHelp,
    112 		},
    113 
    114 		Command{
    115 			Name:        "join",
    116 			Description: "Join the chat",
    117 			Signature:   "/join <ipv4>:<port> <TOKEN>",
    118 			Function:    sendJoin,
    119 		},
    120 
    121 		Command{
    122 			Name:        "signup",
    123 			Description: "Sign up to server",
    124 			Signature:   "/signup <username> <password>",
    125 			Function:    sendSignup,
    126 		},
    127 
    128 		Command{
    129 			Name:        "login",
    130 			Description: "Login to server",
    131 			Signature:   "/login <username> <password>",
    132 			Function:    sendLogin,
    133 		},
    134 
    135 		Command{
    136 			Name:        "exit",
    137 			Description: "Logout from server",
    138 			Signature:   "/exit",
    139 			Function:    sendQuit,
    140 		},
    141 	}
    142 }
    143 
    144 func printHelp(v *gocui.View, input []string) error {
    145 	if len(input) == 1 {
    146 		fmt.Fprintf(v, "Commands:\n")
    147 		for _, cmd := range commands {
    148 			fmt.Fprintf(v, "%s - %s\n", cmd.Signature, cmd.Description)
    149 		}
    150 		return nil
    151 	}
    152 	if len(input) == 2 {
    153 		for _, cmd := range commands {
    154 			if strings.HasPrefix(cmd.Signature, input[1]) {
    155 				fmt.Fprintf(v, "%s - %s\n", cmd.Signature, cmd.Description)
    156 				return nil
    157 			}
    158 		}
    159 		fmt.Fprintf(v, "%s is not a valid command. Type /help to see the full list of commands.\n", input[1])
    160 		return nil
    161 	}
    162 	fmt.Fprintln(v, "Too many arguments for /help command.")
    163 	fmt.Fprintf(v, "%s - %s\n", commands[0].Signature, commands[0].Description)
    164 	return nil
    165 }
    166 
    167 func sendJoin(v *gocui.View, input []string) error {
    168 	var err error
    169 	if len(input) != 3 {
    170 		fmt.Fprintf(v, "Invalid join command.\n")
    171 		input := []string{"/help", "/join"}
    172 		printHelp(v, input)
    173 		return nil
    174 	}
    175 
    176 	if len(input[2]) != 32 {
    177 		fmt.Fprintf(v, "Invalid token: %s\nThe token should be a 32-character string.\n", input[1])
    178 		return nil
    179 	}
    180 
    181 	serverConn, err = net.Dial("tcp", input[1])
    182 	if err != nil {
    183 		fmt.Fprintf(v, "Server is not responding.\n")
    184 		return nil
    185 	}
    186 	_, err = serverConn.Write([]byte(input[0] + " " + input[2]))
    187 	if err != nil {
    188 		fmt.Fprintf(v, "Could not send join request to the server: %s\n", err)
    189 		return nil
    190 	}
    191 	go getMsg(serverConn)
    192 	return nil
    193 }
    194 
    195 func sendSignup(v *gocui.View, input []string) error {
    196 	if len(input) != 3 {
    197 		fmt.Fprintf(v, "Invalid signup command.\n")
    198 		input := []string{"/help", "/signup"}
    199 		printHelp(v, input)
    200 		return nil
    201 	}
    202 
    203 	_, err := serverConn.Write([]byte(input[0] + " " + input[1] + " " + input[2]))
    204 	if err != nil {
    205 		fmt.Fprintf(v, "Could not send signup request to the server: %s\nTry join command first.", err)
    206 	}
    207 	return nil
    208 }
    209 
    210 func sendLogin(v *gocui.View, input []string) error {
    211 	if len(input) != 3 {
    212 		fmt.Fprintf(v, "Invalid login command.\n")
    213 		input := []string{"/help", "/login"}
    214 		printHelp(v, input)
    215 		return nil
    216 	}
    217 
    218 	_, err := serverConn.Write([]byte(input[0] + " " + input[1] + " " + input[2]))
    219 	if err != nil {
    220 		fmt.Fprintf(v, "Could not send login request to the server: %s\nTry /join command first.", err)
    221 	}
    222 	return nil
    223 }
    224 
    225 func sendQuit(v *gocui.View, input []string) error {
    226 	// fmt.Fprintln(v, "Quiting from server")
    227 	serverConn.Close()
    228 	return nil
    229 }
    230 
    231 func getMsg(conn net.Conn) {
    232 	readBuf := make([]byte, 512)
    233 	for {
    234 		n, readErr := conn.Read(readBuf)
    235 
    236 		if readErr != nil {
    237 			return
    238 		}
    239 		gui.Update(func(g *gocui.Gui) error {
    240 			v, err := gui.View("chatLog")
    241 			if err != nil {
    242 				return err
    243 			}
    244 			fmt.Fprintln(v, string(readBuf[:n]))
    245 			return nil
    246 		})
    247 	}
    248 }
    249 
    250 func quit(g *gocui.Gui, v *gocui.View) error {
    251 	sendQuit(nil, nil )
    252 	return gocui.ErrQuit
    253 }