Compare commits
19 Commits
Author | SHA1 | Date |
---|---|---|
IamTheFij | afffddc07b | |
IamTheFij | b74edaff2f | |
IamTheFij | fb0fe18eb7 | |
IamTheFij | 83279f4fbf | |
IamTheFij | a388a3f78b | |
IamTheFij | 5d2f4b1bba | |
IamTheFij | 776a1129f9 | |
IamTheFij | a47d73b669 | |
IamTheFij | 0b0b7fe395 | |
IamTheFij | c303bc055d | |
IamTheFij | 461fa0f4e7 | |
IamTheFij | 03399ac35d | |
IamTheFij | 1a1689cd88 | |
IamTheFij | 62037b9dab | |
Alex Levy | aba5a5b6eb | |
IamTheFij | 9a03c1da00 | |
IamTheFij | bae836fc91 | |
IamTheFij | 58f246f1b3 | |
IamTheFij | 315c486736 |
|
@ -5,6 +5,9 @@ name: test
|
||||||
steps:
|
steps:
|
||||||
- name: check
|
- name: check
|
||||||
image: iamthefij/drone-pre-commit:personal
|
image: iamthefij/drone-pre-commit:personal
|
||||||
|
commands:
|
||||||
|
- make certs
|
||||||
|
- pre-commit run --all-files
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
|
@ -23,7 +26,7 @@ trigger:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build all binaries
|
- name: build all binaries
|
||||||
image: golang:1.15
|
image: golang:1.16
|
||||||
environment:
|
environment:
|
||||||
VERSION: ${DRONE_TAG:-${DRONE_COMMIT}}
|
VERSION: ${DRONE_TAG:-${DRONE_COMMIT}}
|
||||||
CLIENT_ID:
|
CLIENT_ID:
|
||||||
|
|
|
@ -18,3 +18,5 @@
|
||||||
slack-status
|
slack-status
|
||||||
slack-status-cli
|
slack-status-cli
|
||||||
dist/
|
dist/
|
||||||
|
.env
|
||||||
|
certs/
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- bodyclose
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- exhaustive
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- goerr113
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- gomnd
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- interfacer
|
||||||
|
- maligned
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nlreturn
|
||||||
|
- noctx
|
||||||
|
- unparam
|
||||||
|
- wsl
|
||||||
|
disable:
|
||||||
|
- gochecknoglobals
|
|
@ -7,7 +7,7 @@ repos:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- repo: git://github.com/dnephin/pre-commit-golang
|
- repo: https://github.com/dnephin/pre-commit-golang
|
||||||
rev: v0.3.5
|
rev: v0.3.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: go-fmt
|
- id: go-fmt
|
||||||
|
|
20
Makefile
20
Makefile
|
@ -1,6 +1,6 @@
|
||||||
OUTPUT = slack-status
|
OUTPUT = slack-status
|
||||||
GOFILES = *.go go.mod go.sum
|
GOFILES = *.go go.mod go.sum
|
||||||
DIST_ARCH = darwin-amd64 linux-amd64
|
DIST_ARCH = darwin-amd64 darwin-arm64 linux-amd64 linux-arm64
|
||||||
DIST_TARGETS = $(addprefix dist/$(OUTPUT)-,$(DIST_ARCH))
|
DIST_TARGETS = $(addprefix dist/$(OUTPUT)-,$(DIST_ARCH))
|
||||||
VERSION ?= $(shell git describe --tags --dirty)
|
VERSION ?= $(shell git describe --tags --dirty)
|
||||||
|
|
||||||
|
@ -19,24 +19,38 @@ test:
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
go test
|
go test
|
||||||
|
|
||||||
slack-status: $(GOFILES)
|
slack-status: $(GOFILES) certs
|
||||||
go build -o $(OUTPUT)
|
go build -o $(OUTPUT)
|
||||||
|
|
||||||
.PHONY: dist
|
.PHONY: dist
|
||||||
dist: $(DIST_TARGETS)
|
dist: $(DIST_TARGETS)
|
||||||
|
|
||||||
$(DIST_TARGETS): $(GOFILES)
|
$(DIST_TARGETS): $(GOFILES) certs
|
||||||
@mkdir -p ./dist
|
@mkdir -p ./dist
|
||||||
GOOS=$(word 3, $(subst -, ,$(@))) GOARCH=$(word 4, $(subst -, ,$(@))) \
|
GOOS=$(word 3, $(subst -, ,$(@))) GOARCH=$(word 4, $(subst -, ,$(@))) \
|
||||||
go build \
|
go build \
|
||||||
-ldflags '-X "main.version=${VERSION}" -X "main.defaultClientID=$(CLIENT_ID)" -X "main.defaultClientSecret=$(CLIENT_SECRET)"' \
|
-ldflags '-X "main.version=${VERSION}" -X "main.defaultClientID=$(CLIENT_ID)" -X "main.defaultClientSecret=$(CLIENT_SECRET)"' \
|
||||||
-o $@
|
-o $@
|
||||||
|
|
||||||
|
.PHONY: certs
|
||||||
|
certs: certs/key.pem
|
||||||
|
certs/cert.pem: certs/key.pem
|
||||||
|
certs/key.pem:
|
||||||
|
mkdir -p certs/
|
||||||
|
openssl req -x509 -subj "/C=US/O=Slack Status CLI/CN=localhost:8888" \
|
||||||
|
-nodes -days 365 -newkey "rsa:2048" \
|
||||||
|
-addext "subjectAltName=DNS:localhost:8888" \
|
||||||
|
-keyout certs/key.pem -out certs/cert.pem
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -f ./slack-status
|
rm -f ./slack-status
|
||||||
rm -fr ./dist
|
rm -fr ./dist
|
||||||
|
|
||||||
|
.PHONY: clean-certs
|
||||||
|
clean-certs:
|
||||||
|
rm -fr ./certs
|
||||||
|
|
||||||
.PHONY: install-hooks
|
.PHONY: install-hooks
|
||||||
install-hooks:
|
install-hooks:
|
||||||
pre-commit install --overwrite --install-hooks
|
pre-commit install --overwrite --install-hooks
|
||||||
|
|
10
README.md
10
README.md
|
@ -4,11 +4,11 @@ Set your Slack status via the command line
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Rather than host a web server that you would need to trust, this command runs one on your local machine to retrieve the OAuth code. This page is hosted with an auto generated, self-signed certificate.
|
Rather than host a web server that you would need to trust, this command runs one on your local machine to retrieve the OAuth code. This page is hosted with an embedded, self-signed certificate.
|
||||||
|
|
||||||
This requires you to have `openssl` installed on your machine and, when the page loads for the first time, it will require you to trust the certificate or ignore the warning.
|
When the page loads for the first time, it will require you to trust the certificate or ignore the warning.
|
||||||
|
|
||||||
Here's how to do that on [Firefox](https://support.mozilla.org/en-US/kb/error-codes-secure-websites?as=u&utm_source=inproduct#w_self-signed-certificate). On Chrome, you may have to enable `chrome://flags/#allow-insecure-localhost`.
|
Here's how to do that on [Firefox](https://support.mozilla.org/en-US/kb/error-codes-secure-websites?as=u&utm_source=inproduct#w_self-signed-certificate). On Chrome, you may have to enable `chrome://flags/#allow-insecure-localhost`. If you can't get the link to open in Chrome, you can copy the URL into a different browser and try it there. It should work in Firefox or Safari.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
@ -69,3 +69,7 @@ Clear existing status and snooze durations
|
||||||
Snooze notifications without updating your status
|
Snooze notifications without updating your status
|
||||||
|
|
||||||
slack-status -duration 15m -snooze
|
slack-status -duration 15m -snooze
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Building requires you to have `openssl` installed on your machine and to export `CLIENT_ID` and `CLIENT_SECRET` environment variables. The certificate and client info can be overridden at runtime for development purposes by running with exported variables post compilation as well as placing the your openssl pem files in the application's config directory. See `auth.go` for more information.
|
||||||
|
|
82
auth.go
82
auth.go
|
@ -2,11 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -14,9 +15,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// These are set via build flags but can be overriden via environment variables.
|
// These are set via build flags but can be overridden via environment variables.
|
||||||
defaultClientID = ""
|
defaultClientID = ""
|
||||||
defaultClientSecret = ""
|
defaultClientSecret = ""
|
||||||
|
|
||||||
|
//go:embed "certs/cert.pem"
|
||||||
|
certPem []byte
|
||||||
|
//go:embed "certs/key.pem"
|
||||||
|
keyPem []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
httpReadTimeout = 5 * time.Second
|
||||||
|
httpWriteTimeout = 10 * time.Second
|
||||||
|
httpIdleTimeout = 120 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type slackApp struct {
|
type slackApp struct {
|
||||||
|
@ -42,12 +54,42 @@ func (app slackApp) listenForCode() (string, error) {
|
||||||
// start an http listener and listen for the redirect and return the code from params
|
// start an http listener and listen for the redirect and return the code from params
|
||||||
var code string
|
var code string
|
||||||
|
|
||||||
|
certPath, err := getConfigFilePath("cert.pem")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed checking config path for cert: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPath, err := getConfigFilePath("key.pem")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed checking config path for key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCfg := &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If config files don't exist, use embedded
|
||||||
|
if !fileExists(certPath) && !fileExists(keyPath) {
|
||||||
|
cert, err := tls.X509KeyPair(certPem, keyPem)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed loading embedded key pair: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCfg.Certificates = make([]tls.Certificate, 1)
|
||||||
|
tlsCfg.Certificates[0] = cert
|
||||||
|
|
||||||
|
// Empty out paths since they don't exist so embedded certs will be used
|
||||||
|
certPath = ""
|
||||||
|
keyPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
// Also, should generate TLS certificate to use since https is a required scheme
|
// Also, should generate TLS certificate to use since https is a required scheme
|
||||||
server := http.Server{
|
server := http.Server{
|
||||||
Addr: app.listenHost,
|
Addr: app.listenHost,
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: httpReadTimeout,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: httpWriteTimeout,
|
||||||
IdleTimeout: 120 * time.Second,
|
IdleTimeout: httpIdleTimeout,
|
||||||
|
TLSConfig: tlsCfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc(app.listenPath, func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc(app.listenPath, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -68,15 +110,6 @@ func (app slackApp) listenForCode() (string, error) {
|
||||||
}()
|
}()
|
||||||
})
|
})
|
||||||
|
|
||||||
certPath := getConfigFilePath("cert.pem")
|
|
||||||
keyPath := getConfigFilePath("key.pem")
|
|
||||||
|
|
||||||
if !fileExists(certPath) || !fileExists(keyPath) {
|
|
||||||
if err := generateSelfSignedCertificates(certPath, keyPath); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := server.ListenAndServeTLS(certPath, keyPath); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := server.ListenAndServeTLS(certPath, keyPath); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -84,27 +117,6 @@ func (app slackApp) listenForCode() (string, error) {
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSelfSignedCertificates(certPath, keyPath string) error {
|
|
||||||
command := exec.Command(
|
|
||||||
"openssl",
|
|
||||||
"req",
|
|
||||||
"-x509",
|
|
||||||
"-subj",
|
|
||||||
"/C=US/O=Slack Status CLI/CN=localhost:8888",
|
|
||||||
"-nodes",
|
|
||||||
"-days",
|
|
||||||
"365",
|
|
||||||
"-newkey",
|
|
||||||
"rsa:2048",
|
|
||||||
"-keyout",
|
|
||||||
keyPath,
|
|
||||||
"-out",
|
|
||||||
certPath,
|
|
||||||
)
|
|
||||||
|
|
||||||
return command.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func authenticate() (string, error) {
|
func authenticate() (string, error) {
|
||||||
app := slackApp{
|
app := slackApp{
|
||||||
userScopes: []string{"dnd:write", "users.profile:write", "team:read"},
|
userScopes: []string{"dnd:write", "users.profile:write", "team:read"},
|
||||||
|
|
69
config.go
69
config.go
|
@ -4,43 +4,77 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
errUnknownDomain = errors.New("unknown domain")
|
PERM_OWNER_RW = 0o600
|
||||||
|
PERM_RW_ALL = 0o755
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errUnknownDomain = errors.New("unknown domain")
|
||||||
|
|
||||||
type configData struct {
|
type configData struct {
|
||||||
DefaultDomain string
|
DefaultDomain string
|
||||||
DomainTokens map[string]string
|
DomainTokens map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfigFilePath returns the path of a given file within the config folder.
|
// getConfigFilePath returns the path of a given file within the UserConfigDir.
|
||||||
// The config folder will be created in ~/.local/config/slack-status-cli if it does not exist.
|
func getConfigFilePath(filename string) (string, error) {
|
||||||
func getConfigFilePath(filename string) string {
|
configApplicationName := "slack-status-cli"
|
||||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
|
||||||
if configHome == "" {
|
configDir, err := os.UserConfigDir()
|
||||||
configHome = "~/.local/config"
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error getting current config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configDir := filepath.Join(configHome, "slack-status-cli")
|
configDir = filepath.Join(configDir, configApplicationName)
|
||||||
_ = os.MkdirAll(configDir, 0755)
|
_ = os.MkdirAll(configDir, PERM_RW_ALL)
|
||||||
|
configFile := filepath.Join(configDir, filename)
|
||||||
|
|
||||||
return filepath.Join(configDir, filename)
|
// Handle migration of old config file path
|
||||||
|
// NOTE: Will be removed in future versions
|
||||||
|
if !fileExists(configFile) {
|
||||||
|
// Get old config path to see if we should migrate
|
||||||
|
userHomeDir, _ := os.UserHomeDir()
|
||||||
|
legacyConfigFile := filepath.Join(
|
||||||
|
userHomeDir,
|
||||||
|
".config",
|
||||||
|
configApplicationName,
|
||||||
|
filename,
|
||||||
|
)
|
||||||
|
|
||||||
|
if fileExists(legacyConfigFile) {
|
||||||
|
log.Printf("Migrating config from %s to %s\n", legacyConfigFile, configFile)
|
||||||
|
|
||||||
|
err = os.Rename(legacyConfigFile, configFile)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"error migrating old config from %s: %w",
|
||||||
|
legacyConfigFile,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFile, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// readConfig returns the current configuration
|
// readConfig returns the current configuration
|
||||||
func readConfig() (*configData, error) {
|
func readConfig() (*configData, error) {
|
||||||
configPath := getConfigFilePath("config.json")
|
configPath, err := getConfigFilePath("config.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if !fileExists(configPath) {
|
if !fileExists(configPath) {
|
||||||
return &configData{DomainTokens: map[string]string{}}, nil
|
return &configData{DomainTokens: map[string]string{}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(configPath)
|
content, err := os.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading config from file: %w", err)
|
return nil, fmt.Errorf("error reading config from file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -57,14 +91,17 @@ func readConfig() (*configData, error) {
|
||||||
|
|
||||||
// writeConfig writes the provided config data
|
// writeConfig writes the provided config data
|
||||||
func writeConfig(config configData) error {
|
func writeConfig(config configData) error {
|
||||||
configPath := getConfigFilePath("config.json")
|
configPath, err := getConfigFilePath("config.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
contents, err := json.Marshal(config)
|
contents, err := json.Marshal(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed converting config to json: %w", err)
|
return fmt.Errorf("failed converting config to json: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ioutil.WriteFile(configPath, contents, 0600); err != nil {
|
if err = os.WriteFile(configPath, contents, PERM_OWNER_RW); err != nil {
|
||||||
return fmt.Errorf("error writing config to file: %w", err)
|
return fmt.Errorf("error writing config to file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -1,5 +1,7 @@
|
||||||
module github.com/iamthefij/slack-status-cli
|
module github.com/iamthefij/slack-status-cli
|
||||||
|
|
||||||
go 1.15
|
go 1.21
|
||||||
|
|
||||||
require github.com/slack-go/slack v0.7.4
|
require github.com/slack-go/slack v0.12.3
|
||||||
|
|
||||||
|
require github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
|
15
go.sum
15
go.sum
|
@ -1,14 +1,11 @@
|
||||||
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/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/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
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/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.12.3 h1:92/dfFU8Q5XP6Wp5rr5/T5JHLM5c5Smtn53fhToAP88=
|
||||||
github.com/slack-go/slack v0.7.4/go.mod h1:FGqNzJBmxIsZURAxh2a8D21AnOVvvXZvGligs4npPUM=
|
github.com/slack-go/slack v0.12.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
65
main.go
65
main.go
|
@ -10,23 +10,19 @@ import (
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var version = "dev"
|
||||||
version = "dev"
|
|
||||||
)
|
|
||||||
|
|
||||||
// statusInfo contains all args passed from the command line
|
// statusInfo contains all args passed from the command line
|
||||||
type statusInfo struct {
|
type statusInfo struct {
|
||||||
// status contents
|
|
||||||
emoji, statusText string
|
emoji, statusText string
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
snooze bool
|
snooze bool
|
||||||
|
}
|
||||||
|
|
||||||
// domain and login management
|
// commandOptions contains non-status options passed to the command
|
||||||
login, makeDefault bool
|
type commandOptions struct {
|
||||||
domain string
|
login, makeDefault, showVersion bool
|
||||||
|
domain string
|
||||||
// other
|
|
||||||
showVersion bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExipirationTime returns epoch time that status should expire from the duration.
|
// getExipirationTime returns epoch time that status should expire from the duration.
|
||||||
|
@ -74,7 +70,7 @@ func readDurationArgs(args []string) ([]string, *time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// readFlags will read all flags off the command line.
|
// readFlags will read all flags off the command line.
|
||||||
func readFlags() statusInfo {
|
func readFlags() (statusInfo, commandOptions) {
|
||||||
// Non-status flags
|
// Non-status flags
|
||||||
login := flag.Bool("login", false, "login to a Slack workspace")
|
login := flag.Bool("login", false, "login to a Slack workspace")
|
||||||
domain := flag.String("domain", "", "domain to set status on")
|
domain := flag.String("domain", "", "domain to set status on")
|
||||||
|
@ -111,16 +107,16 @@ func readFlags() statusInfo {
|
||||||
statusText := strings.Join(args, " ")
|
statusText := strings.Join(args, " ")
|
||||||
|
|
||||||
return statusInfo{
|
return statusInfo{
|
||||||
duration: *duration,
|
duration: *duration,
|
||||||
snooze: *snooze,
|
snooze: *snooze,
|
||||||
emoji: *emoji,
|
emoji: *emoji,
|
||||||
statusText: statusText,
|
statusText: statusText,
|
||||||
|
}, commandOptions{
|
||||||
login: *login,
|
login: *login,
|
||||||
domain: *domain,
|
domain: *domain,
|
||||||
makeDefault: *makeDefault,
|
makeDefault: *makeDefault,
|
||||||
showVersion: *showVersion,
|
showVersion: *showVersion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loginAndSave will return a client after a new login flow and save the results
|
// loginAndSave will return a client after a new login flow and save the results
|
||||||
|
@ -152,6 +148,7 @@ func loginAndSave(domain string) (*slack.Client, error) {
|
||||||
// getClient returns a client either via the provided login or default login
|
// getClient returns a client either via the provided login or default login
|
||||||
func getClient(domain string) (*slack.Client, error) {
|
func getClient(domain string) (*slack.Client, error) {
|
||||||
var accessToken string
|
var accessToken string
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
|
@ -170,21 +167,23 @@ func getClient(domain string) (*slack.Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
args := readFlags()
|
status, options := readFlags()
|
||||||
|
|
||||||
if args.showVersion {
|
if options.showVersion {
|
||||||
fmt.Println("version:", version)
|
fmt.Println("version:", version)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var client *slack.Client
|
var client *slack.Client
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// If the new-auth flag is present, force an auth flow
|
// If the new-auth flag is present, force an auth flow
|
||||||
if args.login {
|
if options.login {
|
||||||
client, err = loginAndSave(args.domain)
|
client, err = loginAndSave(options.domain)
|
||||||
} else {
|
} else {
|
||||||
client, err = getClient(args.domain)
|
client, err = getClient(options.domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We encountered some error in logging in
|
// We encountered some error in logging in
|
||||||
|
@ -193,21 +192,21 @@ func main() {
|
||||||
log.Fatal(fmt.Errorf("failed to get or save client: %w", err))
|
log.Fatal(fmt.Errorf("failed to get or save client: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a domain is provided and asked to make deafult, save it to config
|
// If a domain is provided and asked to make default, save it to config
|
||||||
if args.makeDefault && args.domain != "" {
|
if options.makeDefault && options.domain != "" {
|
||||||
if err = saveDefaultLogin(args.domain); err != nil {
|
if err = saveDefaultLogin(options.domain); err != nil {
|
||||||
log.Fatal(fmt.Errorf("failed saving default domain %s: %w", args.domain, err))
|
log.Fatal(fmt.Errorf("failed saving default domain %s: %w", options.domain, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.SetUserCustomStatus(args.statusText, args.emoji, args.getExpirationTime())
|
err = client.SetUserCustomStatus(status.statusText, status.emoji, status.getExpirationTime())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error setting status")
|
fmt.Println("error setting status")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.snooze {
|
if status.snooze {
|
||||||
_, err = client.SetSnooze(int(args.duration.Minutes()))
|
_, err = client.SetSnooze(int(status.duration.Minutes()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error setting snooze")
|
fmt.Println("error setting snooze")
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
Loading…
Reference in New Issue