Working proof of concept
This commit is contained in:
parent
4b6cf7d0ce
commit
f2cec168f7
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
slack-status
|
||||
slack-status-cli
|
||||
|
49
README.md
49
README.md
@ -1,3 +1,50 @@
|
||||
# slack-status-cli
|
||||
|
||||
Set your Slack status via the command line
|
||||
Set your Slack status via the command line
|
||||
|
||||
## Example usage
|
||||
|
||||
Set auth token (it will store it in `~/.config/slack-status-cli` or your `$XDG_CONFIG_HOME` dir
|
||||
|
||||
slack-status -auth-token <your auth token>
|
||||
|
||||
Set status without emoji
|
||||
|
||||
slack-status Walking the dog
|
||||
|
||||
Set status with emoji
|
||||
|
||||
slack-status :walking-the-dog: Walking the dog
|
||||
slack-status --emoji :walking-the-dog: Walking the dog
|
||||
|
||||
Set status with duration (eg. `10m`, `2h`, `7d12h`)
|
||||
|
||||
slack-status 10m :walking-the-dog: Walking the dog
|
||||
slack-status :walking-the-dog: Walking the dog for 10m
|
||||
slack-status --duration 10m --emoji :walking-the-dog: Walking the dog
|
||||
|
||||
Set status with duration and snooze notifications
|
||||
|
||||
slack-status --snooze --duration 12h --emoji :bed: Good night
|
||||
slack-status --snooze :bed: Good night for 12h
|
||||
|
||||
Set a status that contains a duration
|
||||
|
||||
# Set status to "On a break" for 5 minutes
|
||||
slack-status :sleeping: On a break for 5m
|
||||
# Set status to "On a break for 5m" for 5 minutes
|
||||
slack-status --duration 5m :sleeping: On a break for 5m
|
||||
# Set status to "On a break for 5m" with no duration
|
||||
slack-status :sleeping: "On a break for 5m"
|
||||
|
||||
Clear existing status and snooze durations
|
||||
|
||||
slack-status
|
||||
|
||||
Snooze notifications without updating your status
|
||||
|
||||
slack-status --duration 15m --snooze
|
||||
|
||||
## Future
|
||||
|
||||
I plan to do a bit of work to bundle this for easier distribution and maybe support multiple workspaces.
|
||||
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/iamthefij/slack-status-cli
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/slack-go/slack v0.7.4
|
14
go.sum
Normal file
14
go.sum
Normal file
@ -0,0 +1,14 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/slack-go/slack v0.7.4 h1:Z+7CmUDV+ym4lYLA4NNLFIpr3+nDgViHrx8xsuXgrYs=
|
||||
github.com/slack-go/slack v0.7.4/go.mod h1:FGqNzJBmxIsZURAxh2a8D21AnOVvvXZvGligs4npPUM=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
189
main.go
Normal file
189
main.go
Normal file
@ -0,0 +1,189 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
// statusInfo contains all args passed from the command line
|
||||
type statusInfo struct {
|
||||
emoji, statusText string
|
||||
duration time.Duration
|
||||
snooze bool
|
||||
accessToken string
|
||||
}
|
||||
|
||||
// getExipirationTime returns epoch time that status should expire from the duration.
|
||||
func (si statusInfo) getExpirationTime() int64 {
|
||||
if si.duration == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return time.Now().Add(si.duration).Unix()
|
||||
}
|
||||
|
||||
// getConfigFilePath returns the path of a given file within the config folder.
|
||||
// The config folder will be created in ~/.local/config/slack-status-cli if it does not exist.
|
||||
func getConfigFilePath(filename string) string {
|
||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if configHome == "" {
|
||||
configHome = "~/.local/config"
|
||||
}
|
||||
|
||||
configDir := filepath.Join(configHome, "slack-status-cli")
|
||||
_ = os.MkdirAll(configDir, 0755)
|
||||
|
||||
return filepath.Join(configDir, filename)
|
||||
}
|
||||
|
||||
// readWriteAccessToken will store and retrieve access tokens for future use.
|
||||
func readWriteAccessToken(accessToken string) (string, error) {
|
||||
tokenFile := getConfigFilePath("token")
|
||||
|
||||
if accessToken != "" {
|
||||
err := ioutil.WriteFile(tokenFile, []byte(accessToken), 0600)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing access token")
|
||||
}
|
||||
|
||||
return accessToken, err
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(tokenFile)
|
||||
if err != nil {
|
||||
fmt.Println("No token provided on command line or in file")
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
// createClient will return a Slack client with the provided access token.
|
||||
// If that token is empty, it will try to get one from the config folder.
|
||||
func createClient(accessToken string) (*slack.Client, error) {
|
||||
var err error
|
||||
|
||||
accessToken, err = readWriteAccessToken(accessToken)
|
||||
if err != nil {
|
||||
fmt.Println("error reading access token")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := slack.New(accessToken)
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// readDurationArgs will attempt to find a duration within command line args rather than flags.
|
||||
// It will look for a prefixed duration. eg. "5m :cowboy: Howdy y'all" and a postfix duration
|
||||
// following the word "for". eg. ":dancing: Dancing for 1h".
|
||||
func readDurationArgs(args []string) ([]string, *time.Duration) {
|
||||
// If there are no args, we have no duration
|
||||
if len(args) == 0 {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Try to parse the first value
|
||||
durationVal, err := time.ParseDuration(args[0])
|
||||
if err == nil {
|
||||
// Found a duration, return the trimmed args and duration
|
||||
return args[1:], &durationVal
|
||||
}
|
||||
|
||||
// If the args are less than two, then we don't have a "for <duration>" expression
|
||||
minArgsForSuffix := 2
|
||||
if len(args) < minArgsForSuffix {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Check for a "for <duration>" expression at end of args
|
||||
if strings.ToLower(args[len(args)-2]) == "for" {
|
||||
durationVal, err = time.ParseDuration(args[len(args)-1])
|
||||
if err == nil {
|
||||
// Found a duration, return the trimmed args and duration
|
||||
return args[:len(args)-2], &durationVal
|
||||
}
|
||||
}
|
||||
|
||||
// Default return input
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// readFlags will read all flags off the command line.
|
||||
func readFlags() statusInfo {
|
||||
snooze := flag.Bool("snooze", false, "snooze notifications")
|
||||
duration := flag.Duration("duration", 0, "duration to set status for")
|
||||
emoji := flag.String("emoji", "", "emoji to use as status")
|
||||
accessToken := flag.String("access-token", "", "slack access token")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Freeform input checks the first argument to see if it's a duration
|
||||
args := flag.Args()
|
||||
|
||||
// Duration was not set via a flag, check the args
|
||||
if *duration == 0 {
|
||||
var parsedDuration *time.Duration
|
||||
args, parsedDuration = readDurationArgs(args)
|
||||
|
||||
if parsedDuration != nil {
|
||||
duration = parsedDuration
|
||||
}
|
||||
}
|
||||
|
||||
if *emoji == "" && len(args) > 0 {
|
||||
if args[0][0] == ':' && args[0][len(args[0])-1] == ':' {
|
||||
emoji = &args[0]
|
||||
args = args[1:]
|
||||
}
|
||||
}
|
||||
|
||||
statusText := strings.Join(args, " ")
|
||||
|
||||
return statusInfo{
|
||||
duration: *duration,
|
||||
snooze: *snooze,
|
||||
emoji: *emoji,
|
||||
accessToken: *accessToken,
|
||||
statusText: statusText,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
args := readFlags()
|
||||
|
||||
client, err := createClient(args.accessToken)
|
||||
if err != nil {
|
||||
fmt.Println("error getting client")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = client.SetUserCustomStatus(args.statusText, args.emoji, args.getExpirationTime())
|
||||
if err != nil {
|
||||
fmt.Println("error setting status")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if args.snooze {
|
||||
_, err = client.SetSnooze(int(args.duration.Minutes()))
|
||||
if err != nil {
|
||||
fmt.Println("error setting snooze")
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
_, err = client.EndSnooze()
|
||||
if err != nil && err.Error() != "snooze_not_active" {
|
||||
fmt.Println("error ending snooze")
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user