package main import ( "log" "time" "syscall" gosocketio "github.com/ambelovsky/gosf-socketio" "github.com/ambelovsky/gosf-socketio/transport" ) var conn *loungeConnection type loungeAuth struct { User string `json:"user"` Pass string `json:"password"` Token string `json:"token"` } type loungeChannel struct { Name string `json:"name"` Type string `json:"type"` Id int `json:"id"` Topic string `json:"topic"` Messages []loungeMessage `json:"messages"` Filtered []loungeMessage } type loungeNetwork struct { Uuid string `json:"uuid"` Name string `json:"name"` Channels []loungeChannel `json:"channels"` } type loungeInit struct { Token string `json:"token"` Networks []loungeNetwork `json:"networks"` } type loungeJoin struct { Network string `json:"network"` Channel loungeChannel `json:"chan"` Index int `json:"index"` } type loungePart struct { Channel int `json:"chan"` } type loungeFrom struct { Mode string `json:"mode"` Nick string `json:"nick"` } type loungeMessageType string const ( messageMessage loungeMessageType = "message" messageAction loungeMessageType = "action" messageMode loungeMessageType = "mode" messageTopic loungeMessageType = "topic" messageNotice loungeMessageType = "notice" messageError loungeMessageType = "error" ) type loungeMessage struct { From loungeFrom `json:"from"` Highlight bool `json:"highlight"` Id int `json:"id"` Self bool `json:"self"` Text string `json:"text"` Time string `json:"time"` Type loungeMessageType `json:"type"` } type loungeNewMessage struct { Channel int `json:"chan"` Message loungeMessage `json:"msg"` Unread int `json:"unread"` } type loungeMoreRequest struct { Target int `json:"target"` LastId int `json:"lastId"` Condensed bool `json:"condensed"` } type loungeMore struct { Channel int `json:"chan"` Messages []loungeMessage `json:"messages"` TotalMessages int `json:"totalMessages"` } type loungeInput struct { Target int `json:"target"` Text string `json:"text"` } type loungeActionType int const ( actionNone loungeActionType = 0 actionConnected loungeActionType = 1 actionDisconnected loungeActionType = 2 actionAuthSuccess loungeActionType = 3 actionAuthFailed loungeActionType = 4 actionInit loungeActionType = 5 actionJoin loungeActionType = 6 actionPart loungeActionType = 7 actionOpen loungeActionType = 8 actionMore loungeActionType = 9 actionNewMessage loungeActionType = 10 ) type loungeConnection struct { Error string Client *gosocketio.Client Host string Path string User string Pass string Token string Channel *loungeChannel Networks []loungeNetwork Action loungeActionType } func loungeFindChannel(conn *loungeConnection, channel int) *loungeChannel { for i := 0; i < len(conn.Networks); i++ { net := conn.Networks[i] for i := 0; i < len(net.Channels); i++ { if net.Channels[i].Id == channel { return &net.Channels[i] } } } return nil } func loungeAuthenticate(conn *loungeConnection) { var err error if conn.Pass != "" { err = conn.Client.Emit("auth:perform", loungeAuth{User: conn.User, Pass: conn.Pass}) } else { err = conn.Client.Emit("auth:perform", loungeAuth{User: conn.User, Token: conn.Token}) } if err != nil { log.Fatal(err) } } func loungeMessageFilter(message loungeMessage) bool { if message.Type == messageMessage || message.Type == messageAction || message.Type == messageNotice || message.Type == messageError { return true } if message.Type == messageTopic && message.From.Nick != "" { return true } return false } func loungeMessageHandle(channel *loungeChannel, message loungeMessage) { channel.Messages = append(channel.Messages, message) if loungeMessageFilter(message) { channel.Filtered = append(channel.Filtered, message) } if message.Type == messageTopic { channel.Topic = message.Text } } func loungeMessagesFilter(messages []loungeMessage) []loungeMessage { filtered := []loungeMessage{} for _, msg := range messages { if loungeMessageFilter(msg) { filtered = append(filtered, msg) } } return filtered } func loungeMessagesUpdate(conn *loungeConnection) { lastMessage := conn.Channel.Messages[len(conn.Channel.Messages) - 1] err := conn.Client.Emit("more", loungeMoreRequest{conn.Channel.Id, lastMessage.Id, true}) if err != nil { log.Fatal(err) } } func loungeChannelOpen(conn *loungeConnection, channel *loungeChannel) { conn.Channel = channel err := conn.Client.Emit("open", channel.Id) if err != nil { log.Fatal(err) } if len(channel.Filtered) == 0 { channel.Filtered = loungeMessagesFilter(channel.Messages) } if len(channel.Messages) == 1 { loungeMessagesUpdate(conn) } } func loungeChannelSend(conn *loungeConnection, channel int, text string) { err := conn.Client.Emit("input", loungeInput{channel, text}) if err != nil { log.Fatal(err) } } func loungeRegisterEvents(conn *loungeConnection) { var err error; err = conn.Client.On("auth:start", func(h *gosocketio.Channel, serverHash int) { loungeAuthenticate(conn) }) if err != nil { log.Fatal(err) } err = conn.Client.On("auth:success", func(h *gosocketio.Channel) { conn.Error = "Authentication succeeded" connActionTrigger(conn, actionAuthSuccess) }) if err != nil { log.Fatal(err) } err = conn.Client.On("auth:failed", func(h *gosocketio.Channel) { conn.Error = "Authentication failed" connActionTrigger(conn, actionAuthFailed) }) if err != nil { log.Fatal(err) } err = conn.Client.On("init", func(h *gosocketio.Channel, init loungeInit) { conn.Token = init.Token conn.Networks = init.Networks connActionTrigger(conn, actionInit) }) if err != nil { log.Fatal(err) } err = conn.Client.On("join", func(h *gosocketio.Channel, join loungeJoin) { for i := 0; i < len(conn.Networks); i++ { if conn.Networks[i].Uuid == join.Network { conn.Networks[i].Channels = append(conn.Networks[i].Channels, join.Channel) } } connActionTrigger(conn, actionJoin) }) if err != nil { log.Fatal(err) } err = conn.Client.On("part", func(h *gosocketio.Channel, part loungePart) { if conn.Channel != nil && conn.Channel.Id == part.Channel { conn.Channel = nil } for i := 0; i < len(conn.Networks); i++ { net := conn.Networks[i] for i := 0; i < len(net.Channels); i++ { if net.Channels[i].Id == part.Channel { channels := net.Channels channels[i] = channels[len(channels)-1] net.Channels = channels[:len(channels)-1] } } conn.Networks[i] = net } connActionTrigger(conn, actionPart) }) if err != nil { log.Fatal(err) } err = conn.Client.On("open", func(h *gosocketio.Channel) { connActionTrigger(conn, actionOpen) }) if err != nil { log.Fatal(err) } err = conn.Client.On("more", func(h *gosocketio.Channel, more loungeMore) { channel := loungeFindChannel(conn, more.Channel) currTime, moreTime := time.Now(), time.Now() if channel != nil && len(channel.Messages) != 0 && len(more.Messages) != 0 { currTime, _ = time.Parse(time.RFC3339, channel.Messages[0].Time) moreTime, _ = time.Parse(time.RFC3339, more.Messages[0].Time) } if moreTime.Unix() > currTime.Unix() { channel.Messages = append(channel.Messages, more.Messages...) channel.Filtered = append(channel.Filtered, loungeMessagesFilter(more.Messages)...) } else { channel.Messages = append(more.Messages, channel.Messages...) channel.Filtered = append(loungeMessagesFilter(more.Messages), channel.Filtered...) } connActionTrigger(conn, actionMore) }) if err != nil { log.Fatal(err) } err = conn.Client.On("msg", func(h *gosocketio.Channel, msg loungeNewMessage) { channel := loungeFindChannel(conn, msg.Channel) if (channel != nil) { loungeMessageHandle(channel, msg.Message) } if conn.Channel != nil && msg.Channel == conn.Channel.Id { connActionTrigger(conn, actionNewMessage) } }) if err != nil { log.Fatal(err) } err = conn.Client.On(gosocketio.OnConnection, func(h *gosocketio.Channel) { conn.Error = "Connected" connActionTrigger(conn, actionConnected) }) if err != nil { log.Fatal(err) } err = conn.Client.On(gosocketio.OnDisconnection, func(h *gosocketio.Channel) { conn.Error = "Disconnected" connActionTrigger(conn, actionDisconnected) }) if err != nil { log.Fatal(err) } } func loungeConnect(conn *loungeConnection) { var err error c, err := gosocketio.Dial( "wss://"+conn.Host+conn.Path+"/socket.io/?EIO=3&transport=websocket", transport.GetDefaultWebsocketTransport()) if err != nil { conn.Error = err.Error() return } conn.Client = c loungeRegisterEvents(conn) } func loungeDisconnect(c *gosocketio.Client) { c.Close() } func connActionTrigger(conn *loungeConnection, action loungeActionType) { // TODO: Subscribe to network events in a sane way. conn.Action = action syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) } func connActionHandle(m mainModel) mainModel { switch m.conn.Action { case actionAuthFailed: m.view = viewLogin configWrite(m.conf) case actionInit: if m.conn.Token != "" { m.conf.Token = m.conn.Token configWrite(m.conf) } m.view = viewChannels case actionPart: if m.view == viewChat && m.conn.Channel == nil { m.view = viewChannels } } m.conn.Action = actionNone return m }