Skip to content

Commit

Permalink
Add a simple example client
Browse files Browse the repository at this point in the history
  • Loading branch information
The FreeChessClub Author(s) committed Jun 2, 2019
1 parent 5e3336f commit 4e5d38d
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 54 deletions.
53 changes: 44 additions & 9 deletions pkg/client.go → client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:generate protoc --go_out=. types.proto

package icsgo

import (
Expand All @@ -24,6 +26,7 @@ type Config struct {
TimesealHello string
ConnTimeout int
ConnRetries int
Debug bool
}

// DefaultConfig represents the default configuration of icsgo client
Expand All @@ -36,6 +39,7 @@ var DefaultConfig = &Config{
TimesealHello: "TIMESEAL2|freeseal|icsgo|",
ConnTimeout: 2,
ConnRetries: 5,
Debug: false,
}

// Client represents a new ICS client
Expand All @@ -45,11 +49,45 @@ type Client struct {
username string
}

func getConfig(cfg *Config) *Config {
if cfg == nil {
cfg = DefaultConfig
}

// merge partial config with default config parameters
if cfg.UserPrompt == "" {
cfg.UserPrompt = DefaultConfig.UserPrompt
}

if cfg.PasswordPrompt == "" {
cfg.PasswordPrompt = DefaultConfig.PasswordPrompt
}

if cfg.ICSPrompt == "" {
cfg.ICSPrompt = DefaultConfig.ICSPrompt
}

if cfg.TimesealHello == "" {
cfg.TimesealHello = DefaultConfig.TimesealHello
}

if cfg.ConnTimeout == 0 {
cfg.ConnTimeout = DefaultConfig.ConnTimeout
}

if cfg.ConnRetries == 0 {
cfg.ConnRetries = DefaultConfig.ConnRetries
}

return cfg
}

// NewClient creates a new ICS client
func NewClient(cfg *Config, addr, username, password string) (*Client, error) {
cfg = getConfig(cfg)
retries := cfg.ConnRetries
timeout := time.Duration(cfg.ConnTimeout) * time.Second
conn, err := Dial(addr, retries, timeout, !cfg.DisableTimeseal)
conn, err := Dial(addr, retries, timeout, !cfg.DisableTimeseal, cfg.Debug)
if err != nil {
return nil, errors.Wrap(err, "failed to create new connection")
}
Expand Down Expand Up @@ -80,16 +118,13 @@ func (client *Client) Send(msg string) error {
}

// Recv receives messages from the ICS server
func (client *Client) Recv(conn *Conn) ([]interface{}, error) {
out, err := conn.ReadUntil(client.config.ICSPrompt)
func (client *Client) Recv() ([]interface{}, error) {
out, err := client.conn.ReadUntil(client.config.ICSPrompt)
if err != nil {
return nil, err
}
if len(out) > 0 {
return decodeMessages(out)
}

return nil, nil
return decodeMessages(out), nil
}

// Destroy destroys a client instance
Expand Down Expand Up @@ -124,7 +159,7 @@ func login(conn *Conn, username, password string, cfg *Config) (string, error) {
if username != "guest" && len(password) > 0 {
prompt = cfg.PasswordPrompt
} else {
prompt = "Press return to enter the server as"
prompt = ":"
password = ""
}

Expand All @@ -141,7 +176,7 @@ func login(conn *Conn, username, password string, cfg *Config) (string, error) {
return "", fmt.Errorf("failed authentication for %s: %v", username, err)
}

re := regexp.MustCompile("\\*\\*\\*\\* Starting FICS session as ([a-zA-Z]+)(?:\\(U\\))? \\*\\*\\*\\*")
re := regexp.MustCompile("\\*\\*\\*\\* Starting FICS session as ([a-zA-Z]+)(?:\\(U\\))?")
user := re.FindSubmatch(out)
if user != nil && len(user) > 1 {
username = string(user[1][:])
Expand Down
19 changes: 16 additions & 3 deletions pkg/conn.go → conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ import (

// Conn represents a connection to the ICS server
type Conn struct {
// whether timeseal is enabled on the connection
timeseal bool
conn *telnet.Conn
// whether debug/verbose logging is enabled
debug bool
// the underlying telnet connection
conn *telnet.Conn
}

// Dial creates a new connection
func Dial(addr string, retries int, timeout time.Duration, timeseal bool) (*Conn, error) {
func Dial(addr string, retries int, timeout time.Duration, timeseal, debug bool) (*Conn, error) {
connected := false

var conn *telnet.Conn
Expand All @@ -45,6 +49,7 @@ func Dial(addr string, retries int, timeout time.Duration, timeseal bool) (*Conn

return &Conn{
timeseal: timeseal,
debug: debug,
conn: conn,
}, nil
}
Expand All @@ -59,6 +64,10 @@ func (c *Conn) ReadUntilTimeout(prompt string, timeout time.Duration) ([]byte, e
return nil, err
}

if c.debug {
log.Printf("< %s", string(bs))
}

if c.timeseal {
for {
i := bytes.Index(bs, []byte{'[', 'G', ']', 0x00})
Expand Down Expand Up @@ -89,7 +98,11 @@ func (c *Conn) ReadUntil(prompt string) ([]byte, error) {
func (c *Conn) Write(msg string) error {
c.conn.SetWriteDeadline(time.Now().Add(20 * time.Second))

bs := []byte(msg)
if c.debug {
log.Printf("> %s", msg)
}

bs := []byte(msg + "\n")
if c.timeseal {
bs = encode(bs, len(msg))
}
Expand Down
Binary file added examples/fics_client/fics_client
Binary file not shown.
69 changes: 69 additions & 0 deletions examples/fics_client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright © 2019 Free Chess Club <hi@freechess.club>
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"bufio"
"fmt"
"io"
"log"
"os"

"github.com/freechessclub/icsgo"
)

func main() {
// connect as guest to the FICS server
client, err := icsgo.NewClient(&icsgo.Config{
DisableKeepAlive: true,
DisableTimeseal: true,
Debug: true,
}, "freechess.org:5000", "guest", "")
if err != nil {
log.Fatalf("error creating new FICS client: %v", err)
}

done := make(chan struct{})
go func() {
defer client.Destroy()
for {
select {
default:
msgs, err := client.Recv()
if err == io.EOF {
return
}
if err != nil {
log.Fatalf("error receiving server output: %v", err)
return
}

if msgs != nil {
fmt.Printf("%v\n", msgs)
}
case <-done:
return
}
}
}()

reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("fics_client% ")
cmd, err := reader.ReadString('\n')
if err != nil {
client.Destroy()
log.Fatalf("error reading console input: %v", err)
}

err = client.Send(cmd)
if err != nil || cmd == "exit\n" {
close(done)
break
}
}

}
24 changes: 12 additions & 12 deletions pkg/msg.go → msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package icsgo

import (
"bytes"
"fmt"
"regexp"
"strconv"
)
Expand Down Expand Up @@ -112,13 +111,17 @@ func getGameResult(p1, p2, who, action string) (string, string, GameEnd_Reason)
return p1, p2, -1
}

func decodeMessages(msg []byte) ([]interface{}, error) {
func decodeMessages(msg []byte) []interface{} {
if len(msg) == 0 {
return nil
}

var msgs []interface{}
var out interface{}

msg = toldMsgRE.ReplaceAll(msg, []byte{})
if msg == nil || bytes.Equal(msg, []byte("\n")) {
return nil, nil
return nil
}

matches := gameMoveRE.FindSubmatch(msg)
Expand All @@ -127,13 +130,10 @@ func decodeMessages(msg []byte) ([]interface{}, error) {
if len(m) > 1 {
for i := 0; i < len(m); i++ {
if len(m[i]) > 0 {
out, err := decodeMessages(m[i])
if err == nil {
msgs = append(msgs, out)
}
msgs = append(msgs, decodeMessages(m[i]))
}
}
return msgs, nil
return msgs
}

fen := ""
Expand Down Expand Up @@ -201,10 +201,10 @@ func decodeMessages(msg []byte) ([]interface{}, error) {
}
}

if out != nil {
msgs = append(msgs, out)
return msgs, nil
out = &Message{
Message: string(msg),
}

return nil, fmt.Errorf("unknown message: %v", msg)
msgs = append(msgs, out)
return msgs
}
File renamed without changes.
Loading

0 comments on commit 4e5d38d

Please sign in to comment.