├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── app.go ├── components.go ├── docs └── screenshots │ └── 2021-04-13.png ├── go.mod ├── go.sum ├── main.go └── state_teams.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.16 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | teams-cli 2 | Copyright © 2021 Denys Vitali 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # teams-cli 2 | 3 | A Command Line Interface (or TUI) to interact with Microsoft Teams 4 | that uses the [teams-api](https://github.com/fossteams/teams-api) 5 | Go package. 6 | 7 | ## Status 8 | 9 | The CLI only let you log-in and fetches your user and conversations, 10 | only the Teams and Channels so far are displayed, but on the background the 11 | conversations (Groups + DMs) are fetched too. 12 | 13 | This project is still WIP and will be updated soon with new features. The goal is to 14 | have a CLI / TUI replacement for the Microsoft Teams desktop client. 15 | 16 | ## Requirements 17 | 18 | - [Golang](https://golang.org/) 19 | 20 | ## Usage 21 | 22 | Follow the instructions on how to obtain a token with [teams-token](https://github.com/fossteams/teams-token), 23 | then simply run the following to start the app. Binary releases will appear on this repository as soon as 24 | we have a product with more features. 25 | 26 | ```bash 27 | go run ./ 28 | ``` 29 | 30 | If everything goes well, you should see something like this: 31 | ![Teams CLI example](./docs/screenshots/2021-04-13.png) 32 | 33 | ## What works 34 | 35 | - Logging in into Teams using the token generated via `teams-token` 36 | - Getting the list of Teams + Channels 37 | - Reading channels 38 | 39 | ## What doesn't work 40 | 41 | - Everything else 42 | 43 | ## You might also be interested in 44 | 45 | - [fossteams-frontend](https://github.com/fossteams/fossteams-frontend): a Vue based frontend for Microsoft Teams -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | teams_api "github.com/fossteams/teams-api" 7 | "github.com/fossteams/teams-api/pkg/csa" 8 | "github.com/gdamore/tcell/v2" 9 | "github.com/rivo/tview" 10 | "github.com/sirupsen/logrus" 11 | "golang.org/x/net/html" 12 | "sort" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | type AppState struct { 18 | app *tview.Application 19 | pages *tview.Pages 20 | logger *logrus.Logger 21 | 22 | TeamsState 23 | components map[string]tview.Primitive 24 | } 25 | 26 | func (s AppState) createApp() { 27 | s.pages = tview.NewPages() 28 | s.components = map[string]tview.Primitive{} 29 | 30 | // Add pages 31 | s.pages.AddPage(PageLogin, s.createLoginPage(), true, false) 32 | s.pages.AddPage(PageMain, s.createMainView(), true, false) 33 | s.pages.AddPage(PageError, s.createErrorView(), true, false) 34 | 35 | frame := tview.NewFrame(s.pages) 36 | frame.SetBorder(true) 37 | frame.SetTitle("teams-cli") 38 | frame.SetBorder(true) 39 | frame.SetTitleAlign(tview.AlignCenter) 40 | 41 | s.app.SetRoot(frame, true) 42 | 43 | // Set main page 44 | s.pages.SwitchToPage(PageLogin) 45 | s.app.SetFocus(s.pages) 46 | 47 | go s.start() 48 | } 49 | 50 | func (s *AppState) createMainView() tview.Primitive { 51 | // Top: User information 52 | // Left side: Tree view (Teams _ Channels / Conversations) 53 | // Right side: Chat view 54 | // Bottom: Navigation bar 55 | 56 | treeView := tview.NewTreeView() 57 | chatView := tview.NewList() 58 | chatView.SetBackgroundColor(tcell.ColorBlack) 59 | 60 | s.components[TrChat] = treeView 61 | s.components[ViChat] = chatView 62 | 63 | flex := tview.NewFlex(). 64 | AddItem(treeView, 0, 1, false). 65 | AddItem(chatView, 0, 2, false) 66 | 67 | return flex 68 | } 69 | 70 | func (s *AppState) createLoginPage() tview.Primitive { 71 | p := tview.NewTextView() 72 | p.SetTitle("Log-in") 73 | p.SetText("Logging in...") 74 | p.SetBackgroundColor(tcell.ColorBlue) 75 | p.SetTextAlign(tview.AlignCenter) 76 | p.SetBorder(true) 77 | p.SetBorderPadding(1, 1, 1, 1) 78 | 79 | s.components[TvLoginStatus] = p 80 | 81 | return tview.NewFlex(). 82 | AddItem(nil, 0, 1, false). 83 | AddItem(tview.NewFlex().SetDirection(tview.FlexRow). 84 | AddItem(nil, 0, 1, false). 85 | AddItem(p, 10, 1, false). 86 | AddItem(nil, 0, 1, false), 30, 1, false). 87 | AddItem(nil, 0, 1, false) 88 | } 89 | 90 | func (s *AppState) start() { 91 | // Initialize Teams client 92 | var err error 93 | s.teamsClient, err = teams_api.New() 94 | if err != nil { 95 | s.showError(err) 96 | return 97 | } 98 | 99 | // Initialize Teams State 100 | err = s.TeamsState.init(s.teamsClient) 101 | if err != nil { 102 | s.showError(err) 103 | return 104 | } 105 | 106 | go s.fillMainWindow() 107 | } 108 | 109 | func (s *AppState) showError(err error) { 110 | val, ok := s.components[TvError] 111 | if !ok { 112 | s.logger.Fatalf("unable to show error on screen: %v", err) 113 | return 114 | } 115 | val.(*tview.TextView).SetText(err.Error()) 116 | s.pages.SwitchToPage(PageError) 117 | s.app.Draw() 118 | } 119 | 120 | func (s *AppState) createErrorView() tview.Primitive { 121 | p := tview.NewTextView() 122 | p.SetTitle("ERROR") 123 | p.SetText("An error has occurred") 124 | p.SetBackgroundColor(tcell.ColorRed) 125 | p.SetTextAlign(tview.AlignCenter) 126 | p.SetBorder(true) 127 | p.SetBorderPadding(1, 1, 1, 1) 128 | 129 | s.components[TvError] = p 130 | 131 | return tview.NewFlex(). 132 | AddItem(nil, 0, 1, false). 133 | AddItem(tview.NewFlex().SetDirection(tview.FlexRow). 134 | AddItem(nil, 0, 1, false). 135 | AddItem(p, 10, 1, false). 136 | AddItem(nil, 0, 1, false), 60, 1, false). 137 | AddItem(nil, 0, 1, false) 138 | } 139 | 140 | func (s *AppState) fillMainWindow() { 141 | treeView := s.components[TrChat].(*tview.TreeView) 142 | teamsNode := tview.NewTreeNode("Teams") 143 | 144 | var firstNode *tview.TreeNode 145 | for _, t := range s.conversations.Teams { 146 | currentTeamTreeNode := tview.NewTreeNode(t.DisplayName) 147 | currentTeamTreeNode.SetReference(t) 148 | if firstNode == nil { 149 | firstNode = currentTeamTreeNode 150 | } 151 | 152 | for _, c := range t.Channels { 153 | currentChannelTreeNode := tview.NewTreeNode(c.DisplayName) 154 | currentChannelTreeNode.SetReference(c) 155 | currentChannelTreeNode.SetColor(tcell.ColorGreen) 156 | currentTeamTreeNode.AddChild(currentChannelTreeNode) 157 | } 158 | currentTeamTreeNode.CollapseAll() 159 | currentTeamTreeNode.SetColor(tcell.ColorBlue) 160 | 161 | teamsNode.AddChild(currentTeamTreeNode) 162 | } 163 | 164 | treeView.SetSelectedFunc(func(node *tview.TreeNode) { 165 | reference := node.GetReference() 166 | if reference == nil { 167 | return 168 | } 169 | 170 | children := node.GetChildren() 171 | if len(children) == 0 { 172 | channelRef := reference.(csa.Channel) 173 | s.components[ViChat].(*tview.List). 174 | SetTitle(channelRef.DisplayName). 175 | SetBorder(true). 176 | SetTitleAlign(tview.AlignCenter) 177 | 178 | // Load Conversations here 179 | go s.loadConversations(&channelRef) 180 | } else { 181 | // Collapse if visible, expand if collapsed. 182 | node.SetExpanded(!node.IsExpanded()) 183 | } 184 | }) 185 | 186 | treeView.SetRoot(teamsNode) 187 | treeView.SetCurrentNode(firstNode) 188 | 189 | s.pages.SwitchToPage(PageMain) 190 | s.app.SetFocus(treeView) 191 | s.app.Draw() 192 | } 193 | 194 | func textMessage(input string) string { 195 | output := "" 196 | z := html.NewTokenizer(bytes.NewBuffer([]byte(input))) 197 | for { 198 | tt := z.Next() 199 | if tt == html.ErrorToken { 200 | break 201 | } 202 | 203 | switch tt { 204 | case html.TextToken: 205 | text := string(z.Text()) 206 | if strings.TrimSpace(text) == "" { 207 | continue 208 | } 209 | output += fmt.Sprintf("\t%v\n", text) 210 | } 211 | if tt == html.ErrorToken { 212 | break 213 | } 214 | } 215 | return output 216 | } 217 | 218 | func (s *AppState) loadConversations(c *csa.Channel) { 219 | messages, err := s.teamsClient.GetMessages(c) 220 | 221 | if err != nil { 222 | s.showError(err) 223 | time.Sleep(5 * time.Second) 224 | s.pages.SwitchToPage(PageMain) 225 | s.app.Draw() 226 | s.app.SetFocus(s.pages) 227 | return 228 | } 229 | // Clear chat 230 | chatList := s.components[ViChat].(*tview.List) 231 | chatList.Clear() 232 | 233 | // Sort Messages by time 234 | sort.Sort(csa.SortMessageByTime(messages)) 235 | 236 | for _, message := range messages { 237 | if message.ImDisplayName == "" { 238 | // Skip messages w/o author 239 | continue 240 | } 241 | chatList.AddItem(textMessage(message.Content), message.ImDisplayName, 0, nil) 242 | } 243 | s.app.Draw() 244 | } 245 | -------------------------------------------------------------------------------- /components.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | TvLoginStatus = "tvLoginStatus" 5 | TvError = "tvErrTitle" 6 | ) 7 | 8 | // Trees 9 | const ( 10 | TrChat = "trChat" 11 | ) 12 | 13 | // Generic Views 14 | 15 | const ( 16 | ViChat = "viChat" 17 | ) 18 | 19 | // Pages 20 | 21 | const ( 22 | PageMain = "pageMain" 23 | PageLogin = "pageLogin" 24 | PageError = "pageError" 25 | ) -------------------------------------------------------------------------------- /docs/screenshots/2021-04-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossteams/teams-cli/cfd56bd330f283401bc407bf4d432e6cac4c9781/docs/screenshots/2021-04-13.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fossteams/teams-cli 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fossteams/teams-api v0.0.0-20220604181459-dbbdc3681f32 7 | github.com/gdamore/tcell/v2 v2.5.1 8 | github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 9 | github.com/sirupsen/logrus v1.8.1 10 | golang.org/x/net v0.0.0-20220531201128-c960675eff93 11 | ) 12 | 13 | require ( 14 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 15 | github.com/gdamore/encoding v1.0.0 // indirect 16 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 17 | github.com/mattn/go-runewidth v0.0.13 // indirect 18 | github.com/rivo/uniseg v0.2.0 // indirect 19 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect 20 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect 21 | golang.org/x/text v0.3.7 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 5 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 6 | github.com/fossteams/teams-api v0.0.0-20210412224300-81acfab613c5 h1:j4QTl5gbMgxqvYDFZSfXv4OXEm+jPBR/h+UG/S7jwvk= 7 | github.com/fossteams/teams-api v0.0.0-20210412224300-81acfab613c5/go.mod h1:IcMXmTe2AJKtUOHcvaSobj7D3V01aYyXdkdQebKshws= 8 | github.com/fossteams/teams-api v0.0.0-20220309212657-4c57c5b85e47 h1:SZQiQ+WCNPcbhYC3zavy7iHDFkl3Cw395BXlFA29tGs= 9 | github.com/fossteams/teams-api v0.0.0-20220309212657-4c57c5b85e47/go.mod h1:QWsDlFTF+0fxEjM0jDo51WdbgIy7JuHxI2TaxU6rRwQ= 10 | github.com/fossteams/teams-api v0.0.0-20220604181459-dbbdc3681f32 h1:8L5c5ec00rBWZzTJK+eJrMfRWYTB7R9VBhD/9yXi5Ok= 11 | github.com/fossteams/teams-api v0.0.0-20220604181459-dbbdc3681f32/go.mod h1:QWsDlFTF+0fxEjM0jDo51WdbgIy7JuHxI2TaxU6rRwQ= 12 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 13 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 14 | github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= 15 | github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= 16 | github.com/gdamore/tcell/v2 v2.2.0 h1:vSyEgKwraXPSOkvCk7IwOSyX+Pv3V2cV9CikJMXg4U4= 17 | github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= 18 | github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= 19 | github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I= 20 | github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo= 21 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 22 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 23 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 24 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 25 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 26 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 27 | github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= 28 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 29 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 30 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598 h1:AbRrGXhagPRDItERv7nauBUUPi7Ma3IGIj9FqkQKW6k= 34 | github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598/go.mod h1:VzCN9WX13RF88iH2CaGkmdHOlsy1ZZQcTmNwROqC+LI= 35 | github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 h1:xe+mmCnDN82KhC010l3NfYlA8ZbOuzbXAzSYBa6wbMc= 36 | github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk= 37 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 38 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 39 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 40 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 41 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 42 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 43 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 44 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 45 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 46 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= 47 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 48 | golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA= 49 | golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 50 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= 55 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= 58 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 60 | golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 61 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= 62 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 63 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= 64 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 65 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 66 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 67 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= 68 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 69 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 70 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 71 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 72 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 75 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 76 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | "github.com/sirupsen/logrus" 6 | ) 7 | 8 | func main(){ 9 | app := tview.NewApplication() 10 | logger := logrus.New() 11 | 12 | state := AppState{ 13 | app: app, 14 | logger: logger, 15 | } 16 | 17 | state.createApp() 18 | if err := app.EnableMouse(true).Run(); err != nil { 19 | panic(err) 20 | } 21 | } -------------------------------------------------------------------------------- /state_teams.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | teams_api "github.com/fossteams/teams-api" 6 | "github.com/fossteams/teams-api/pkg/csa" 7 | "github.com/fossteams/teams-api/pkg/models" 8 | "sort" 9 | ) 10 | 11 | type TeamsState struct { 12 | teamsClient *teams_api.TeamsClient 13 | 14 | conversations *csa.ConversationResponse 15 | me *models.User 16 | pinnedChannels []csa.ChannelId 17 | channelById map[string]Channel 18 | teamById map[string]*csa.Team 19 | } 20 | 21 | type Channel struct { 22 | *csa.Channel 23 | parent *csa.Team 24 | } 25 | 26 | func (s *TeamsState) init(client *teams_api.TeamsClient) error { 27 | if client == nil { 28 | return fmt.Errorf("client is nil") 29 | } 30 | 31 | var err error 32 | s.me, err = client.GetMe() 33 | if err != nil { 34 | return fmt.Errorf("unable to get your profile: %v", err) 35 | } 36 | 37 | s.pinnedChannels, err = client.GetPinnedChannels() 38 | if err != nil { 39 | return fmt.Errorf("unable to get pinned channels: %v", err) 40 | } 41 | 42 | s.conversations, err = client.GetConversations() 43 | if err != nil { 44 | return fmt.Errorf("unable to get conversations: %v", err) 45 | } 46 | 47 | // Sort Teams by Name 48 | sort.Sort(csa.TeamsByName(s.conversations.Teams)) 49 | 50 | // Create maps 51 | s.teamById = map[string]*csa.Team{} 52 | s.channelById = map[string]Channel{} 53 | 54 | for _, t := range s.conversations.Teams { 55 | s.teamById[t.Id] = &t 56 | for _, c := range t.Channels { 57 | s.channelById[c.Id] = Channel{ 58 | Channel: &c, 59 | parent: &t, 60 | } 61 | } 62 | } 63 | 64 | return nil 65 | } 66 | --------------------------------------------------------------------------------