From 88e73ca72655601d4596b248921335c91bf5adc7 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 29 Jun 2018 10:42:56 -0400 Subject: [PATCH] initial commit --- AUTHORS | 1 + LICENSE | 14 +++ emojytool/emojytool.go | 57 ++++++++++ gethistory/gethistory.go | 220 +++++++++++++++++++++++++++++++++++++++ readme.adoc | 14 +++ 5 files changed, 306 insertions(+) create mode 100755 AUTHORS create mode 100755 LICENSE create mode 100644 emojytool/emojytool.go create mode 100644 gethistory/gethistory.go create mode 100644 readme.adoc diff --git a/AUTHORS b/AUTHORS new file mode 100755 index 0000000..b3b7078 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Chris Sexton diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..5a8e332 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/emojytool/emojytool.go b/emojytool/emojytool.go new file mode 100644 index 0000000..e0958b7 --- /dev/null +++ b/emojytool/emojytool.go @@ -0,0 +1,57 @@ +// © 2018 the slacktricks Authors under the WTFPL license. See AUTHORS for the list of authors. + +package main + +import ( + "bufio" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + "strings" +) + +var dataDir = flag.String("data", "data", "Where to save emoji") + +type emojyResp struct { + Emoji map[string]string +} + +func main() { + flag.Parse() + f, err := ioutil.ReadAll(bufio.NewReader(os.Stdin)) + if err != nil { + panic(err) + } + + var resp emojyResp + if err := json.Unmarshal(f, &resp); err != nil { + panic(err) + } + + count := 0 + for k, v := range resp.Emoji { + if !strings.HasPrefix(v, "alias") { + ext := filepath.Ext(v) + resp, err := http.Get(v) + if err != nil { + panic(err) + } + fname := fmt.Sprintf("%s%s", k, ext) + f, err := os.Create(filepath.Join(*dataDir, fname)) + defer f.Close() + b, err := io.Copy(f, resp.Body) + if err != nil { + panic(err) + } + log.Printf("Saved %d bytes to %s", b, fname) + count++ + } + } + log.Printf("Saved %d emojys", count) +} diff --git a/gethistory/gethistory.go b/gethistory/gethistory.go new file mode 100644 index 0000000..0a5d20c --- /dev/null +++ b/gethistory/gethistory.go @@ -0,0 +1,220 @@ +// © 2018 the slacktricks Authors under the WTFPL license. See AUTHORS for the list of authors. + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strconv" +) + +var ( + token = flag.String("token", "", "Slack API token") + channel = flag.String("channel", "", "Slack channel ID") + limit = flag.Int("limit", 100, "Number of items to return") + byChannel = flag.Bool("byChannel", false, "Arrange data by channel (default by user)") +) + +func main() { + flag.Parse() + + users := getUsers() + history := getHistory(*channel) + + if !*byChannel { + for _, u := range users.Members { + writeUser(history, u) + } + } else { + writeChannel(history) + } +} + +func getUsers() userResp { + users := userResp{} + + log.Printf("Getting users") + body := mkReq("https://slack.com/api/users.list", + "token", *token, + ) + + err := json.Unmarshal(body, &users) + checkErr(err) + + return users +} + +func getHistory(ch string) []histResp { + history := []histResp{} + cursor := "" + for { + log.Printf("Getting history cursor: '%s'", cursor) + body := mkReq("https://slack.com/api/conversations.history", + "token", *token, + "channel", ch, + "limit", strconv.Itoa(*limit), + "cursor", cursor, + ) + resp := histResp{} + err := json.Unmarshal(body, &resp) + checkErr(err) + history = append(history, resp) + if resp.ResponseMetadata.NextCursor == "" { + break + } else { + cursor = resp.ResponseMetadata.NextCursor + } + } + + return history +} + +func checkErr(err error) { + if err != nil { + log.Fatal(err) + } +} + +func mkReq(path string, arg ...string) []byte { + if len(arg)%2 != 0 { + log.Fatal("Bad request arg number.") + } + + u, err := url.Parse(path) + checkErr(err) + + q := u.Query() + for i := 0; i < len(arg); i += 2 { + key := arg[i] + val := arg[i+1] + if val != "" { + q.Set(key, val) + } + } + u.RawQuery = q.Encode() + + resp, err := http.Get(u.String()) + checkErr(err) + + body, err := ioutil.ReadAll(resp.Body) + checkErr(err) + + return body +} + +func writeChannel(history []histResp) { + fname := *channel + ".json" + f, err := os.Create(fname) + checkErr(err) + defer f.Close() + + j, err := json.Marshal(history) + checkErr(err) + f.Write(j) + + reactionCount := map[string]int{} + for _, h := range history { + for _, m := range h.Messages { + if len(m.Reactions) > 0 { + for _, r := range m.Reactions { + reactionCount[r.Name] += r.Count + } + } + } + } + + fmt.Printf("%+v", reactionCount) +} + +func writeUser(history []histResp, u user) { + // open file and write shit out + fname := u.Name + ".txt" + id := u.ID + + f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE, 0755) + checkErr(err) + defer f.Close() + + for _, h := range history { + for _, m := range h.Messages { + if m.User == id { + fmt.Fprintf(f, "%s\n", m.Text) + } + } + } +} + +type histResp struct { + Ok bool `json:"ok"` + Messages []struct { + Type string `json:"type"` + User string `json:"user"` + Text string `json:"text"` + Ts string `json:"ts"` + Reactions []reaction `json:"reactions"` + } `json:"messages"` + HasMore bool `json:"has_more"` + PinCount int `json:"pin_count"` + ResponseMetadata struct { + NextCursor string `json:"next_cursor"` + } `json:"response_metadata"` +} + +type reaction struct { + Name string `json:"name"` + Users []string `json:"users"` + Count int `json:"count"` +} + +type user struct { + ID string `json:"id"` + TeamID string `json:"team_id"` + Name string `json:"name"` + Deleted bool `json:"deleted"` + Color string `json:"color"` + RealName string `json:"real_name"` + Tz string `json:"tz"` + TzLabel string `json:"tz_label"` + TzOffset int `json:"tz_offset"` + Profile struct { + AvatarHash string `json:"avatar_hash"` + StatusText string `json:"status_text"` + StatusEmoji string `json:"status_emoji"` + RealName string `json:"real_name"` + DisplayName string `json:"display_name"` + RealNameNormalized string `json:"real_name_normalized"` + DisplayNameNormalized string `json:"display_name_normalized"` + Email string `json:"email"` + Image24 string `json:"image_24"` + Image32 string `json:"image_32"` + Image48 string `json:"image_48"` + Image72 string `json:"image_72"` + Image192 string `json:"image_192"` + Image512 string `json:"image_512"` + Team string `json:"team"` + } `json:"profile"` + IsAdmin bool `json:"is_admin"` + IsOwner bool `json:"is_owner"` + IsPrimaryOwner bool `json:"is_primary_owner"` + IsRestricted bool `json:"is_restricted"` + IsUltraRestricted bool `json:"is_ultra_restricted"` + IsBot bool `json:"is_bot"` + Updated int `json:"updated"` + IsAppUser bool `json:"is_app_user,omitempty"` + Has2Fa bool `json:"has_2fa"` +} + +type userResp struct { + Ok bool `json:"ok"` + Members []user `json:"members"` + CacheTs int `json:"cache_ts"` + ResponseMetadata struct { + NextCursor string `json:"next_cursor"` + } `json:"response_metadata"` +} diff --git a/readme.adoc b/readme.adoc new file mode 100644 index 0000000..33210a1 --- /dev/null +++ b/readme.adoc @@ -0,0 +1,14 @@ += Slack Tricks + +Just a collection of silly slack utils that have come in handy. + +== emojytool + +Back up your slack "emojy" as local image files. Suitable for mass exodus from +Slack. + +== gethistory + +Grab the available history from slack. This works by user for text hisory, +suitable for babbler purposes or by channel if more detailed information is +desired.