Skip to content

Instantly share code, notes, and snippets.

@jackcarter
Last active November 29, 2023 07:03
Show Gist options
  • Save jackcarter/d86808449f0d95060a40 to your computer and use it in GitHub Desktop.
Save jackcarter/d86808449f0d95060a40 to your computer and use it in GitHub Desktop.
Delete Slack files older than 30 days. Rewrite of https://gist.github.com/jamescmartinez/909401b19c0f779fc9c1
import requests
import time
import json
token = ''
#Delete files older than this:
ts_to = int(time.time()) - 30 * 24 * 60 * 60
def list_files():
params = {
'token': token
,'ts_to': ts_to
,'count': 1000
}
uri = 'https://slack.com/api/files.list'
response = requests.get(uri, params=params)
return json.loads(response.text)['files']
def delete_files(file_ids):
count = 0
num_files = len(file_ids)
for file_id in file_ids:
count = count + 1
params = {
'token': token
,'file': file_id
}
uri = 'https://slack.com/api/files.delete'
response = requests.get(uri, params=params)
print count, "of", num_files, "-", file_id, json.loads(response.text)['ok']
files = list_files()
file_ids = [f['id'] for f in files]
delete_files(file_ids)
@katspaugh
Copy link

I've modified the script a bit to avoid external dependencies:

import urllib
import urllib2
import time
import json

token = ''

#Delete files older than this:
days = 30
ts_to = int(time.time()) - days * 24 * 60 * 60

def list_files():
  params = {
    'token': token,
    'ts_to': ts_to,
    'count': 1000,
  }
  uri = 'https://slack.com/api/files.list'
  response = urllib2.urlopen(uri + '?' + urllib.urlencode(params)).read()
  return json.loads(response)['files']

def delete_files(file_ids):
  count = 0
  num_files = len(file_ids)
  for file_id in file_ids:
    count = count + 1
    params = {
      'token': token
      ,'file': file_id
      }
    uri = 'https://slack.com/api/files.delete'
    response = urllib2.urlopen(uri + '?' + urllib.urlencode(params)).read()
    print count, "of", num_files, "-", file_id, json.loads(response)['ok']

files = list_files()
file_ids = [f['id'] for f in files]
delete_files(file_ids)

@lyrixx
Copy link

lyrixx commented Aug 2, 2016

And the PHP version

@Paradoxis
Copy link

Paradoxis commented Aug 3, 2016

Refactored the code to make it easier to use (fork)
Usage: python slack_delete.py --token <token here>

@kentwongg
Copy link

This doesn't work on python 3.5 does it? I keep getting module/library issues.

@cosmonauta
Copy link

@kkreezy @DrOverdose I've modified the script to python3

from urllib.parse import urlencode
from urllib.request import urlopen
import time
import json
import codecs

reader = codecs.getreader("utf-8")

token = ''

#Delete files older than this:
days = 30
ts_to = int(time.time()) - days * 24 * 60 * 60

def list_files():
  params = {
    'token': token,
    'ts_to': ts_to,
    'count': 1000,
  }
  uri = 'https://slack.com/api/files.list'
  response = reader(urlopen(uri + '?' + urlencode(params)))
  return json.load(response)['files']

def delete_files(file_ids):
  count = 0
  num_files = len(file_ids)
  for file_id in file_ids:
    count = count + 1
    params = {
      'token': token
      ,'file': file_id
      }
    uri = 'https://slack.com/api/files.delete'
    response = reader(urlopen(uri + '?' + urlencode(params)))
    print(count, "of", num_files, "-", file_id, json.load(response)['ok'])

files = list_files()
file_ids = [f['id'] for f in files]
delete_files(file_ids)

@something915
Copy link

Does this delete all file types? if so is there a way to only have it delete images?

@darieldejesus
Copy link

@something915 This delete all file types. So far there is not filter but it can be implemented.

@mcgoughp0870
Copy link

So I am not a computer person at all - is there an easy way that i can implement this into slack? I have no idea what i am doing

@DanielMT57
Copy link

Hey!, we had some problem at work with our files, so we were told to delete them, i had a lot so i had to find something, and google took me here. Thank you very much, it worked like a charm.

@Siludorf
Copy link

Is it appropriate to ask how this code works? If so, how does it work? If not, thank you for the amazing code!

@thamaraiselvam
Copy link

Good job @jackcarter

@savage4618
Copy link

does this work on files that might be in Private channels?

@henryngo
Copy link

Can I put tokens from multiple users?

@henry-p
Copy link

henry-p commented Nov 30, 2017

@cosmonauta

I extended the script to be filterable by file size and filetype. I also added a info method that returns the most relevant info and refactored the code a little bit :)

from urllib.parse import urlencode
from urllib.request import urlopen
import time
import json
import codecs
import datetime
from collections import OrderedDict

reader = codecs.getreader("utf-8")

# Obtain here: https://api.slack.com/custom-integrations/legacy-tokens
token = ''

# Params for file listing. More info here: https://api.slack.com/methods/files.list

# Delete files older than this:
days = 30
ts_to = int(time.time()) - days * 24 * 60 * 60

# How many? (Maximum is 1000, otherwise it defaults to 100)
count = 1000

# Types?
types = 'all'
# types = 'spaces,snippets,images,gdocs,zips,pdfs'
# types = 'zips'


def list_files():
    params = {
        'token': token,
        'ts_to': ts_to,
        'count': count,
        'types': types
    }
    uri = 'https://slack.com/api/files.list'
    response = reader(urlopen(uri + '?' + urlencode(params)))
    return json.load(response)['files']


def filter_by_size(files, mb, greater_or_smaller):
    if greater_or_smaller == 'greater':
        return [file for file in files if (file['size'] / 1000000) > mb]
    elif greater_or_smaller == 'smaller':
        return [file for file in files if (file['size'] / 1000000) < mb]
    else:
        return None


def info(file):
    order = ['Title', 'Name', 'Created', 'Size', 'Filetype',
             'Comment', 'Permalink', 'Download', 'User', 'Channels']
    info = {
        'Title': file['title'],
        'Name': file['name'],
        'Created': datetime.datetime.utcfromtimestamp(file['created']).strftime('%B %d, %Y %H:%M:%S'),
        'Size': str(file['size'] / 1000000) + ' MB',
        'Filetype': file['filetype'],
        'Comment': file['initial_comment'] if 'initial_comment' in file else '',
        'Permalink': file['permalink'],
        'Download': file['url_private'],
        'User': file['user'],
        'Channels': file['channels']
    }
    return OrderedDict((key, info[key]) for key in order)


def file_ids(files):
    return [f['id'] for f in files]


def delete_files(file_ids):
    num_files = len(file_ids)
    for index, file_id in enumerate(file_ids):
        params = {
            'token': token,
            'file': file_id
        }
        uri = 'https://slack.com/api/files.delete'
        response = reader(urlopen(uri + '?' + urlencode(params)))
        print((index + 1, "of", num_files, "-",
               file_id, json.load(response)['ok']))

files = list_files()
files_by_size = filter_by_size(files, 50, 'greater')
print(len(files_by_size))
[info(file) for file in files_by_size]
file_ids = file_ids(files_by_size)
# delete_files(file_ids) # Commented out, so you don't accidentally run this.

@MattHofmann
Copy link

Thanks! @jackcarter

@WopKatan
Copy link

awesome work. thanks 👍

@agaltsoff
Copy link

agaltsoff commented Apr 18, 2018

Added an option to retrieve files by slack member id. Handy 'cause if not admin you retrieve all files but can delete only yours.

Plus some refactoring as well.

from urllib.parse import urlencode
from urllib.request import urlopen
import time
import json
import codecs
import datetime
from collections import OrderedDict

reader = codecs.getreader("utf-8")

# Obtain here: https://api.slack.com/custom-integrations/legacy-tokens
token = '' 

# Set it to delete only this user's files. Handy if you are not admin.
member_id= ''

# Params for file listing. More info here: https://api.slack.com/methods/files.list

# Delete files older than this:
days = 30
ts_to = int(time.time()) - days * 24 * 60 * 60

# How many? (Maximum is 1000, otherwise it defaults to 100)
count = 1000

# Types?
types = 'all'
# types = 'spaces,snippets,images,gdocs,zips,pdfs'
# types = 'zips'

def list_files(user= ''):
    params = {
        'token': token,
        'ts_to': ts_to,
        'count': count,
        'types': types,
        'user': user,
    }
    uri = 'https://slack.com/api/files.list'
    response = reader(urlopen(uri + '?' + urlencode(params)))
    return json.load(response)['files']

def greater_mb(file, mb):
    return file['size'] / 1000000 >= mb

def smaller_mb(file, mb):
    return file['size'] / 1000000 < mb

def filter_by_size(files, greater_or_smaller, mb):
    return [file for file in files if greater_or_smaller(file, mb)]

def info(file):
    order = ['Title', 'Name', 'Created', 'Size', 'Filetype',
             'Comment', 'Permalink', 'Download', 'User', 'Channels']
    info = {
        'Title': file['title'],
        'Name': file['name'],
        'Created': datetime.datetime.utcfromtimestamp(file['created']).strftime('%B %d, %Y %H:%M:%S'),
        'Size': str(file['size'] / 1000000) + ' MB',
        'Filetype': file['filetype'],
        'Comment': file['initial_comment'] if 'initial_comment' in file else '',
        'Permalink': file['permalink'],
        'Download': file['url_private'],
        'User': file['user'],
        'Channels': file['channels']
    }
    return OrderedDict((key, info[key]) for key in order)

def delete_files(files):
    num_files = len(files)
    file_ids = map(lambda f: f['id'], files)
    print('Deleting %i files'%num_files)
    for index, file_id in enumerate(file_ids):
        params = {
            'token': token,
            'file': file_id
        }
        uri = 'https://slack.com/api/files.delete'
        response = reader(urlopen(uri + '?' + urlencode(params)))
        print((index + 1, "of", num_files, "-",
               file_id, json.load(response)['ok']))

print('Retrieving files older than %s days'%(days))			   
			   
files = list_files(member_id)

print('Total %i files'%len(files))

files = filter_by_size(files, greater_mb, 50)

print('Match size %i files'%len(files))

#delete_files(files) # Commented out, so you don't accidentally run this.

@jamiesphinx
Copy link

Thank you @jackcarter. This works like a charm! I have been running https://github.com/PalmBeachPost/SlackPruner until for some reason, the script exits after deleting just a couple of files from each user.
Query: how can I delete files of multiple users? I am admin on Slack and have their tokens.

@alpinstang
Copy link

@jamiesphinx If you are an admin and use a legacy API key from Slack you will be able to delete all users files. https://api.slack.com/custom-integrations/legacy-tokens

@maltose1117
Copy link

Thanks @jackcarter . However, after execute the code, it shows:
1 of 510 - F1KE8KE9H False
2 of 510 - F1KFF9QFQ False
3 of 510 - F1KFF9QJ2 False
.
.
and so on. After running the code, I checked my Slack files. Many of them as expected are not deleted. May I ask what is going on here? Thank you.

Copy link

ghost commented Jan 22, 2019

@maltose1117 I got the same error. I keep executing the code (python3 slack_delete.py) until it's all deleted.

@ZachBania
Copy link

Hey @alpinstang - @pravashkarki, I had the same problem.
It's because the slack API is requesting for all files within a private group. If your not admin, the request fails due to authorization.
Check out @agaltsoff's script above in the comments, worked for me.

@amcguireweb
Copy link

I just get a bunch of exceptions when attempting to use @agaltsoff's script above. Admittedly I really have no idea what I'm doing, but the original script runs for me. Just not deleting all the files.

@JeffHanna
Copy link

JeffHanna commented Feb 20, 2019

For all of this refactoring why does everyone keep the uri = 'https://slack.com/api/files.delete' line under for loop? Its better placed outside of the loop so Python doesn't keep allocating memory for the same static string.

@jindov
Copy link

jindov commented Mar 4, 2019

I update this script because I want to delete file by id, this will handle case can't not delete because the file is not belong to another user and workaround ratelimit of Slack's API. With some default value as count=1000. You have to get a list of id, token, username to fill in the users list. Run with command python slack_delete.py --days 30. Note python 2.7

import argparse
import requests
import time
import json
requests.packages.urllib3.disable_warnings()

users = [{"name": "myName", "user":"UDPKXJ8JU", "token": "xoxp-xxxxxxxxxx"}]

def main():
    """
    Entry point of the application
    :return: void
    """
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--days", type=int, default=None, help="Delete files older than x days (optional)")
    options = parser.parse_args()

    try:
        print "[*] Fetching file list.."
        file_ids = list_file_ids(days=options.days)
    except KeyboardInterrupt:
        print "\b\b[-] Aborted"
        exit(1)

def calculate_days(days):
    """
    Calculate days to unix time
    :param days: int
    :return: int
    """
    return int(time.time()) - days * 24 * 60 * 60

def list_file_ids(days=None):
    for user in users:
        user_name = user['name']
        print "delete files of user: "+user_name
        user_id = user['user']
        user_token = user['token']
        if days:
            params = {'token': user_token, 'count': 1000, 'ts_to': calculate_days(days), 'user': user_id}
            print "ts_to: ", calculate_days(days)
        else:
            params = {'token': user_token, 'count': 1000, 'user': user_id}
        uri = 'https://slack.com/api/files.list'
        response = requests.get(uri, params=params).json()
        files = response['files']
        #return [f['id'] for f in files]
        print [f['id'] for f in files]
        for f in files:
            dresponse = json.loads(requests.get('https://slack.com/api/files.delete', params={'token': user_token, 'file': f['id']}).text)
            time.sleep(2)
            if dresponse["ok"]:
                print "[+] Deleted: ", f['id']
            else:
                print "[!] Unable to delete: ", f['id'] + ", reason:", dresponse["error"]

if __name__ == '__main__':
    main()

@Redsandro
Copy link

api.slack.com is a maze. I've tried so many tokens. Where exactly do I get the proper token? Or this user and token combination?

@Bears-Beets-BSG
Copy link

I'm not a programmer/coder, but I love tinkering with code to get stuff done more efficiently. Apologies in advance if anything below is wrong/misleading. I hope it helps others running into this problem.

We have long ago reached and passed the storage limit of our free Slack plan (>22GB). Naturally, older files were "tombstoned" or archived automatically once our storage usage passed 5.0GB. I've been trying to figure how to delete the tombstoned files using this script or its variations above (thank you for all the work, by the way!) to no avail. It seems I missed a little section in the Slack API documentation:

In order to gather information on tombstoned files in Free workspaces, so that you can delete or revoke them, pass the show_files_hidden_by_limit parameter. While the yielded files will still be redacted, you'll gain the id of the files so that you can delete or revoke them.

In @jackcarter's original code at the top, which worked fine for me except for the "hidden" files, I added the missing parameter as below. (I also used a workspace owner legacy token.)

def list_files():
   params = {
    'token': token,  
    'ts_to': ts_to,  
    'count': 1000,  
    'show_files_hidden_by_limit': 1,  
}

It seems to have worked. I've deleted thousands more files older than X days. Slack analytics also showed a significant decrease in storage usage. Unfortunately, and as confirmed with Slack Support, (with a Free plan) we are unable to see or delete files shared by users in private channels or direct messages, even through the API.

Anyway, hope this still helps someone.

@IlanVivanco
Copy link

As of today, legacy tester tokens may no longer be created.
However, you can still use this script following this steps:

  1. Create a Slack app on https://api.slack.com/apps?new_app=1 and assign that to your workspace.

  2. Then within this new app, go to OAuth & Permissions and in the section Scopes > User Token Scopes add the files:read and files:write Oauth scopes.

  3. Finally, you can now go to the top of the page and install the app to the workspace. After that you'll get the User OAuth Token that you can use on the script.

* Bare in mind that if you forget to add the scopes, or you need to modify them after you install the app to the workspace, you would need to reinstall it after changing the scopes.

I have also adapted the code to work with the Oauth authentication.
https://gist.github.com/IlanVivanco/d2a96abb364ccb3b59e198f1c5fdf673

@dengwt
Copy link

dengwt commented Jul 13, 2021

@IlanVivanco good job! saved my time.

@dgarciam
Copy link

None of the scripts listed here works anymore for me. Something changed @slack?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment