├── README.md ├── bar └── app.go ├── go.mod └── go.sum /README.md: -------------------------------------------------------------------------------- 1 | # SlackProgressBar 2 | 3 | SlackProgressBar is a Go library for creating a real-time progress bar in a Slack channel. It provides a way to visualize the progress of long-running tasks directly in Slack, so your team can stay updated on the task's progress. 4 | 5 | ![Screenshot 2023-10-03 at 04 17 00](https://github.com/r-scheele/SlackProgressBar/assets/67229938/9449e821-117f-4192-9252-33779c93a983) 6 | 7 | 8 | ## Features 9 | 10 | - Real-time progress updates in Slack 11 | - Customizable progress bar with ETA 12 | - Pause, resume, and log additional information while processing 13 | - Memory usage display 14 | 15 | ## Installation 16 | 17 | ```bash 18 | go get github.com/r-scheele/SlackProgressBar@v0.1.0 19 | ``` 20 | 21 | ## Usage 22 | 23 | 1. First, import the library in your Go file: 24 | 25 | ```go 26 | import ( 27 | "github.com/r-scheele/SlackProgressBar/bar" 28 | ) 29 | ``` 30 | 31 | 2. Create a new `SlackProgress` instance with your Slack token and channel: 32 | 33 | ```go 34 | sp := &bar.SlackProgress{ 35 | Token: "your-slack-token", 36 | Channel: "#your-channel", 37 | } 38 | ``` 39 | 40 | 3. Define your list of items and the function to process each item: 41 | 42 | ```go 43 | items := make([]string, 100) 44 | for i := 1; i <= 100; i++ { 45 | items[i-1] = fmt.Sprintf("item%d", i) 46 | } 47 | 48 | func processItem(item string) { 49 | // Your item processing code here 50 | } 51 | ``` 52 | 53 | 4. Call `Iter` on your `SlackProgress` instance to process the items and update the progress bar in Slack: 54 | 55 | ```go 56 | sp.Iter(items, processItem) 57 | ``` 58 | 59 | ## Configuration 60 | 61 | You can customize the appearance and behavior of the progress bar by setting additional fields on the `SlackProgress` struct: 62 | 63 | ```go 64 | sp := &SlackProgress{ 65 | Token: "your-slack-token", 66 | Channel: "#your-channel", 67 | Suffix: "%", 68 | ProgressBarChar: "▓", 69 | Precision: 1, 70 | RateLimit: time.Millisecond * 500, 71 | CompletionCallback: func() { 72 | fmt.Println("Task completed!") 73 | }, 74 | } 75 | ``` 76 | 77 | 78 | 79 | ### Creating a Slack App 80 | 81 | 1. **Navigate to Slack API Page**: 82 | - Go to the [Slack API page](https://api.slack.com/apps) and click on `Create New App`. 83 | 84 | 2. **Name Your App**: 85 | - Enter a name for your app (e.g., `SlackProgressBar`) and select the Slack workspace where you want the app to reside, then click `Create App`. 86 | 87 | 3. **Configure Basic Information**: 88 | - Under the `Basic Information` section, you can set up various information about your app such as its name, description, and icons. 89 | 90 | ### Setting Up OAuth and Permissions 91 | 92 | 1. **Navigate to OAuth & Permissions**: 93 | - On the left sidebar, click on `OAuth & Permissions`. 94 | 95 | 2. **Add OAuth Scopes**: 96 | - Under the `Bot Token Scopes` section, click `Add an OAuth Scope` and add the following scopes: 97 | - `chat:write` (to send messages) 98 | - `chat:write.public` (if you want to post to channels without joining) 99 | 100 | 3. **Install App to Workspace**: 101 | - Click on `Install to Workspace` button at the top of the page. 102 | - You'll be redirected to a permission request screen in your Slack workspace. Click `Allow` to install the app and grant it the requested permissions. 103 | 104 | 4. **Copy the Bot User OAuth Token**: 105 | - Once the app is installed, you'll be redirected back to the `OAuth & Permissions` page. 106 | - Under the `OAuth Tokens for Your Workspace` section, you'll find a `Bot User OAuth Token`. This token will start with `xoxb-`. Copy this token as you'll need it for your Go code. 107 | 108 | ### Using the Token in Your Go Code 109 | 110 | Now that you have the OAuth token, you can use it in your Go code as follows: 111 | 112 | ```go 113 | sp := &SlackProgress{ 114 | Token: "xoxb-your-token-here", 115 | Channel: "#your-channel", 116 | // ... other configuration ... 117 | } 118 | ``` 119 | 120 | Replace `"xoxb-your-token-here"` with the actual Bot User OAuth Token you copied earlier. 121 | 122 | ### Running Your Go Code 123 | 124 | Now you can run your Go code, and it should be able to post and update messages in the specified Slack channel using the credentials of the Slack app you created. 125 | 126 | This setup allows your Go code to interact with the Slack API and post progress updates to your Slack workspace. 127 | 128 | ### Note: 129 | 130 | - Make sure that the channel you specify in your Go code exists in your Slack workspace. 131 | - If you need to post messages to multiple channels or private channels, you may need to invite the Slack app bot user to those channels within Slack. 132 | 133 | ## Contributing 134 | 135 | We welcome contributions! Please feel free to submit a pull request with any improvements. 136 | 137 | ## License 138 | 139 | This project is licensed under the MIT License. 140 | ``` 141 | 142 | You can copy and paste this template into a `README.md` file in the root of your GitHub repository, and then customize it to match the exact features and usage of your library. 143 | -------------------------------------------------------------------------------- /bar/app.go: -------------------------------------------------------------------------------- 1 | package bar 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "github.com/nlopes/slack" 11 | ) 12 | 13 | type SlackProgress struct { 14 | Token string 15 | Channel string 16 | Suffix string 17 | ProgressBarChar string 18 | Precision int 19 | RateLimit time.Duration 20 | CompletionCallback func() 21 | } 22 | 23 | type ProgressBar struct { 24 | Sp *SlackProgress 25 | Total int 26 | Done int 27 | Pos int 28 | MsgTs string 29 | ChannelID string 30 | MsgLog []string 31 | StartTime time.Time 32 | Paused bool 33 | ResumeCh chan bool 34 | mu sync.Mutex 35 | } 36 | 37 | func (sp *SlackProgress) New(total int) (*ProgressBar, error) { 38 | api := slack.New(sp.Token) 39 | channelID, timestamp, err := api.PostMessage(sp.Channel, slack.MsgOptionText("Starting...", false)) 40 | if err != nil { 41 | return nil, fmt.Errorf("unable to post initial message: %w", err) 42 | } 43 | return &ProgressBar{ 44 | Sp: sp, 45 | Total: total, 46 | MsgTs: timestamp, 47 | ChannelID: channelID, 48 | StartTime: time.Now(), 49 | ResumeCh: make(chan bool), 50 | }, nil // Return nil error 51 | } 52 | 53 | func (pb *ProgressBar) Update() { 54 | pb.mu.Lock() 55 | defer pb.mu.Unlock() 56 | 57 | if pb.Paused { 58 | <-pb.ResumeCh 59 | } 60 | 61 | ticker := time.NewTicker(pb.Sp.RateLimit) 62 | defer ticker.Stop() 63 | 64 | <-ticker.C 65 | 66 | // Update the position and percentage completion 67 | pb.Pos = int(float64(pb.Done) / float64(pb.Total) * 100) 68 | 69 | // Call makeBar to get the progress bar string with memory usage 70 | bar := pb.makeBar() 71 | 72 | _, _, _, err := slack.New(pb.Sp.Token).UpdateMessage(pb.ChannelID, pb.MsgTs, slack.MsgOptionText(bar, false)) 73 | if err != nil { 74 | pb.Log(fmt.Sprintf("Error updating progress bar: %v", err)) 75 | } 76 | } 77 | 78 | func (pb *ProgressBar) Log(msg string) { 79 | pb.mu.Lock() 80 | defer pb.mu.Unlock() 81 | 82 | timestamp := time.Now().Format("15:04:05") 83 | pb.MsgLog = append(pb.MsgLog, fmt.Sprintf("*%s* - [%s]", timestamp, msg)) 84 | content := fmt.Sprintf("%s\n%s", pb.makeBar(), strings.Join(pb.MsgLog, "\n")) 85 | 86 | _, _, _, err := slack.New(pb.Sp.Token).UpdateMessage(pb.ChannelID, pb.MsgTs, slack.MsgOptionText(content, false)) 87 | if err != nil { 88 | fmt.Printf("Error logging message: %v\n", err) 89 | } 90 | } 91 | 92 | func (pb *ProgressBar) makeBar() string { 93 | var mem runtime.MemStats 94 | runtime.ReadMemStats(&mem) 95 | memUsage := fmt.Sprintf("Memory Usage: %v KB", mem.Alloc/1024) 96 | 97 | elapsed := time.Since(pb.StartTime).Seconds() 98 | remaining := (elapsed / float64(pb.Done+1)) * float64(pb.Total-pb.Done-1) 99 | eta := time.Duration(remaining) * time.Second 100 | 101 | perc := fmt.Sprintf("%.*f", pb.Sp.Precision, float64(pb.Pos)) 102 | bar := fmt.Sprintf("%s %s%s ETA: %v", strings.Repeat(pb.Sp.ProgressBarChar, pb.Pos), perc, pb.Sp.Suffix, eta) 103 | return fmt.Sprintf("%s | %s", bar, memUsage) 104 | } 105 | 106 | func (pb *ProgressBar) Pause() { 107 | pb.Paused = true 108 | } 109 | 110 | func (pb *ProgressBar) Resume() { 111 | pb.Paused = false 112 | pb.ResumeCh <- true 113 | } 114 | 115 | func (sp *SlackProgress) Iter(items []string, processFunc func(item string)) { 116 | pb, err := sp.New(len(items)) 117 | if err != nil { 118 | fmt.Printf("Error: %v\n", err) 119 | return 120 | } 121 | for idx, item := range items { 122 | processFunc(item) 123 | pb.Done = idx + 1 // Increment idx by 1 to reflect the completed item 124 | pb.Update() 125 | } 126 | if sp.CompletionCallback != nil { 127 | sp.CompletionCallback() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/r-scheele/SlackProgressBar 2 | 3 | go 1.20 4 | 5 | require github.com/nlopes/slack v0.6.0 6 | 7 | require ( 8 | github.com/gorilla/websocket v1.2.0 // indirect 9 | github.com/pkg/errors v0.8.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= 4 | github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 5 | github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA= 6 | github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= 7 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 8 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 12 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 13 | --------------------------------------------------------------------------------