diff --git a/archive-slack.py b/archive-slack.py index e2413fa..9627c48 100755 --- a/archive-slack.py +++ b/archive-slack.py @@ -3,7 +3,7 @@ # $Id: archive-slack.py,v 1.6 2015/05/11 08:15:55 errror Exp $ # apt-get install python-anyjson -import httplib, anyjson, pprint, sys, os, getopt +import httplib, anyjson, pprint, sys, os, getopt, json # generic wrapper for slack api calls, error handling only basic def slackApi(function, args = {}): @@ -59,7 +59,8 @@ def getDMs(): # writes a json output file 'name.json' containing json serialization of 'data' def writeJson(name, data, subdir = "."): f = open(subdir+os.sep+name+'.json', 'w') - f.write(anyjson.serialize(data)) + f.write(json.dumps(data, sort_keys=True, indent=2)) +# f.write(anyjson.serialize(data)) f.close() # reads a json input file 'name.json' returning deserialized data @@ -171,15 +172,19 @@ def fetchFiles(files, oldfiles): infoprint(" "+f['name']) hcon = httplib.HTTPSConnection('slack-files.com') if not f.has_key('url_download'): + print("No 'url_download' in file: "+f['name']+"; skipping."); pprint.pprint(f) - hcon.request('GET', f['url_download'][23:]) - result = hcon.getresponse() - if result.status != 200: - print 'Error fetching file '+f['id']+' from '+f['url_download'] else: - out = open(outfilename, 'w') - out.write(result.read()) - out.close() + hcon.request('GET', f['url_download'][23:]) + result = hcon.getresponse() + if result.status != 200: + print 'Error fetching file '+f['id']+' from '+f['url_download'] + else: + out = open(outfilename, 'w') + out.write(result.read()) + out.close() + oldfiledict[f['id']]=f + return oldfiledict.values() def usage(exitcode): print "" @@ -265,4 +270,6 @@ def infoprint(text): writeJson('files', files) if private: infoprint("DMs") - fetchChannels(getDMs(), 'im', 'dms') + dms = getDMs() + writeJson("dms", dms) + fetchChannels(dms, 'im', 'dms') diff --git a/json2html.py b/json2html.py new file mode 100755 index 0000000..20273ed --- /dev/null +++ b/json2html.py @@ -0,0 +1,226 @@ +#!/usr/bin/python +# apt-get install python-anyjson + + +import anyjson, pprint, sys, os, getopt, json, time, shutil + + + +# reads a json input file 'name.json' returning deserialized data +def readJson(name, subdir="."): + if os.path.isfile(subdir + os.sep + name + '.json'): + f = open(subdir + os.sep + name + '.json', 'r') + data = anyjson.deserialize(f.read()) + f.close() + return data + else: + return None + + +# writes a json output file 'name.json' containing json serialization of 'data' +def writeHTML(name, data, subdir="html"): + f = open(subdir + os.sep + name + '.html', 'w') + f.write(data.encode('utf-8')) + f.close() + + +def itemName(item, users): + if 'name' in item: + return item['name'] + else: + return dmUserName(item, users) + + +# format a single message according to the rules +# FIXME: should be improved for different message types +def formatMessage(message, users): + #Quick and dirty workarount for no 'text' key. + if not 'text' in message: + print("Unexpected message, no 'text' key:") + print(json.dumps(message, sort_keys=True, indent=2).encode('utf-8')) + return "" + + res = '
' + \ + time.strftime('%H:%M:%S', time.localtime(float(message['ts']))) + \ + '
' + userColor='' + if 'username' in message: + username = message['username'] + elif 'user' in message: + uid = message['user'] + username= user_name(uid, users) + if uid in users and 'color' in users[uid]: + userColor='style="color: #'+users[uid]['color']+'"' + else: + username = '????' + res += '
'+username + '
' + res += message['text'] + res += '
' + return res + + +# returns basic header for all generated html files +def htmlHeader(title): + res = ''' + + + + ''' + title + ''' + + + + ''' + return res + +# default footer. +def htmlFooter(): + return '''''' + +# formats a channel contents into a single html returned as string. +def prepareChannelContent(channel, users, item_type='channel'): + item_name=itemName(channel, users) + content = htmlHeader(item_name) + content += '

' + ('#' if item_type=='channel'else '') + item_name + '

\n' + messages = channel['messages'] + current_day = None + for message in messages: + mg_day = time.strftime('%Y%j', time.localtime(float(message['ts']))) + if (current_day != mg_day): + current_day = mg_day + content += '
' + time.strftime('%Y-%m-%d', + time.localtime(float(message['ts']))) + '
\n' + content += formatMessage(message, users) + content += '\n' + content += htmlFooter() + return content + + +def prepareGroupList(channels): + html='\n' + return html + + +#Human readable user name (include Slackbot and unknown cases) +def user_name(uid, users): + if uid in users: + return users[uid]['name'] + elif uid=='USLACKBOT': + return "Slackbot" + else: + return "Unknown ("+uid+")" + +def dmUserName(dm, users): + uid=dm['user'] + return user_name(uid, users) + +#prepare index.html with links to +def prepareTOC(channels, groups, dms, users): + html=htmlHeader('Slack dump') + if channels: + html += '

Channels

\n' + html += prepareGroupList(channels) + if groups: + html += '

Groups

\n' + html += prepareGroupList(groups) + if dms: + html += '

DMs

\n' + html += '\n' + + html += htmlFooter() + return html + + +def verboseprint(text): + if verbose: + print(text) + #text.encode('ascii', 'ignore') + + +def infoprint(text): + if not quiet: + print(text) + #text.encode('ascii', 'ignore') + + + +def exportClass(items, users, type, dir): + for item in items: + verboseprint('Exporting '+type+' '+itemName(items[item], users)) + item_content = readJson(item, dir) + html = prepareChannelContent(item_content, users, type) + writeHTML(item, html) + return + +def usage(exitcode): + print("") + print("Usage: json2html.py [options] ") + print("") + print("Run this program in the root directory where previously archive-slack has been run.") + print("Options:") + print(" -h --help : print(this help") + print(" -q --quiet : no output except errors") + print(" -v --verbose : verbose output") + print("") + exit(exitcode) + +opts, args = getopt.gnu_getopt(sys.argv, 'hqv', ['help', 'quiet', 'verbose']) +# and a authentication token, given via cmdline arg, use https://api.slack.com/#auth to generate your own +if len(args) != 1: + usage(1) + +quiet = False +verbose = False + +for o, v in opts: + if o == '--help' or o == '-h': + usage(0) + elif o == '--quiet' or o == '-q': + quiet = True + elif o == '--verbose' or o == '-v': + verbose = True + else: + usage(1) + +# verbose overrides quiet +if verbose: + quiet = False + +#read channels +if not os.path.isdir("html"): + infoprint("Creating 'html' directory") + os.mkdir("html") +#copying the style.css file: +shutil.copy2(sys.path[0]+os.sep+'style.css', 'html') +channels = readJson("channels") +groups = readJson("groups") +dms = readJson("dms") +users = readJson("users") +infoprint('Preparing index.html') +writeHTML("index", prepareTOC(channels, groups, dms, users)) +infoprint("\n\nPreparing CHANNELS:") +exportClass(channels, users, 'channel', 'channels') +if groups: + infoprint("\n\nPreparing GROUPS:") + exportClass(groups, users, 'group', 'groups') +if dms: + infoprint("\n\nPreparing DMs:") + exportClass(dms, users, 'dm', 'dms') + +# for channel in channels: +# channelContent = readJson(channel, "channels") +# verboseprint("Formatting channel: " + channelContent['name']); +# html = prepareChannelContent(channelContent, users) +# writeHTML(channel, html) +infoprint("Export complete.") diff --git a/style.css b/style.css new file mode 100644 index 0000000..bb72366 --- /dev/null +++ b/style.css @@ -0,0 +1,50 @@ +body { + color: #555459; + font-family: "Helvetica Neue",Helvetica,"Segoe UI",Tahoma,Arial,sans-serif; + font-size: 16px; + line-height: 1rem; +} + +.message_user { + display: inline; +} + + +.message_content { +margin-left: 80px; +} + +.timestamp { + width: 80px; + /*margin-right: 2em;*/ + text-align: left; + position: absolute; + +} + +.message_user { +font-weight: 900; +} +.message { +padding-bottom: 2px; +padding-top: 1px; + +} + +.date_separator { + background: #fff none repeat scroll 0 0; + clear: both; + color: #2c2d30; + cursor: default; + font-family: Slack-Lato,appleLogo,sans-serif; + font-size: 0.9rem; + font-weight: 700; + line-height: 1rem; + margin: 1.2rem 0; + padding: 0; + position: relative; + text-align: center; + border-top: 1px solid black; + border-bottom: 1px solid black; + width: 90%; +} \ No newline at end of file