import os import requests from flask import Flask, request, Response from urllib.parse import urljoin from uuid import uuid4 import json from dotenv import load_dotenv from exporter import parse_replies, parse_channel_history app = Flask(__name__) load_dotenv(os.path.join(app.root_path, '.env')) # chat write interactions def post_response(response_url, text): requests.post(response_url, json={'text': text}) # pagination handling def get_at_cursor(url, params, response_url, cursor=None): if cursor is not None: params['cursor'] = cursor r = requests.get(url, params=params) data = r.json() try: if data['ok'] is False: post_response(response_url, "I encountered an error: %s" % data['error']) next_cursor = None if 'response_metadata' in data and 'next_cursor' in data['response_metadata']: next_cursor = data['response_metadata']['next_cursor'] if str(next_cursor).strip() == '': next_cursor = None return next_cursor, data except KeyError as e: post_response(response_url, "Something went wrong: %s." % e) return None, [] def paginated_get(url, params, response_url, combine_key=None): next_cursor = None result = [] while True: next_cursor, data = get_at_cursor(url, params, response_url, cursor=next_cursor) try: result.extend(data) if combine_key is None else result.extend(data[combine_key]) except KeyError: post_response(response_url, "Sorry! I got an unexpected response (KeyError).") if next_cursor is None: break return result # GET requests def channel_history(channel_id, response_url): params = { 'token': os.environ['SLACK_USER_TOKEN'], 'channel': channel_id, 'limit': 200 } return paginated_get('https://slack.com/api/conversations.history', params, response_url, combine_key='messages') def user_list(team_id, response_url): params = { 'token': os.environ['SLACK_USER_TOKEN'], 'limit': 200, 'team_id': team_id } return paginated_get('https://slack.com/api/users.list', params, response_url, combine_key='members') def channel_replies(timestamps, channel_id, response_url): replies = [] for timestamp in timestamps: params = { 'token': os.environ['SLACK_USER_TOKEN'], 'channel': channel_id, 'ts': timestamp, 'limit': 200 } r = paginated_get('https://slack.com/api/conversations.replies', params, response_url, combine_key='messages') replies.append(r) return replies # Flask routes @app.route('/slack/export-channel', methods=['POST']) def export_channel(): data = request.form try: team_id = data['team_id'] team_domain = data['team_domain'] ch_id = data['channel_id'] ch_name = data['channel_name'] response_url = data['response_url'] command_args = data['text'] except KeyError: return Response("Sorry! I got an unexpected response (KeyError)."), 200 post_response(response_url, "Retrieving history for this channel...") ch_hist = channel_history(ch_id, response_url) export_mode = str(command_args).lower() exports_subdir = 'exports' exports_dir = os.path.join(app.root_path, exports_subdir) file_ext = '.txt' if export_mode == 'text' else '.json' filename = "%s-ch_%s-%s%s" % (team_domain, ch_id, str(uuid4().hex)[:6], file_ext) filepath = os.path.join(exports_dir, filename) loc = urljoin(request.url_root, 'download/%s' % filename) if not os.path.isdir(exports_dir): os.makedirs(exports_dir, exist_ok=True) with open(filepath, mode='w') as f: if export_mode == 'text': num_msgs = len(ch_hist) sep = '=' * 24 header_str = 'Channel Name: %s\nChannel ID: %s\n%s Messages\n%s\n\n' % (ch_name, ch_id, num_msgs, sep) data_ch = header_str + parse_channel_history(ch_hist, user_list(team_id, response_url)) f.write(data_ch) else: json.dump(ch_hist, f, indent=4) post_response(response_url, "Done! This channel's history is available for download here (note that this link " "is single-use): %s" % loc) return Response(), 200 @app.route('/slack/export-replies', methods=['POST']) def export_replies(): data = request.form try: team_id = data['team_id'] team_domain = data['team_domain'] ch_id = data['channel_id'] ch_name = data['channel_name'] response_url = data['response_url'] command_args = data['text'] except KeyError: return Response("Sorry! I got an unexpected response (KeyError)."), 200 post_response(response_url, "Retrieving reply threads for this channel...") print(ch_id) ch_hist = channel_history(ch_id, response_url) print(ch_hist) ch_replies = channel_replies([x['ts'] for x in ch_hist if 'reply_count' in x], ch_id, response_url) export_mode = str(command_args).lower() exports_subdir = 'exports' exports_dir = os.path.join(app.root_path, exports_subdir) file_ext = '.txt' if export_mode == 'text' else '.json' filename = "%s-re_%s-%s%s" % (team_domain, ch_id, str(uuid4().hex)[:6], file_ext) filepath = os.path.join(exports_dir, filename) loc = urljoin(request.url_root, 'download/%s' % filename) if export_mode == 'text': header_str = 'Threads in: %s\n%s Messages' % (ch_name, len(ch_replies)) data_replies = parse_replies(ch_replies, user_list(team_id, response_url)) sep = '=' * 24 data_replies = '%s\n%s\n\n%s' % (header_str, sep, data_replies) else: data_replies = ch_replies if not os.path.isdir(exports_dir): os.makedirs(exports_dir, exist_ok=True) with open(filepath, mode='w') as f: if export_mode == 'text': f.write(data_replies) else: json.dump(data_replies, f, indent=4) post_response(response_url, "Done! This channel's reply threads are available for download here (note that this " "link is single-use): %s" % loc) return Response(), 200 @app.route('/download/') def download(filename): path = os.path.join(app.root_path, 'exports', filename) def generate(): with open(path) as f: yield from f os.remove(path) mimetype = 'text/plain' if os.path.splitext(filename)[-1] == '.txt' else 'application/json' r = app.response_class(generate(), mimetype=mimetype) r.headers.set('Content-Disposition', 'attachment', filename=filename) return r if __name__ == '__main__': app.run(debug=False)