From a321f0887db74a8f64fef95356a74a44c397fcb7 Mon Sep 17 00:00:00 2001 From: shama_codesurfer Date: Sat, 25 Sep 2021 21:20:07 +0600 Subject: [PATCH 1/3] Slack channels endpoints deprecated in Feb, 2021; repalced by conversations endpoints --- README.md | 8 +++++--- slack_autoarchive.py | 46 +++++++++++++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1ec3374..f80dff5 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,11 @@ - An [OAuth token](https://api.slack.com/docs/oauth) from a [Slack app](https://api.slack.com/slack-apps) on your workspace that has the following permission scopes: - `channels:history` - `channels:read` - - `channels:write` - - `chat:write:bot` - - `chat:write:user` + - `channels:manage` + - `channels:join` + - `chat:write` + - `chat:write.customize` + - `chat:write.public` ## Example Usages diff --git a/slack_autoarchive.py b/slack_autoarchive.py index d35dd40..1d30b40 100755 --- a/slack_autoarchive.py +++ b/slack_autoarchive.py @@ -68,7 +68,7 @@ def slack_api_http( """ Helper function to query the slack api and handle errors and rate limit. """ # pylint: disable=no-member uri = 'https://slack.com/api/' + api_endpoint - payload['token'] = self.settings.get('slack_token') + headers = {'Authorization': 'Bearer ' + self.settings.get('slack_token')} try: # Force request to take at least 1 second. Slack docs state: # > In general we allow applications that integrate with Slack to send @@ -78,16 +78,28 @@ def slack_api_http( time.sleep(retry_delay) if method == 'POST': - response = requests.post(uri, data=payload) + response = requests.post(uri, json=payload, headers=headers) else: - response = requests.get(uri, params=payload) + response = requests.get(uri, params=payload, headers=headers) if response.status_code == requests.codes.ok and 'error' in response.json( - ) and response.json()['error'] == 'not_authed': - self.logger.error( - 'Need to setup auth. eg, SLACK_TOKEN= python slack-autoarchive.py' - ) - sys.exit(1) + ): + if response.json()['error'] == 'not_authed': + self.logger.error( + 'Need to setup auth. eg, SLACK_TOKEN= python slack-autoarchive.py' + ) + sys.exit(1) + elif response.json()['error'] == 'not_in_channel': + self.logger.error( + 'Need to add bot to channel described by ' + str(payload) + ) + self.join_channel(payload['channel']) + return response.json() + else: + self.logger.error( + response.json()['error'] + ) + sys.exit(1) elif response.status_code == requests.codes.ok and response.json( )['ok']: return response.json() @@ -100,10 +112,18 @@ def slack_api_http( raise Exception(error_msg) return None + def join_channel(self, channel_id): + api_endpoint = 'conversations.join' + info_payload = {'channel': channel_id} + channel_info = self.slack_api_http(api_endpoint=api_endpoint, + payload=info_payload, + method='POST') + self.logger.info('Joined channel ' + channel_id) + def get_all_channels(self): """ Get a list of all non-archived channels from slack channels.list. """ payload = {'exclude_archived': 1} - api_endpoint = 'channels.list' + api_endpoint = 'conversations.list' channels = self.slack_api_http(api_endpoint=api_endpoint, payload=payload)['channels'] all_channels = [] @@ -144,7 +164,7 @@ def is_channel_disused(self, channel, too_old_datetime): """ Return True or False depending on if a channel is "active" or not. """ num_members = channel['num_members'] payload = {'inclusive': 0, 'oldest': 0, 'count': 50} - api_endpoint = 'channels.history' + api_endpoint = 'conversations.history' payload['channel'] = channel['id'] channel_history = self.slack_api_http(api_endpoint=api_endpoint, @@ -165,7 +185,7 @@ def is_channel_whitelisted(self, channel, white_listed_channels): # self.settings.get('skip_channel_str') # if the channel purpose contains the string self.settings.get('skip_channel_str'), we'll skip it. info_payload = {'channel': channel['id']} - channel_info = self.slack_api_http(api_endpoint='channels.info', + channel_info = self.slack_api_http(api_endpoint='conversations.info', payload=info_payload, method='GET') channel_purpose = channel_info['channel']['purpose']['value'] @@ -197,7 +217,7 @@ def send_channel_message(self, channel_id, message): def archive_channel(self, channel, alert): """ Archive a channel, and send alert to slack admins. """ - api_endpoint = 'channels.archive' + api_endpoint = 'conversations.archive' stdout_message = 'Archiving channel... %s' % channel['name'] self.logger.info(stdout_message) @@ -205,7 +225,7 @@ def archive_channel(self, channel, alert): channel_message = alert.format(self.settings.get('days_inactive')) self.send_channel_message(channel['id'], channel_message) payload = {'channel': channel['id']} - self.slack_api_http(api_endpoint=api_endpoint, payload=payload) + self.slack_api_http(api_endpoint=api_endpoint, payload=payload, method='POST') self.logger.info(stdout_message) def send_admin_report(self, channels): From f2979adfd0d5ddbd2bb21969c6a996320decdc44 Mon Sep 17 00:00:00 2001 From: shama_codesurfer Date: Sat, 25 Sep 2021 21:36:49 +0600 Subject: [PATCH 2/3] Handled large list of channels by adding pagination capabilities using cursor --- slack_autoarchive.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/slack_autoarchive.py b/slack_autoarchive.py index 1d30b40..bdaef83 100755 --- a/slack_autoarchive.py +++ b/slack_autoarchive.py @@ -122,18 +122,34 @@ def join_channel(self, channel_id): def get_all_channels(self): """ Get a list of all non-archived channels from slack channels.list. """ - payload = {'exclude_archived': 1} + payload = {'exclude_archived':1, 'limit':100} api_endpoint = 'conversations.list' - channels = self.slack_api_http(api_endpoint=api_endpoint, - payload=payload)['channels'] + + flag_first_page = True + flag_last_page = False + next_cursor = '' all_channels = [] - for channel in channels: - all_channels.append({ - 'id': channel['id'], - 'name': channel['name'], - 'created': channel['created'], - 'num_members': channel['num_members'] - }) + while not flag_last_page: + if not flag_first_page: + payload['cursor'] = next_cursor + api_response = self.slack_api_http(api_endpoint=api_endpoint, payload=payload) + if flag_first_page: + flag_first_page = False + + channels = api_response['channels'] + self.logger.info('%s channel(s) retrieved' % str(len(channels))) + for channel in channels: + all_channels.append({ + 'id': channel['id'], + 'name': channel['name'], + 'created': channel['created'], + 'num_members': channel['num_members'] + }) + + next_cursor = api_response['response_metadata'].get('next_cursor') + if not next_cursor: + flag_last_page = True + return all_channels def get_last_message_timestamp(self, channel_history, too_old_datetime): From 79c3b44b8d5e468d0e82dd8c5d4ca513d8a4283b Mon Sep 17 00:00:00 2001 From: Shama Rashid <35882505+shamarashid@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:58:11 +0600 Subject: [PATCH 3/3] Bug fix - resubmit channel history request after joining a channel. Without this fix, the channel will be archived right after the bot joins the channel since channel history has no messages. Co-authored-by: Nils Reichardt --- slack_autoarchive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slack_autoarchive.py b/slack_autoarchive.py index bdaef83..79ddae8 100755 --- a/slack_autoarchive.py +++ b/slack_autoarchive.py @@ -94,7 +94,7 @@ def slack_api_http( 'Need to add bot to channel described by ' + str(payload) ) self.join_channel(payload['channel']) - return response.json() + return self.slack_api_http(api_endpoint, payload, method) else: self.logger.error( response.json()['error']