Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First commit for moonsla #1

Merged
merged 6 commits into from
Jul 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
moonsla
25 changes: 25 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
language: go

go:
- "1.10"

services:
- docker

script:
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
- wget https://github.com/Crazybus/lope/releases/download/0.2.0/lope-linux_amd64 -O lope && chmod +x lope
- ./lope -workDir /go/src/github.com/Crazybus/moonsla -blacklist GO golang:1.10 '/usr/local/go/bin/go get -v ./... && /usr/local/go/bin/go run build/build.go'

after_success:
- bash <(curl -s https://codecov.io/bash)

deploy:
provider: releases
api_key:
secure: hT+TBmMhj8yFvGfTOGPhoVuiINprm600JijpT//kgO2PBiiQ/Tcc+ShEiXGXNoXb+mngMj+w4NsPbNjFR6K1TIxsyFDrmN2RvKi01SJZMKgpbJH5+VmkMPHJX0AxMcHjYTBkz9inM6PLzFsSHrDO296gjGpM4xMvh4wuOYz/4JH9dLcsn6BdchhrA1ipKrHwsDU6DaK9YvZwjRpB3eO4s5woUuYfHgr9OkAPLCUuh6u+HCRhtxH8F9aNtNVlHm43u9lLcwJ41YVycpfRNEhHp30Nrghn4PciPoqISJVyRSSva96HSlR9+T55aCGY0ch762k9P6CeP3OQfK/2s8v5Fcq40ScUm+GLntRv49O1eJuqBkI7HImnHzrb1w329MgJTijFVxf3W7jEj4mjyttuyScMzd9b7/ERRGUzhqqQx488ICw3tU9+fnfguhEaNt3DanyijT6ZYqVA6TQTanc/z+w+uQTy7w92mRocNNYPJA17yL8BOS7EJb5vu0+ifVyXco+5McTBcJUSm+zqFzLm2Ey3VGs4eVbCY3r4XvOSr3P3MjR1iB8u+fBeVqL6TPi+p0eRPtSHjoRRPjsmoupv9fpmRIDA7hXi3GoT9HZB8fSfFtKoEgfi7lz67UPwQXADhIEnTD5SKptt897lvq1DOj4lDDETakdkBkBKIBWdh4w=
file_glob: true
file: build/moonsla*
skip_cleanup: true
on:
tags: true
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,40 @@
# moonsla
View all the slack messages!
# Moonsla

[![Build Status](https://travis-ci.org/Crazybus/moonsla.svg?branch=master)](https://travis-ci.org/Crazybus/moonsla)

Moonsla is a small tool to display a stream of slack messages in a single view.

It looks something like this

```
10:42:37 - general - Michael Russell: Weird I never knew that slack threads were just normal messages
10:42:55 - random - Someone Else: Sweet Potato!
10:43:37 - general - John Smith: Can people please stop using threads for everything!
```

# Usage

If you don't have one already you will need to generate a [slack API token](https://api.slack.com/custom-integrations/legacy-tokens)

You need to set this to your `SLACK_TOKEN` environment variable
```
export SLACK_TOKEN='xoxp-1231231231232323-123123123123-123123123123123-c91238917239123'
moonsla
```

You can also set `SLACK_CHANNELS` to a comma separated list of channels to filter for
```
export SLACK_CHANNELS='general,random'
```

# Why?

I'm not a fan of notifications because they are very intrusive. Instead I used to keep slack always open with the slackbot channel active (so I don't accidentally type shell commands into #general). Whenever I had a spare moment I would then check each slack channel to see if there was anything that needed my attention. This took a bit too long and quite often the message would be bot telling me I had just submitted a pull request

# Future

* Fix channel naming for slackbot and private channels
* Improve formatting of messages so that sub-teams, urls and everything else is formatted as expected
* Automatically link to the slack message so it is easy to open up the message from moonsla in slack
* Support multiple slack workspaces
* Add an optional web interface to make things look pretty and allow displaying of images
84 changes: 84 additions & 0 deletions build/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"crypto/sha256"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
)

var buildDir = filepath.FromSlash("build/")

var operatingSystems = [...]string{
"darwin",
"linux",
"windows",
}

var archs = [...]string{
"386",
"amd64",
}

func checksum(goos string, goarch string) error {
file := buildDir + "moonsla-" + goos + "_" + goarch
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()

h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return err
}
sumFile := file + ".sha256"
hash := fmt.Sprintf("%x", h.Sum(nil))
fmt.Println(hash, file)

hashFile, err := os.Create(sumFile)
if err != nil {
return err
}
defer hashFile.Close()

_, err = hashFile.WriteString(hash)
if err != nil {
return err
}
return nil
}

func build(goos string, goarch string) error {
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)

cmd := exec.Command(
"/usr/local/go/bin/go",
"build",
"-v",
"-o",
buildDir+"moonsla-"+goos+"_"+goarch,
)
return cmd.Run()
}

func main() {
for _, goos := range operatingSystems {
for _, goarch := range archs {
err := build(goos, goarch)
if err != nil {
log.Printf("Failed to build %s/%s with error: %v", goos, goarch, err)
os.Exit(1)
}
err = checksum(goos, goarch)
if err != nil {
log.Printf("Failed to generate checksums for %s/%s with error: %v", goos, goarch, err)
os.Exit(1)
}
}
}
}
143 changes: 143 additions & 0 deletions moonsla.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package main

import (
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"time"

"github.com/nlopes/slack"
)

func getChannels(api *slack.Client) (channels map[string]string) {
channels = make(map[string]string)
chans, _ := api.GetChannels(true)
for _, c := range chans {
channels[c.ID] = c.Name
}
return channels
}

func getUsers(api *slack.Client) (users map[string]string) {
users = make(map[string]string)
allUsers, _ := api.GetUsers()
for _, u := range allUsers {
users[u.ID] = u.RealName
}
return users
}

func getTimeStamp(ts string) (timeStamp time.Time) {
i, err := strconv.ParseInt(strings.Split(ts, ".")[0], 10, 64)
if err != nil {
panic(err)
}
timeStamp = time.Unix(i, 0)
return timeStamp
}

func formatMentions(msg string, users map[string]string) string {
re := regexp.MustCompile("<@U.*?>")
matches := re.FindAllString(msg, -1)
for _, m := range matches {
userID := m[2:(len(m) - 1)]
username, ok := users[userID]
if ok {
username = "@" + username
msg = strings.Replace(msg, m, username, -1)
}
}
return msg
}

func filterChannel(name string, channels map[string]string, whitelist []string) (whitelisted bool, cName string) {
whitelisted = false

cName, ok := channels[name]
if ok {
for _, w := range whitelist {
if cName == w {
whitelisted = true
}
}
} else {
whitelisted = true
cName = name
}

if len(whitelist) == 0 {
whitelisted = true
}

return whitelisted, cName
}

func main() {

slackToken, ok := os.LookupEnv("SLACK_TOKEN")
if !ok {
fmt.Println("Please set your SLACK_TOKEN")
}
api := slack.New(slackToken)

logger := log.New(os.Stdout, "slack-bot: ", log.Lshortfile|log.LstdFlags)
slack.SetLogger(logger)
api.SetDebug(false)

channels := getChannels(api)
fmt.Printf("Found %v channels\n", len(channels))

users := getUsers(api)
fmt.Printf("Found %v users\n", len(users))

rtm := api.NewRTM()
go rtm.ManageConnection()

whitelist := strings.Split(os.Getenv("SLACK_CHANNELS"), ",")
fmt.Printf("Channel whitelist: %v\n", whitelist)

for msg := range rtm.IncomingEvents {

switch ev := msg.Data.(type) {

case *slack.MessageEvent:

// Skip empty messages
if ev.Text == "" {
continue
}

whitelisted, cName := filterChannel(ev.Channel, channels, whitelist)
if !whitelisted {
continue
}

// Map the users ID to a username if it exists
uName, ok := users[ev.User]
if !ok {
uName = ev.User
}

t := getTimeStamp(ev.EventTimestamp)
timeStamp := fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second())

msg := formatMentions(ev.Text, users)

fmt.Printf("%v - %v - %v: %v\n", timeStamp, cName, uName, msg)

case *slack.RTMError:
fmt.Printf("Error: %s\n", ev.Error())

case *slack.InvalidAuthEvent:
fmt.Printf("Invalid credentials")
return

default:
// Ignore other events..
// fmt.Printf("Unexpected: %v\n", msg.Data)
}
}
}
Loading