Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
@Grapes([
@Grab(group='com.squareup.retrofit', module='retrofit', version='1.9.0')
])
import retrofit.http.*
import retrofit.RestAdapter
import java.time.ZoneOffset
import java.time.LocalDateTime
import java.text.NumberFormat
class CleanSlack {
static final String TEAM = 'zirbes' // your Slack team
static final String API_TOKEN = 'YOUR_SLACK_API_TOKEN'
static final Integer PAGE_SIZE = 50
static final LocalDateTime DELETE_THRESHOLD = LocalDateTime.now().minusDays(90)
SlackApi slackApi = new RestAdapter.Builder().setEndpoint('https://slack.com/api').build().create(SlackApi)
// static final Long MAX_SPACE = 1000000000 // 1 GB
static final Long MAX_SPACE = 750000000 // 750 MB
static void main(String ... args) {
CleanSlack cleaner = new CleanSlack()
cleaner.clean()
}
void clean() {
println "# Cleaning up Slack file attachments.\n"
Integer totalFiles
List<SlackFile> files = getFiles(users, channels, totalFiles)
Long totalSize = calculateTotalSize(files)
Long spaceToClean = calculateSpaceToClean(totalSize)
List<SlackFile> filesToRemove = findOldest(files, spaceToClean)
List<SlackFile> sortedBySize = files.sort { it.size * -1 }
Long cleanTotal = filesToRemove*.size.sum() ?: 0
println "## ${filesToRemove.size()}/${totalFiles} files to remove (${format(cleanTotal)} bytes):\n"
filesToRemove.each { SlackFile file -> println " * ${file}" }
println "## ${filesToRemove.size()}/${totalFiles} files to remove (${format(cleanTotal)}/${format(totalSize)} bytes)."
filesToRemove.each { SlackFile file ->
println " ! Deleting ${file.id}, ${file}"
slackApi.fileDelete(API_TOKEN, file.id)
}
println "## Top 20 files remaining\n"
sortedBySize[0..19].each { SlackFile file -> println " * ${file}" }
}
protected Long calculateSpaceToClean(Long totalSize) {
Long spaceToClean = 0
if (totalSize > MAX_SPACE) { spaceToClean = totalSize - MAX_SPACE }
println "Space to Clean up: ${format(spaceToClean)}"
return spaceToClean
}
protected Map<String, SlackUser> getUsers() {
println "## Scanning users\n"
UserList userList = slackApi.userList(token: API_TOKEN)
return userList.members.collectEntries { [ (it.id): it ] }
}
protected Map<String, SlackChannel> getChannels() {
println "## Scanning channels\n"
ChannelList channelList = slackApi.channelList(token: API_TOKEN)
return channelList.channels.collectEntries { [ (it.id): it ] }
}
protected Long calculateTotalSize(Collection<SlackFile> files) {
Long totalSize = 0
println "## Files by Size:\n"
files.sort { it.size }.each { SlackFile file ->
totalSize += file.size
println " * ${file}"
}
println "Total Size: ${format(totalSize)}"
return totalSize
}
protected Collection<SlackFile> findOldest(Collection<SlackFile> files, Long spaceToClean) {
List<SlackFile> filesToRemove = []
Long deleted = 0
List<SlackFile> sortedByDate = files.sort { it.created * -1 }
sortedByDate.each { SlackFile file ->
if (deleted < spaceToClean && file.createdDate < DELETE_THRESHOLD) {
filesToRemove << file
deleted += file.size
}
}
return filesToRemove.sort { it.created }
}
protected Collection<SlackFile> getFiles(def users, def channels, Integer totalFiles) {
println "## Scanning all files for details\n"
Integer page = 0
Integer pages = null
List<SlackFile> files = []
Integer fileNo = 0
while (!pages || page < pages) {
Map<String, String> params = [ token: API_TOKEN, count: PAGE_SIZE, page: page ]
FileList resp = slackApi.fileList(params)
pages = resp.paging.pages
totalFiles = resp.paging.total
resp.files.each { SlackFile file ->
fileNo++
file.username = users[file.user].name
file.channels = file.channels.collect{ channels[it].name }
files << file
println " * Scanning [pg${page}/${pages}, ${fileNo}/${totalFiles}] ${file}"
}
page++
}
return files
}
protected String format(Long number) {
NumberFormat.getInstance().format(number)
}
}
class SlackResponse {
Boolean ok
String warning
String error
}
class FileList extends SlackResponse {
Paging paging
List<SlackFile> files
}
class UserList extends SlackResponse {
List<SlackUser> members
}
class ChannelList extends SlackResponse {
List<SlackChannel> channels
}
class Paging {
Integer count
Integer total
Integer page
Integer pages
}
class SlackUser {
String id
String name
}
class SlackChannel {
String id
String name
Boolean is_archived
}
class SlackFile {
String id
Integer created
Integer timestamp
LocalDateTime getCreatedDate() {
LocalDateTime.ofEpochSecond(created, 0, ZoneOffset.UTC)
}
LocalDateTime getTimestampDate() {
LocalDateTime.ofEpochSecond(timestamp, 0, ZoneOffset.UTC)
}
String name
String title
String mimetype
String filetype
String pretty_type
String user
String mode
Boolean editable
Boolean is_external
String external_type
String username
Long size
URL url_private
URL url_private_download
URL permalink
URL permalink_public
URL edit_link
String preview
String preview_highlight
Integer lines
Integer lines_more
Boolean is_public
Boolean public_url_shared
Boolean display_as_bot
List<String> channels
List<String> groups
List<String> ims
Integer num_stars
Boolean is_starred
List<String> pinned_to
Integer comments_count
String getSizeString() {
NumberFormat.getInstance().format(size)
}
String getChannel() {
if (channels) {
return "#${channels[0]}"
}
return null
}
String toString() {
"${name} (${sizeString} bytes) - Uploaded ${createdDate} by @${username} ${channel ? 'in ' : ''}${channel}"
}
}
protected interface SlackApi {
@Headers(['Accept: application/json'])
@GET('/channels.list')
ChannelList channelList(@QueryMap Map<String, String> params)
@Headers(['Accept: application/json'])
@GET('/files.list')
FileList fileList(@QueryMap Map<String, String> params)
@Headers(['Accept: application/json'])
@GET('/users.list')
UserList userList(@QueryMap Map<String, String> params)
@Headers(['Accept: application/json'])
@DELETE('/files.delete')
SlackResponse fileDelete(@Query('token') String token, @Query('file') String id)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.