// © 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"` }