package main import ( "encoding/json" "flag" "io" "io/ioutil" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) var ( token = flag.String("token", "", "Slack API token") channel = flag.String("channel", "", "Slack channel ID") limit = flag.Int("limit", 10000, "Number of items to return") types = flag.String("types", "images,pdfs", "Type of object") path = flag.String("path", "./", "Path to save files") ) func main() { flag.Parse() log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) for { files, count := getFiles() for _, f := range files { downloadFile(f) deleteFile(f) } if count == 1 { break } } } func getFiles() ([]slackFile, int) { files := fileResp{} log.Debug().Msg("Getting files") body := mkReq("https://slack.com/api/files.list", "token", *token, "count", strconv.Itoa(*limit), "types", *types, ) err := json.Unmarshal(body, &files) checkErr(err) log.Info(). Int("count", files.Paging.Count). Bool("ok", files.Ok) if !files.Ok { log.Error().Interface("files", files) } return files.Files, files.Paging.Pages } func deleteFile(f slackFile) { body := mkReq("https://slack.com/api/files.delete", "token", *token, "file", f.ID, ) del := delResp{} err := json.Unmarshal(body, &del) checkErr(err) if !del.Ok { log.Fatal(). Bytes("body", body). Str("id", f.ID). Msg("Couldn't delete") } log.Info(). Str("id", f.ID). Msg("Deleted") } func downloadFile(f slackFile) { url := strings.Replace(f.URLPrivateDownload, "\\", "", -1) // because fuck slack fname := filepath.Join(*path, f.ID+f.Name) log.Info(). Str("url", url). Msg("Downloading") client := &http.Client{} req, err := http.NewRequest("GET", url, nil) checkErr(err) req.Header.Add("Authorization", "Bearer "+*token) resp, err := client.Do(req) checkErr(err) defer resp.Body.Close() out, err := os.Create(fname) checkErr(err) defer out.Close() io.Copy(out, resp.Body) log.Info(). Str("id", f.ID). Msg("Downloaded") } func checkErr(err error) { if err != nil { log.Fatal().Err(err) } } func mkReq(path string, arg ...string) []byte { if len(arg)%2 != 0 { log.Fatal().Msg("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 } type delResp struct { Ok bool } type fileResp struct { Ok bool `json:"ok"` Files []slackFile `json:"files"` Paging struct { Count int `json:"count"` Total int `json:"total"` Page int `json:"page"` Pages int `json:"pages"` } `json:"paging"` } type slackFile struct { ID string `json:"id"` Created int `json:"created"` Timestamp int `json:"timestamp"` Name string `json:"name"` Title string `json:"title"` Mimetype string `json:"mimetype"` Filetype string `json:"filetype"` PrettyType string `json:"pretty_type"` User string `json:"user"` Editable bool `json:"editable"` Size int `json:"size"` Mode string `json:"mode"` IsExternal bool `json:"is_external"` ExternalType string `json:"external_type"` IsPublic bool `json:"is_public"` PublicURLShared bool `json:"public_url_shared"` DisplayAsBot bool `json:"display_as_bot"` Username string `json:"username"` URLPrivate string `json:"url_private"` URLPrivateDownload string `json:"url_private_download"` Thumb64 string `json:"thumb_64"` Thumb80 string `json:"thumb_80"` Thumb360 string `json:"thumb_360"` Thumb360W int `json:"thumb_360_w"` Thumb360H int `json:"thumb_360_h"` Thumb480 string `json:"thumb_480,omitempty"` Thumb480W int `json:"thumb_480_w,omitempty"` Thumb480H int `json:"thumb_480_h,omitempty"` Thumb160 string `json:"thumb_160"` Thumb720 string `json:"thumb_720,omitempty"` Thumb720W int `json:"thumb_720_w,omitempty"` Thumb720H int `json:"thumb_720_h,omitempty"` Thumb800 string `json:"thumb_800,omitempty"` Thumb800W int `json:"thumb_800_w,omitempty"` Thumb800H int `json:"thumb_800_h,omitempty"` Thumb960 string `json:"thumb_960,omitempty"` Thumb960W int `json:"thumb_960_w,omitempty"` Thumb960H int `json:"thumb_960_h,omitempty"` Thumb1024 string `json:"thumb_1024,omitempty"` Thumb1024W int `json:"thumb_1024_w,omitempty"` Thumb1024H int `json:"thumb_1024_h,omitempty"` ImageExifRotation int `json:"image_exif_rotation"` OriginalW int `json:"original_w"` OriginalH int `json:"original_h"` Permalink string `json:"permalink"` PermalinkPublic string `json:"permalink_public"` Channels []string `json:"channels"` Groups []interface{} `json:"groups"` Ims []interface{} `json:"ims"` CommentsCount int `json:"comments_count"` InitialComment struct { ID string `json:"id"` Created int `json:"created"` Timestamp int `json:"timestamp"` User string `json:"user"` IsIntro bool `json:"is_intro"` Comment string `json:"comment"` } `json:"initial_comment,omitempty"` Reactions []struct { Name string `json:"name"` Users []string `json:"users"` Count int `json:"count"` } `json:"reactions,omitempty"` }