From 555184a03357e206f6ba7ded7f3a09daa03e7360 Mon Sep 17 00:00:00 2001 From: erroneousboat Date: Sun, 25 Sep 2016 22:34:02 +0200 Subject: [PATCH] Add slack message retrieval --- TODO.md | 4 -- src/components/channels.go | 56 +++++++++++++++++++++++ src/components/chat.go | 22 +++++---- src/components/components.go | 45 ------------------ src/components/mode.go | 43 ++++++++++++++++++ src/config/config.go | 25 ++++++++++ src/context/context.go | 36 +++++++++++---- src/handlers/event.go | 45 +++++++++++++++++- src/main.go | 29 ++++++++++-- src/service/slack.go | 88 ++++++++++++++++++++++++++++++++++++ src/views/chat.go | 13 +++--- 11 files changed, 326 insertions(+), 80 deletions(-) create mode 100644 src/components/channels.go delete mode 100644 src/components/components.go create mode 100644 src/components/mode.go create mode 100644 src/config/config.go create mode 100644 src/service/slack.go diff --git a/TODO.md b/TODO.md index c075a14..4af28d9 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,3 @@ Examples: - https://github.com/nsf/godit - https://github.com/moncho/dry - https://github.com/mikepea/go-jira-ui - -- [ ] show cursor, see if you can alter Par -- [ ] input overflow -- [ ] chat diff --git a/src/components/channels.go b/src/components/channels.go new file mode 100644 index 0000000..cbbdd6e --- /dev/null +++ b/src/components/channels.go @@ -0,0 +1,56 @@ +package components + +import ( + "github.com/erroneousboat/slack-term/src/service" + "github.com/gizak/termui" +) + +type Channels struct { + List *termui.List +} + +func CreateChannels(svc *service.SlackService, inputHeight int) *Channels { + channels := &Channels{ + List: termui.NewList(), + } + + channels.List.BorderLabel = "Channels" + channels.List.Overflow = "wrap" + channels.List.Height = termui.TermHeight() - inputHeight + + channels.GetChannels(svc) + + return channels +} + +// Buffer implements interface termui.Bufferer +func (c *Channels) Buffer() termui.Buffer { + buf := c.List.Buffer() + return buf +} + +// GetHeight implements interface termui.GridBufferer +func (c *Channels) GetHeight() int { + return c.List.Block.GetHeight() +} + +// SetWidth implements interface termui.GridBufferer +func (c *Channels) SetWidth(w int) { + c.List.SetWidth(w) +} + +// SetX implements interface termui.GridBufferer +func (c *Channels) SetX(x int) { + c.List.SetX(x) +} + +// SetY implements interface termui.GridBufferer +func (c *Channels) SetY(y int) { + c.List.SetY(y) +} + +func (c *Channels) GetChannels(svc *service.SlackService) { + for _, slackChan := range svc.GetChannels() { + c.List.Items = append(c.List.Items, slackChan.Name) + } +} diff --git a/src/components/chat.go b/src/components/chat.go index 422b4b5..e7b3b44 100644 --- a/src/components/chat.go +++ b/src/components/chat.go @@ -3,21 +3,26 @@ package components import ( "strings" + "github.com/erroneousboat/slack-term/src/service" "github.com/gizak/termui" ) type Chat struct { - List *termui.List + List *termui.List + SelectedChannel string } -func CreateChat(inputHeight int) *Chat { +func CreateChat(svc *service.SlackService, inputHeight int) *Chat { chat := &Chat{ List: termui.NewList(), } + // TODO: should be SetSelectedChannel + chat.SelectedChannel = svc.GetChannels()[0].ID + chat.List.Height = termui.TermHeight() - inputHeight chat.List.Overflow = "wrap" - chat.LoadMessages() + chat.GetMessages(svc) return chat } @@ -125,18 +130,15 @@ func (c *Chat) SetY(y int) { c.List.SetY(y) } -func (c *Chat) LoadMessages() { - messages := []string{ - "[jp] hello world", - "[erroneousboat] foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar", - } +func (c *Chat) GetMessages(svc *service.SlackService) { + messages := svc.GetMessages(c.SelectedChannel) for _, message := range messages { - c.AddMessages(message) + c.AddMessage(message) } } -func (c *Chat) AddMessages(message string) { +func (c *Chat) AddMessage(message string) { c.List.Items = append(c.List.Items, message) } diff --git a/src/components/components.go b/src/components/components.go deleted file mode 100644 index 0768faa..0000000 --- a/src/components/components.go +++ /dev/null @@ -1,45 +0,0 @@ -package components - -import "github.com/gizak/termui" - -func CreateInputComponent() *termui.Par { - compInput := termui.NewPar("") - compInput.Height = 3 - return compInput -} - -func CreateChannelsComponent(inputHeight int) *termui.List { - channels := []string{ - "general", - "random", - } - - compChannels := termui.NewList() - compChannels.Items = channels - compChannels.BorderLabel = "Channels" - compChannels.Height = termui.TermHeight() - inputHeight - compChannels.Overflow = "wrap" - - return compChannels -} - -func CreateChatComponent(inputHeight int) *termui.List { - messages := []string{ - "[jp] hello world", - "[erroneousboat] foo bar", - } - - compChat := termui.NewList() - compChat.Items = messages - compChat.BorderLabel = "Channel01" - compChat.Height = termui.TermHeight() - inputHeight - compChat.Overflow = "wrap" - - return compChat -} - -func CreateModeComponent() *termui.Par { - compMode := termui.NewPar("NORMAL") - compMode.Height = 3 - return compMode -} diff --git a/src/components/mode.go b/src/components/mode.go new file mode 100644 index 0000000..7c2cd2c --- /dev/null +++ b/src/components/mode.go @@ -0,0 +1,43 @@ +package components + +import "github.com/gizak/termui" + +type Mode struct { + Par *termui.Par +} + +func CreateMode() *Mode { + mode := &Mode{ + Par: termui.NewPar("NORMAL"), + } + + mode.Par.Height = 3 + + return mode +} + +// Buffer implements interface termui.Bufferer +func (m *Mode) Buffer() termui.Buffer { + buf := m.Par.Buffer() + return buf +} + +// GetHeight implements interface termui.GridBufferer +func (m *Mode) GetHeight() int { + return m.Par.Block.GetHeight() +} + +// SetWidth implements interface termui.GridBufferer +func (m *Mode) SetWidth(w int) { + m.Par.SetWidth(w) +} + +// SetX implements interface termui.GridBufferer +func (m *Mode) SetX(x int) { + m.Par.SetX(x) +} + +// SetY implements interface termui.GridBufferer +func (m *Mode) SetY(y int) { + m.Par.SetY(y) +} diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 0000000..39d7d26 --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,25 @@ +package config + +import ( + "encoding/json" + "os" +) + +type Config struct { + SlackToken string `json:"slack_token"` +} + +func NewConfig(filepath string) (*Config, error) { + var cfg Config + + file, err := os.Open(filepath) + if err != nil { + return &cfg, err + } + + if err := json.NewDecoder(file).Decode(&cfg); err != nil { + return &cfg, err + } + + return &cfg, nil +} diff --git a/src/context/context.go b/src/context/context.go index 8aeac58..820d3a5 100644 --- a/src/context/context.go +++ b/src/context/context.go @@ -1,8 +1,13 @@ package context import ( - "github.com/erroneousboat/slack-term/src/views" + "log" + "github.com/gizak/termui" + + "github.com/erroneousboat/slack-term/src/config" + "github.com/erroneousboat/slack-term/src/service" + "github.com/erroneousboat/slack-term/src/views" ) const ( @@ -11,17 +16,30 @@ const ( ) type AppContext struct { - Body *termui.Grid - View *views.View - Mode string + Service *service.SlackService + Body *termui.Grid + View *views.View + Config *config.Config + Mode string } -// TODO: arg Config -func CreateAppContext() *AppContext { - view := views.CreateChatView() +func CreateAppContext(flgConfig string) *AppContext { + // Load config + config, err := config.NewConfig(flgConfig) + if err != nil { + log.Fatalf("ERROR: not able to load config file: %s", flgConfig) + } + + // Create Service + svc := service.NewSlackService(config.SlackToken) + + // Create ChatView + view := views.CreateChatView(svc) return &AppContext{ - View: view, - Mode: CommandMode, + Service: svc, + View: view, + Config: config, + Mode: CommandMode, } } diff --git a/src/handlers/event.go b/src/handlers/event.go index 36d9a57..15c2af5 100644 --- a/src/handlers/event.go +++ b/src/handlers/event.go @@ -1,7 +1,10 @@ package handlers import ( + "fmt" + "github.com/gizak/termui" + "github.com/nlopes/slack" "github.com/erroneousboat/slack-term/src/context" "github.com/erroneousboat/slack-term/src/views" @@ -10,6 +13,28 @@ import ( func RegisterEventHandlers(ctx *context.AppContext) { termui.Handle("/sys/kbd/", anyKeyHandler(ctx)) termui.Handle("/sys/wnd/resize", resizeHandler(ctx)) + termui.Handle("/timer/1s", timeHandler(ctx)) + + // TODO: check channel of message should be added to correct channel + go func() { + for { + select { + case msg := <-ctx.Service.RTM.IncomingEvents: + switch ev := msg.Data.(type) { + case *slack.MessageEvent: + var name string + user, err := ctx.Service.Client.GetUserInfo(ev.User) + if err == nil { + name = user.Name + } else { + name = "unknown" + } + msg := fmt.Sprintf("[%s] %s", name, ev.Text) + ctx.View.Chat.AddMessage(msg) + } + } + } + }() } func anyKeyHandler(ctx *context.AppContext) func(termui.Event) { @@ -60,6 +85,11 @@ func resizeHandler(ctx *context.AppContext) func(termui.Event) { } } +func timeHandler(ctx *context.AppContext) func(termui.Event) { + return func(e termui.Event) { + } +} + // FIXME: resize only seems to work for width and resizing it too small // will cause termui to panic func actionResize(ctx *context.AppContext) { @@ -103,12 +133,23 @@ func actionQuit() { func actionInsertMode(ctx *context.AppContext) { ctx.Mode = context.InsertMode - ctx.View.Mode.Text = "INSERT" + ctx.View.Mode.Par.Text = "INSERT" termui.Render(ctx.View.Mode) } func actionCommandMode(ctx *context.AppContext) { ctx.Mode = context.CommandMode - ctx.View.Mode.Text = "NORMAL" + ctx.View.Mode.Par.Text = "NORMAL" termui.Render(ctx.View.Mode) } + +// TODO: get message for channel +func actionGetMessages(ctx *context.AppContext) { + ctx.View.Chat.GetMessages(ctx.Service) + termui.Render(ctx.View.Chat) +} + +func actionGetChannels(ctx *context.AppContext) { + ctx.View.Channels.GetChannels(ctx.Service) + termui.Render(ctx.View.Channels) +} diff --git a/src/main.go b/src/main.go index 40891c8..cf29922 100644 --- a/src/main.go +++ b/src/main.go @@ -1,6 +1,11 @@ package main import ( + "flag" + "log" + "os/user" + "path" + "github.com/erroneousboat/slack-term/src/context" "github.com/erroneousboat/slack-term/src/handlers" @@ -8,16 +13,31 @@ import ( ) func main() { + // Start terminal user interface err := termui.Init() if err != nil { panic(err) } defer termui.Close() - // create context - ctx := context.CreateAppContext() + // Get home dir for config file default + usr, err := user.Current() + if err != nil { + log.Fatal(err) + } - // setup view + // Parse flags + flgConfig := flag.String( + "config", + path.Join(usr.HomeDir, "slack-term.json"), + "location of config file", + ) + flag.Parse() + + // Create context + ctx := context.CreateAppContext(*flgConfig) + + // Setup body termui.Body.AddRows( termui.NewRow( termui.NewCol(1, 0, ctx.View.Channels), @@ -31,9 +51,10 @@ func main() { termui.Body.Align() termui.Render(termui.Body) + // Set body in context ctx.Body = termui.Body - // register handlers + // Register handlers handlers.RegisterEventHandlers(ctx) termui.Loop() diff --git a/src/service/slack.go b/src/service/slack.go new file mode 100644 index 0000000..331dfba --- /dev/null +++ b/src/service/slack.go @@ -0,0 +1,88 @@ +package service + +import ( + "fmt" + "log" + + "github.com/nlopes/slack" +) + +type SlackService struct { + Client *slack.Client + RTM *slack.RTM + Channels []slack.Channel +} + +type Channel struct { + ID string + Name string +} + +func NewSlackService(token string) *SlackService { + svc := new(SlackService) + + svc.Client = slack.New(token) + svc.RTM = svc.Client.NewRTM() + + go svc.RTM.ManageConnection() + + return svc +} + +func (s *SlackService) Connect() { + +} + +func (s *SlackService) GetChannels() []Channel { + var chans []Channel + + slackChans, err := s.Client.GetChannels(true) + if err != nil { + chans = append(chans, Channel{}) + } + + s.Channels = slackChans + + for _, slackChan := range slackChans { + chans = append(chans, Channel{slackChan.ID, slackChan.Name}) + } + + return chans +} + +func (s *SlackService) SendMessage(message string) {} + +func (s *SlackService) GetMessages(channel string) []string { + // https://api.slack.com/methods/channels.history + historyParams := slack.HistoryParameters{ + // Latest: "now", + // Oldest: 0, + Count: 50, + Inclusive: false, + Unreads: false, + } + + // https://godoc.org/github.com/nlopes/slack#History + history, err := s.Client.GetChannelHistory(channel, historyParams) + if err != nil { + log.Fatal(err) + return []string{""} + } + + // TODO: this takes a long time, maybe use some dynamic programming + var messages []string + for _, message := range history.Messages { + var name string + user, err := s.Client.GetUserInfo(message.User) + if err == nil { + name = user.Name + } else { + name = "unknown" + } + + msg := fmt.Sprintf("[%s] %s", name, message.Text) + messages = append(messages, msg) + } + + return messages +} diff --git a/src/views/chat.go b/src/views/chat.go index ed123c1..435ddc6 100644 --- a/src/views/chat.go +++ b/src/views/chat.go @@ -2,6 +2,7 @@ package views import ( "github.com/erroneousboat/slack-term/src/components" + "github.com/erroneousboat/slack-term/src/service" "github.com/gizak/termui" ) @@ -9,15 +10,15 @@ import ( type View struct { Input *components.Input Chat *components.Chat - Channels *termui.List - Mode *termui.Par + Channels *components.Channels + Mode *components.Mode } -func CreateChatView() *View { +func CreateChatView(svc *service.SlackService) *View { input := components.CreateInput() - channels := components.CreateChannelsComponent(input.Par.Height) - chat := components.CreateChat(input.Par.Height) - mode := components.CreateModeComponent() + channels := components.CreateChannels(svc, input.Par.Height) + chat := components.CreateChat(svc, input.Par.Height) + mode := components.CreateMode() view := &View{ Input: input,