├── .gitignore ├── plugins ├── nsfw │ ├── nsfw.jpg │ └── nsfw.go ├── reddit │ ├── reddit.png │ ├── README.md │ └── reddit.go ├── sing │ └── sing.go ├── troutslap │ └── troutslap.go ├── excuse │ └── excuse.go ├── uptime │ └── uptime.go ├── urbandictionary │ └── urbandictionary.go ├── soundcloud │ └── soundcloud.go ├── define │ └── define.go └── dellarism │ └── dellarism.go ├── server-files ├── supervisor.conf └── nginx.conf ├── sscaas └── sscaas.go ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | _site 3 | .idea -------------------------------------------------------------------------------- /plugins/nsfw/nsfw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsamuels/sscaas/HEAD/plugins/nsfw/nsfw.jpg -------------------------------------------------------------------------------- /plugins/reddit/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsamuels/sscaas/HEAD/plugins/reddit/reddit.png -------------------------------------------------------------------------------- /server-files/supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:sscaas] 2 | command = /var/www/go/bin/sscaas 3 | user = sscaas 4 | stdout_logfile = /var/log/gunicorn_supervisor.log 5 | redirect_stderr = true 6 | -------------------------------------------------------------------------------- /sscaas/sscaas.go: -------------------------------------------------------------------------------- 1 | package sscaas 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // Plugin format for all plugins. 8 | type Plugin interface { 9 | Run(http.ResponseWriter, *http.Request) (*PluginResponse, error) 10 | } 11 | 12 | // PluginResponse is structure to be returned from plugins. 13 | type PluginResponse struct { 14 | Username string 15 | Emoji string 16 | Text string 17 | UnfurlMedia bool 18 | UnfurlLinks bool 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slack Slash Commands as a Service 2 | 3 | Website: https://sscaas.eu/ 4 | 5 | ## Example package 6 | 7 | ``` go 8 | package example 9 | 10 | import ( 11 | "github.com/danielsamuels/sscaas/sscaas" 12 | ) 13 | 14 | type Plugin struct { 15 | Writer http.ResponseWriter 16 | Request *http.Request 17 | } 18 | 19 | func (p Plugin) Run(http.ResponseWriter, *http.Request) (*sscaas.PluginResponse, error) { 20 | return &sscaas.PluginResponse{ 21 | Username: "Example", 22 | Emoji: ":smile:", 23 | Text: "Hello world.", 24 | }, nil 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /plugins/reddit/README.md: -------------------------------------------------------------------------------- 1 | # Reddit plugin 2 | 3 | Get the meta-data of a subreddit. 4 | 5 | ## Configuration 6 | * Add an Incoming Webhook to your account: https://slack.com/services/new/incoming-webhook 7 | * Copy the Webhook URL 8 | 9 | * Add a Slash Command to your account: https://slack.com/services/new/slash-commands 10 | * Set the URL to: http://sscaas.eu/reddit/?callback=", baseData[0].Permalink_url, baseData[0].Title), 58 | UnfurlLinks: true, 59 | UnfurlMedia: true, 60 | }, nil 61 | } 62 | -------------------------------------------------------------------------------- /plugins/define/define.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/danielsamuels/sscaas/sscaas" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | ) 13 | 14 | type Plugin struct { 15 | Writer http.ResponseWriter 16 | Request *http.Request 17 | } 18 | 19 | func (p Plugin) Run(http.ResponseWriter, *http.Request) (*sscaas.PluginResponse, error) { 20 | text := "" 21 | userName := "" 22 | 23 | if p.Request.Method == "POST" { 24 | text = p.Request.Form.Get("text") 25 | userName = p.Request.Form.Get("user_name") 26 | } else { 27 | text = p.Request.URL.Query().Get("text") 28 | userName = p.Request.URL.Query().Get("user_name") 29 | } 30 | 31 | url := fmt.Sprintf( 32 | "http://api.wordnik.com/v4/word.json/%v/definitions?limit=1&includeRelated=false&useCanonical=false&includeTags=true&api_key=f8ab3913c02c28a5b8a4c086d3b072d3b92e4551e13f52d0f", 33 | url.QueryEscape(text), 34 | ) 35 | resp, err := http.Get(url) 36 | 37 | body, _ := ioutil.ReadAll(resp.Body) 38 | 39 | stringJSON := string(body[:]) 40 | stringJSON = stringJSON[1:] 41 | stringJSON = stringJSON[:len(stringJSON)-1] 42 | 43 | var baseData map[string]string 44 | json.Unmarshal([]byte(stringJSON), &baseData) 45 | 46 | if stringJSON == "" { 47 | return &sscaas.PluginResponse{}, errors.New("Word not found.") 48 | } 49 | 50 | if err == nil { 51 | returnString := fmt.Sprintf( 52 | "%v: %v - %v", 53 | userName, 54 | baseData["word"], 55 | baseData["text"], 56 | ) 57 | 58 | returnString = strings.Replace(returnString, "", "", -1) 59 | returnString = strings.Replace(returnString, "", "", -1) 60 | 61 | return &sscaas.PluginResponse{ 62 | Username: "Dictionary", 63 | Emoji: ":book:", 64 | Text: returnString, 65 | }, nil 66 | } 67 | 68 | return &sscaas.PluginResponse{ 69 | Username: "Dictionary", 70 | Emoji: ":book:", 71 | Text: "", 72 | UnfurlLinks: true, 73 | UnfurlMedia: true, 74 | }, nil 75 | } 76 | -------------------------------------------------------------------------------- /plugins/dellarism/dellarism.go: -------------------------------------------------------------------------------- 1 | package dellarism 2 | 3 | import ( 4 | "fmt" 5 | "github.com/danielsamuels/sscaas/sscaas" 6 | "math/rand" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type Plugin struct { 13 | Writer http.ResponseWriter 14 | Request *http.Request 15 | } 16 | 17 | func shuffle(arr []string) { 18 | rand.Seed(int64(time.Now().Nanosecond())) 19 | 20 | for i := len(arr) - 1; i > 0; i-- { 21 | j := rand.Intn(i) 22 | arr[i], arr[j] = arr[j], arr[i] 23 | } 24 | } 25 | 26 | func random(min, max int) int { 27 | rand.Seed(int64(time.Now().Nanosecond())) 28 | return rand.Intn(max-min) + min 29 | } 30 | 31 | func (p Plugin) Run(http.ResponseWriter, *http.Request) (*sscaas.PluginResponse, error) { 32 | userName := "" 33 | 34 | if p.Request.Method == "POST" { 35 | userName = p.Request.Form.Get("user_name") 36 | } else { 37 | userName = p.Request.URL.Query().Get("user_name") 38 | } 39 | 40 | generatedString := "" 41 | 42 | firstWord := []string{"Change", "Make", "Build", "Need", "Put"} 43 | secondWord := []string{"black", "social", "keyline", "content", "copy", "red", "scrolling", "footer", "header", "button", "navigation", "grid", "font", "spacing", "typekit", "website"} 44 | thirdWord := []string{"to", "from", "into", "into the", "under the", "on top of"} 45 | fourthWord := []string{"blue", "carousel", "section", "map", "paragraph", "ajax", "footer", "responsive", "packery", "python", "social"} 46 | 47 | shuffle(firstWord) 48 | shuffle(secondWord) 49 | shuffle(thirdWord) 50 | shuffle(fourthWord) 51 | 52 | generatedString = fmt.Sprintf("%v %v %v %v", firstWord[0], secondWord[0], thirdWord[0], fourthWord[0]) 53 | 54 | if random(1, 5) == 4 { 55 | generatedString = strings.ToUpper(generatedString) 56 | } 57 | 58 | return &sscaas.PluginResponse{ 59 | Username: "Dellarism", 60 | Emoji: ":dellar:", 61 | Text: fmt.Sprintf( 62 | "%v, your Dellarism is: %v", 63 | userName, 64 | generatedString, 65 | ), 66 | UnfurlLinks: true, 67 | UnfurlMedia: true, 68 | }, nil 69 | } 70 | -------------------------------------------------------------------------------- /plugins/reddit/reddit.go: -------------------------------------------------------------------------------- 1 | package reddit 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/danielsamuels/sscaas/sscaas" 8 | "io/ioutil" 9 | "net/http" 10 | ) 11 | 12 | type Plugin struct { 13 | Writer http.ResponseWriter 14 | Request *http.Request 15 | } 16 | 17 | func (p Plugin) Run(http.ResponseWriter, *http.Request) (*sscaas.PluginResponse, error) { 18 | text := "" 19 | userName := "" 20 | 21 | if p.Request.Method == "POST" { 22 | text = p.Request.Form.Get("text") 23 | userName = p.Request.Form.Get("user_name") 24 | } else { 25 | text = p.Request.URL.Query().Get("text") 26 | userName = p.Request.URL.Query().Get("user_name") 27 | } 28 | 29 | if len(text) == 0 { 30 | http.Error(p.Writer, "No subreddit supplied.", http.StatusBadRequest) 31 | return &sscaas.PluginResponse{}, errors.New("No subreddit supplied.") 32 | } 33 | 34 | url := fmt.Sprintf("http://www.reddit.com/r/%s/about.json", text) 35 | 36 | client := &http.Client{} 37 | 38 | req, err := http.NewRequest("GET", url, nil) 39 | req.Header.Add("User-Agent", "Slack slash command") 40 | resp, err := client.Do(req) 41 | 42 | if err != nil || resp.StatusCode != 200 { 43 | if resp.StatusCode == 404 { 44 | http.Error(p.Writer, "That subreddit does not exist.", http.StatusNotFound) 45 | return &sscaas.PluginResponse{}, errors.New("That subreddit does not exist.") 46 | } 47 | 48 | return &sscaas.PluginResponse{}, errors.New("There was an error with the request.") 49 | } 50 | 51 | body, err := ioutil.ReadAll(resp.Body) 52 | 53 | if err != nil { 54 | return &sscaas.PluginResponse{}, err 55 | } 56 | 57 | var baseData map[string]interface{} 58 | json.Unmarshal([]byte(body), &baseData) 59 | dataKey := baseData["data"] 60 | 61 | data := dataKey.(map[string]interface{}) 62 | nsfw := "" 63 | 64 | if data["over18"] == true { 65 | nsfw = "(NSFW)" 66 | } 67 | 68 | returnString := fmt.Sprintf( 69 | "%v - %v (%v): http://www.reddit.com/r/%v %v", 70 | userName, 71 | data["display_name"], 72 | data["title"], 73 | text, 74 | nsfw, 75 | ) 76 | 77 | return &sscaas.PluginResponse{ 78 | Username: "Reddit", 79 | Emoji: ":reddit:", 80 | Text: returnString, 81 | UnfurlLinks: true, 82 | UnfurlMedia: true, 83 | }, nil 84 | } 85 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/danielsamuels/sscaas/sscaas" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/danielsamuels/sscaas/plugins/define" 15 | "github.com/danielsamuels/sscaas/plugins/dellarism" 16 | "github.com/danielsamuels/sscaas/plugins/excuse" 17 | "github.com/danielsamuels/sscaas/plugins/reddit" 18 | "github.com/danielsamuels/sscaas/plugins/sing" 19 | "github.com/danielsamuels/sscaas/plugins/soundcloud" 20 | "github.com/danielsamuels/sscaas/plugins/troutslap" 21 | "github.com/danielsamuels/sscaas/plugins/uptime" 22 | "github.com/danielsamuels/sscaas/plugins/urbandictionary" 23 | "github.com/danielsamuels/sscaas/plugins/nsfw" 24 | ) 25 | 26 | func logRequest(w http.ResponseWriter, r *http.Request, contentLength string, statusCode int) { 27 | fmt.Printf( 28 | "%v - %v - [%v] \"%v %v %v\" %v -\n", 29 | r.RemoteAddr, 30 | contentLength, 31 | time.Now().Format("2/Jan/2006 15:04:05"), 32 | r.Method, 33 | r.URL.String(), 34 | r.Proto, 35 | statusCode, 36 | ) 37 | } 38 | 39 | type responsePayload struct { 40 | Channel string `json:"channel"` 41 | Username string `json:"username"` 42 | IconEmoji string `json:"icon_emoji"` 43 | Text string `json:"text"` 44 | UnfurlMedia bool `json:"unfurl_media"` 45 | UnfurlLinks bool `json:"unfurl_links"` 46 | } 47 | 48 | func main() { 49 | port := 8080 50 | 51 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 52 | statusCode := 200 53 | contentLength := "" 54 | channelID := "" 55 | callback := "" 56 | 57 | if r.Method == "POST" { 58 | r.ParseForm() 59 | channelID = r.Form.Get("channel_id") 60 | callback = r.Form.Get("callback") 61 | } else { 62 | channelID = r.URL.Query().Get("channel_id") 63 | callback = r.URL.Query().Get("callback") 64 | } 65 | 66 | if len(channelID) == 0 { 67 | errorText := "No channel supplied." 68 | http.Error(w, errorText, http.StatusBadRequest) 69 | statusCode = 400 70 | contentLength = strconv.Itoa(len(errorText)) 71 | } else if len(callback) == 0 { 72 | errorText := "No callback supplied." 73 | http.Error(w, errorText, http.StatusBadRequest) 74 | statusCode = 400 75 | contentLength = strconv.Itoa(len(errorText)) 76 | } else { 77 | parts := strings.Split(r.URL.Path[1:], "/") 78 | key := parts[0] 79 | 80 | var plugin sscaas.Plugin 81 | 82 | // Plugin definitions.. 83 | // TODO: Make some sort of plugin registration system? 84 | switch key { 85 | case "reddit": 86 | plugin = reddit.Plugin{w, r} 87 | case "urbandictionary": 88 | plugin = urbandictionary.Plugin{w, r} 89 | case "dellarism": 90 | plugin = dellarism.Plugin{w, r} 91 | case "define": 92 | plugin = define.Plugin{w, r} 93 | case "excuse": 94 | plugin = excuse.Plugin{w, r} 95 | case "sing": 96 | plugin = sing.Plugin{w, r} 97 | case "soundcloud": 98 | plugin = soundcloud.Plugin{w, r} 99 | case "troutslap": 100 | plugin = troutslap.Plugin{w, r} 101 | case "uptime": 102 | plugin = uptime.Plugin{w, r} 103 | case "nsfw": 104 | plugin = nsfw.Plugin{w, r} 105 | } 106 | 107 | if plugin != nil { 108 | res, err := sscaas.Plugin(plugin).Run(w, r) 109 | 110 | if err == nil { 111 | // Create the JSON payload. 112 | responsePayload := &responsePayload{ 113 | Channel: channelID, 114 | Username: res.Username, 115 | IconEmoji: res.Emoji, 116 | Text: res.Text, 117 | UnfurlMedia: res.UnfurlMedia, 118 | UnfurlLinks: res.UnfurlLinks, 119 | } 120 | 121 | responsePayloadJSON, _ := json.Marshal(responsePayload) 122 | stringJSON := string(responsePayloadJSON[:]) 123 | 124 | // Make the request to the Slack API. 125 | http.PostForm(callback, url.Values{"payload": {stringJSON}}) 126 | } else { 127 | http.Error(w, err.Error(), 200) 128 | } 129 | } else { 130 | errorText := "Sorry, it was not possible to load that plugin." 131 | http.Error(w, errorText, http.StatusNotFound) 132 | statusCode = 404 133 | contentLength = strconv.Itoa(len(errorText)) 134 | } 135 | } 136 | 137 | logRequest(w, r, contentLength, statusCode) 138 | }) 139 | 140 | fmt.Printf("Running server on port %d..\n", port) 141 | log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) 142 | } 143 | --------------------------------------------------------------------------------