Skip to content

Commit

Permalink
Add general chat API
Browse files Browse the repository at this point in the history
The chat API enables users to chat with each other in a globally
available chat. It supports basic text messages to be sent between
each user.
  • Loading branch information
seternate committed Aug 9, 2024
1 parent 712d640 commit 0ab2b66
Show file tree
Hide file tree
Showing 14 changed files with 398 additions and 16 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
!cmd/
!pkg/
!pkg/api/
!pkg/chat/
!pkg/filesystem/
!pkg/game/
!pkg/game/argument/
Expand Down
7 changes: 5 additions & 2 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,19 @@ func main() {
handler := handler.NewHandler(&settings).
WithGamehandler().
WithUserhandler(errCtx, errgrp).
WithDownloadHandler()
WithDownloadHandler().
WithChatHandler()
log.Trace().Msg("handler created")
gameRoutes := router.GameRoutes(handler)
userRoutes := router.UserRoutes(handler)
downloadRoutes := router.DownloadRoutes(handler)
chatRoutes := router.ChatRoutes(handler)
log.Trace().Msg("routes created")
router := router.NewRouter().
WithRoutes(gameRoutes).
WithRoutes(userRoutes).
WithRoutes(downloadRoutes)
WithRoutes(downloadRoutes).
WithRoutes(chatRoutes)
log.Trace().Msg("router created")

listener, err := net.Listen("tcp4", ":"+strconv.Itoa(settings.ServerPort))
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.21

require (
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
github.com/rs/zerolog v1.31.0
golang.org/x/sync v0.6.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand Down
98 changes: 98 additions & 0 deletions pkg/api/chatservice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package api

import (
"fmt"
"net/http"
"strings"
"time"

"github.com/gorilla/websocket"
"github.com/seternate/go-lanty/pkg/chat"
)

type ChatService struct {
client *Client
ws *websocket.Conn

Messages chan chat.Message
Error error
}

// If client.baseURL is changed while being connected a Reconnect for this service has to be made
func (service *ChatService) Connect() (resp *http.Response, err error) {
defer func() { service.Error = err }()
path, err := service.client.router.Get("Chat").URLPath()
if err != nil {
return
}
path.Scheme = "ws"
url := service.client.buildURL(*path)
service.ws, resp, err = websocket.DefaultDialer.Dial((&url).String(), nil)
if err != nil {
err = fmt.Errorf("error connecting to server chat websocket: %w", err)
return
}
service.Error = nil
go service.run()
return
}

func (service *ChatService) Disconnect() (err error) {
if service.ws == nil {
return
}
err = service.ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
service.Error = err
}
return
}

func (service *ChatService) Reconnect() (err error) {
defer func() { service.Error = err }()
err = service.Disconnect()
_, err = service.Connect()
return
}

func (service *ChatService) SendMessage(message chat.Message) (err error) {
if service.ws == nil {
return fmt.Errorf("can not send chat message: %w", service.Error)
}
message.SetTime(time.Now())
err = service.ws.WriteJSON(message)
if err != nil {
service.Error = err
}
return
}

func (service *ChatService) run() {
for service.Error == nil && service.ws != nil {
messageType, reader, err := service.ws.NextReader()
if err != nil {
service.Error = fmt.Errorf("%w: permanent error reading from client websocket", err)
service.Disconnect()
return
}
if messageType == websocket.BinaryMessage {
continue
}

message, err := chat.ReadMessage(reader)
if err != nil {
continue
}
if len(strings.TrimSpace(message.GetMessage())) == 0 {
continue
}
if len(strings.TrimSpace(message.GetUser().Name)) == 0 || len(strings.TrimSpace(message.GetUser().IP)) == 0 {
continue
}
if message.GetTime().IsZero() {
continue
}

service.Messages <- message
}
}
15 changes: 12 additions & 3 deletions pkg/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"
"time"

"github.com/seternate/go-lanty/pkg/chat"
"github.com/seternate/go-lanty/pkg/router"
)

Expand All @@ -16,6 +17,7 @@ type Client struct {

Game *GameService
User *UserService
Chat *ChatService
}

func NewClient(baseURL string, timeout time.Duration) (client *Client, err error) {
Expand All @@ -25,7 +27,8 @@ func NewClient(baseURL string, timeout time.Duration) (client *Client, err error

router := router.NewRouter().
WithRoutes(router.GameRoutes(nil)).
WithRoutes(router.UserRoutes(nil))
WithRoutes(router.UserRoutes(nil)).
WithRoutes(router.ChatRoutes(nil))

client = &Client{
httpclient: httpclient,
Expand All @@ -34,6 +37,10 @@ func NewClient(baseURL string, timeout time.Duration) (client *Client, err error
err = client.SetBaseURL(baseURL)
client.Game = &GameService{client: client}
client.User = &UserService{client: client}
client.Chat = &ChatService{
client: client,
Messages: make(chan chat.Message, 100),
}

return
}
Expand All @@ -49,8 +56,10 @@ func (c *Client) SetBaseURL(baseURL string) (err error) {
return
}

func (c *Client) BuildURL(url url.URL) url.URL {
url.Scheme = "http"
func (c *Client) buildURL(url url.URL) url.URL {
if len(url.Scheme) == 0 {
url.Scheme = c.baseURL.Scheme
}
url.Host = c.baseURL.Host
return url
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/api/gameservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (service *GameService) GetGames() (games []string, err error) {
if err != nil {
return
}
request, err := service.client.newRESTRequest(http.MethodGet, service.client.BuildURL(*path), nil, nil)
request, err := service.client.newRESTRequest(http.MethodGet, service.client.buildURL(*path), nil, nil)
if err != nil {
return
}
Expand Down Expand Up @@ -54,7 +54,7 @@ func (service *GameService) GetGame(slug string) (game game.Game, err error) {
if err != nil {
return
}
request, err := service.client.newRESTRequest(http.MethodGet, service.client.BuildURL(*path), nil, nil)
request, err := service.client.newRESTRequest(http.MethodGet, service.client.buildURL(*path), nil, nil)
if err != nil {
return
}
Expand All @@ -80,7 +80,7 @@ func (service *GameService) GetIcon(game game.Game) (image image.Image, err erro
return
}

download, err := network.NewDownload(service.client.BuildURL(*path))
download, err := network.NewDownload(service.client.buildURL(*path))
if err != nil {
return
}
Expand All @@ -107,7 +107,7 @@ func (service *GameService) Download(ctx context.Context, game game.Game, direct
return
}

download, err = network.NewDownload(service.client.BuildURL(*path))
download, err = network.NewDownload(service.client.buildURL(*path))
if err != nil {
return
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/api/userservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func (service *UserService) GetUsers() (users []string, err error) {
if err != nil {
return
}
request, err := service.client.newRESTRequest(http.MethodGet, service.client.BuildURL(*path), nil, nil)
request, err := service.client.newRESTRequest(http.MethodGet, service.client.buildURL(*path), nil, nil)
if err != nil {
return
}
Expand All @@ -43,7 +43,7 @@ func (service *UserService) GetUser(ip string) (user user.User, err error) {
if err != nil {
return
}
request, err := service.client.newRESTRequest(http.MethodGet, service.client.BuildURL(*path), nil, nil)
request, err := service.client.newRESTRequest(http.MethodGet, service.client.buildURL(*path), nil, nil)
if err != nil {
return
}
Expand Down Expand Up @@ -73,7 +73,7 @@ func (service *UserService) CreateNewUser(user user.User) (u user.User, err erro
if err != nil {
return
}
request, err := service.client.newRESTRequest(http.MethodPost, service.client.BuildURL(*path), nil, bytes.NewReader(userjson))
request, err := service.client.newRESTRequest(http.MethodPost, service.client.buildURL(*path), nil, bytes.NewReader(userjson))
if err != nil {
return
}
Expand Down
49 changes: 49 additions & 0 deletions pkg/chat/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package chat

import (
"encoding/json"
"fmt"
"io"
"time"

"github.com/seternate/go-lanty/pkg/user"
)

type Message interface {
GetType() Type
GetUser() user.User
GetMessage() string
GetTime() time.Time
SetTime(time.Time)
}

type tmpMessage struct {
Type Type `json:"type"`
User user.User `json:"user"`
Time time.Time `json:"time"`
}

func ReadMessage(r io.Reader) (Message, error) {
tmpMessageJson, err := io.ReadAll(r)
if err != nil {
return nil, err
}

tmpMessage := &tmpMessage{}
err = json.Unmarshal(tmpMessageJson, tmpMessage)
if err == io.EOF {
err = io.ErrUnexpectedEOF
return nil, err
}

switch tmpMessage.Type {
case TYPE_TEXT:
textmessage := &TextMessage{}
err = json.Unmarshal(tmpMessageJson, textmessage)
if err != nil {
return nil, err
}
return textmessage, err
}
return nil, fmt.Errorf("undefined message type: %s", tmpMessage.Type.String())
}
47 changes: 47 additions & 0 deletions pkg/chat/textmessage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package chat

import (
"time"

"github.com/seternate/go-lanty/pkg/user"
)

type TextMessage struct {
Type Type `json:"type"`
User user.User `json:"user"`
Message textMessageText `json:"message"`
Time time.Time `json:"time"`
}

type textMessageText struct {
Text string `json:"text"`
}

func NewTextMessage(user user.User, message string) *TextMessage {
return &TextMessage{
Type: TYPE_TEXT,
User: user,
Message: textMessageText{message},
Time: time.Now(),
}
}

func (message *TextMessage) GetType() Type {
return message.Type
}

func (message *TextMessage) GetUser() user.User {
return message.User
}

func (message *TextMessage) GetMessage() string {
return message.Message.Text
}

func (message *TextMessage) GetTime() time.Time {
return message.Time
}

func (message *TextMessage) SetTime(t time.Time) {
message.Time = t
}
Loading

0 comments on commit 0ab2b66

Please sign in to comment.