<? | |
///////////////////// | |
// slack2html | |
// by @levelsio | |
///////////////////// | |
// | |
///////////////////// | |
// WHAT DOES THIS DO? | |
///////////////////// | |
// | |
// Slack lets you export the chat logs (back to the first messages!), even if | |
// you are a free user (and have a 10,000 user limit) | |
// | |
// This is pretty useful for big chat groups (like mine, #nomads), where you | |
// do wanna see the logs, but can't see them within Slack | |
// | |
// Problem is that Slack exports it as JSON files, which is a bit unusable, | |
// so this script makes it into actual HTML files that look like Slack chats | |
// | |
/////////////////// | |
// INSTRUCTIONS | |
/////////////////// | |
// | |
// Run this script inside the directory of an extracted (!) Slack export zip | |
// e.g. "/tmp/#nomads Slack export Aug 25 2015" like this: | |
// MacBook-Pro:#nomads Slack export Aug 25 2015 mbp$ php slack2html.php | |
// | |
// It will then make two dirs: | |
// /slack2html/json | |
// /slack2html/html | |
// | |
// In the JSON dir it will put each channels chat log combined from all the | |
// daily logs that Slack outputs (e.g. /channel/2014-11-26.json) | |
// | |
// In the HTML dir it will generate HTML files with Slack's typical styling. | |
// It will also create an index.html that shows all channels | |
// | |
/////////////////// | |
// FEEDBACK | |
/////////////////// | |
// | |
// Let me know any bugs by tweeting me @levelsio | |
// | |
// Hope this helps! | |
// | |
// Pieter @levelsio | |
// | |
///////////////////// | |
ini_set('memory_limit', '1024M'); | |
date_default_timezone_set('UTC'); | |
mb_internal_encoding("UTF-8"); | |
error_reporting(E_ERROR); | |
// <config> | |
$stylesheet=" | |
* { | |
font-family:sans-serif; | |
} | |
body { | |
text-align:center; | |
padding:1em; | |
} | |
.messages { | |
width:100%; | |
max-width:700px; | |
text-align:left; | |
display:inline-block; | |
} | |
.messages img { | |
background-color:rgb(248,244,240); | |
width:36px; | |
height:36px; | |
border-radius:0.2em; | |
display:inline-block; | |
vertical-align:top; | |
margin-right:0.65em; | |
} | |
.messages .time { | |
display:inline-block; | |
color:rgb(200,200,200); | |
margin-left:0.5em; | |
} | |
.messages .username { | |
display:inline-block; | |
font-weight:600; | |
line-height:1; | |
} | |
.messages .message { | |
display:inline-block; | |
vertical-align:top; | |
line-height:1; | |
width:calc(100% - 3em); | |
} | |
.messages .message .msg { | |
line-height:1.5; | |
} | |
"; | |
// </config> | |
// <compile daily logs into single channel logs> | |
$files=scandir(__DIR__); | |
$baseDir=__DIR__.'/../slack2html'; | |
$jsonDir=$baseDir.'/'.'json'; | |
if(!is_dir($baseDir)) mkdir($baseDir); | |
if(!is_dir($jsonDir)) mkdir($jsonDir); | |
foreach($files as $channel) { | |
if($channel=='.' || $channel=='..') continue; | |
if(is_dir($channel)) { | |
$channelJsonFile=$jsonDir.'/'.$channel.'.json'; | |
if(file_exists($channelJsonFile)) { | |
echo "JSON already exists ".$channelJsonFile."\n"; | |
continue; | |
} | |
unset($chats); | |
$chats=array(); | |
echo '====='."\n"; | |
echo 'Combining JSON files for #'.$channel."\n"; | |
echo '====='."\n"; | |
$dates=scandir(__DIR__.'/'.$channel); | |
foreach($dates as $date) { | |
if(!is_dir($date)) { | |
echo '.'; | |
$messages=json_decode(file_get_contents(__DIR__.'/'.$channel.'/'.$date),true); | |
if(empty($messages)) continue; | |
foreach($messages as $message) { | |
array_push($chats,$message); | |
} | |
} | |
} | |
echo "\n"; | |
file_put_contents($channelJsonFile,json_encode($chats)); | |
echo number_format(count($chats)).' messages exported to '.$channelJsonFile."\n"; | |
} | |
} | |
// </compile daily logs into single channel logs> | |
// <load users file> | |
$users=json_decode(file_get_contents(__DIR__.'/'.'users.json'),true); | |
$usersById=array(); | |
foreach($users as $user) { | |
$usersById[$user['id']]=$user; | |
} | |
// </load users file> | |
// <load channels file> | |
$channels=json_decode(file_get_contents(__DIR__.'/'.'channels.json'),true); | |
$channelsById=array(); | |
foreach($channels as $channel) { | |
$channelsById[$channel['id']]=$channel; | |
} | |
// </load channels file> | |
// <generate html from channel logs> | |
$htmlDir=$baseDir.'/'.'html'; | |
if(!is_dir($htmlDir)) mkdir($htmlDir); | |
$channels=scandir($jsonDir); | |
$channelNames=array(); | |
$mostRecentChannelTimestamps=array(); | |
foreach($channels as $channel) { | |
if($channel=='.' || $channel=='..') continue; | |
if(is_dir($channel)) continue; | |
$mostRecentChannelTimestamp=0; | |
if($message['ts']>$mostRecentChannelTimestamp) { | |
$mostRecentChannelTimestamp=$message['ts']; | |
} | |
$array=explode('.json',$channel); | |
$channelName=$array[0]; | |
$channelHtmlFile=$htmlDir.'/'.$channelName.'.html'; | |
if(file_exists($channelHtmlFile)) { | |
echo "HTML already exists ".$channelJsonFile."\n"; | |
continue; | |
} | |
array_push($channelNames,$channelName); | |
echo '====='."\n"; | |
echo 'Generating HTML for #'.$channelName."\n"; | |
echo '====='."\n"; | |
$messages=json_decode(file_get_contents($jsonDir.'/'.$channel),true); | |
if(empty($messages)) continue; | |
$htmlMessages='<html><body><style>'.$stylesheet.'</style><div class="messages">'; | |
foreach($messages as $message) { | |
if(empty($message)) continue; | |
if(empty($message['text'])) continue; | |
echo '.'; | |
// change <@U38A3DE9> into levelsio | |
if(stripos($message['text'],'<@')!==false) { | |
$usersInMessage=explode('<@',$message['text']); | |
foreach($usersInMessage as $userInMessage) { | |
$array=explode('>',$userInMessage); | |
$userHandleInBrackets=$array[0]; | |
$array=explode('|',$array[0]); | |
$userInMessage=$array[0]; | |
$username=$array[1]; | |
if(empty($username)) { | |
$username=$usersById[$userInMessage]['name']; | |
} | |
$message['text']=str_replace('<@'.$userHandleInBrackets.'>','@'.$username,$message['text']); | |
} | |
} | |
// change <#U38A3DE9> into #_chiang-mai | |
if(stripos($message['text'],'<#')!==false) { | |
$channelsInMessage=explode('<#',$message['text']); | |
foreach($channelsInMessage as $channelInMessage) { | |
$array=explode('>',$channelInMessage); | |
$channelHandleInBrackets=$array[0]; | |
$array=explode('|',$array[0]); | |
$channelInMessage=$array[0]; | |
$channelNameInMessage=$array[1]; | |
if(empty($username)) { | |
$channelNameInMessage=$channelsById[$channelInMessage]['name']; | |
} | |
if(!empty($username)) { | |
$message['text']=str_replace('<#'.$channelHandleInBrackets.'>','#'.$channelNameInMessage,$message['text']); | |
} | |
} | |
} | |
// change <http://url> into link | |
if(stripos($message['text'],'<http')!==false) { | |
$linksInMessage=explode('<http',$message['text']); | |
foreach($linksInMessage as $linkInMessage) { | |
$array=explode('>',$linkInMessage); | |
$linkTotalInBrackets=$array[0]; | |
$array=explode('|',$array[0]); | |
$linkInMessage=$array[0]; | |
$message['text']=str_replace('<http'.$linkTotalInBrackets.'>','<a href="http'.$linkInMessage.'">http'.$linkInMessage.'</a>',$message['text']); | |
} | |
} | |
// change @levelsio has joined the channel into | |
// @levelsio\n has joined #channel | |
if(stripos($message['text'],'has joined the channel')!==false) { | |
$message['text']=str_replace('the channel','#'.$channelName,$message['text']); | |
$message['text']=str_replace('@'.$usersById[$message['user']]['name'].' ','',$message['text']); | |
} | |
$array=explode('.',$message['ts']); | |
$time=$array[0]; | |
$message['text']=utf8_decode($message['text']); | |
$htmlMessage=''; | |
$htmlMessage.='<div><img src="'.$usersById[$message['user']]['profile']['image_72'].'" /><div class="message"><div class="username">'.$usersById[$message['user']]['name'].'</div><div class="time">'.date('Y-m-d H:i',$message['ts']).'</div><div class="msg">'.$message['text']."</div></div></div><br/>\n"; | |
$htmlMessages.=$htmlMessage; | |
} | |
$htmlMessages.='</div></body></html>'; | |
file_put_contents($channelHtmlFile,$htmlMessages); | |
$mostRecentChannelTimestamps[$channelName]=$mostRecentChannelTimestamp; | |
echo "\n"; | |
} | |
asort($mostRecentChannelTimestamps); | |
$mostRecentChannelTimestamps=array_reverse($mostRecentChannelTimestamps); | |
// </generate html from channel logs> | |
// <make index html> | |
$html='<html><body><style>'.$stylesheet.'</style><div class="messages">'; | |
foreach($mostRecentChannelTimestamps as $channel => $timestamp) { | |
$html.='<a href="./'.$channel.'.html">#'.$channel.'</a> '.date('Y-m-d H:i',$timestamp).'<br/>'."\n"; | |
} | |
$html.='</div></body></html>'; | |
file_put_contents($htmlDir.'/index.html',$html); | |
// </make index html> | |
?> |
This comment has been minimized.
This comment has been minimized.
good work |
This comment has been minimized.
This comment has been minimized.
Try changing <? to <?php See http://stackoverflow.com/questions/2185320/how-to-enable-php-short-tags |
This comment has been minimized.
This comment has been minimized.
I give up :( i tired. I installed php, tried to run it with php. googled and googled and ended up even installing Apache. Need a dummies guide to this. |
This comment has been minimized.
This comment has been minimized.
Whenever I try using this I get an error that VCRUNTIME140.dll is not installed on my computer. Online, it says that I need to install the correct .NET framework from Microsoft (specifically here: http://stackoverflow.com/questions/30811668/php7-missing-vcruntime140-dll). What did you use to make this? |
This comment has been minimized.
This comment has been minimized.
@ everyone who has problems with Unicode ( @levelsio: Maybe you can review my fix and incorporate it somehow into your gist, as I feel the Unicode issue is an important one. |
This comment has been minimized.
This comment has been minimized.
@targi Thank you!! |
This comment has been minimized.
This comment has been minimized.
My PHP skills are weak - any way to get this to grab attachment text (like for pinned items) and spit that out too instead of just "user has pinned an item"? I would be happy with just a point in the right direction to try and tinker with it. Ninja Edit - got something working: |
This comment has been minimized.
This comment has been minimized.
This is great! Thank you @levelsio +1 for changing <? to <?php on line one of the file, and +1 to @F1rstStep for reminding me I needed to rename gistfile1.txt to slack2html.php |
This comment has been minimized.
This comment has been minimized.
Thanks @levelsio for this code ! I forked and modified it to add an optional parameter to have result in chronological order (my default preferred view) or in reverse chronological order (which is your default). |
This comment has been minimized.
This comment has been minimized.
Hey folks, I decided to take this a little further and wrote https://github.com/hfaran/slack-export-viewer with an expanded feature set including:
Check it out if you'd like :) |
This comment has been minimized.
This comment has been minimized.
Thanks @levelsio for creating this. I just wanted to let everyone I've forked and modified this script to allow it to also work for Slack history exported using https://github.com/hisabimbola/slack-history-export - a command line module to allow you to download your Slack history. When you export all your history from Slack, it does not include history from private groups/channels and DMs. The slack-history-export tool provides a way to export history from these private groups/channels and DMs, but the original version of this script was not compatible with the exported JSON files from the slack-history-export tool. This fork updates the script to make it compatible so that you can combine both your exported public channel history from Slack, and your private group/channel and DM history from the slack-history-export tool and convert all of this to HTML. See the "Changes in this fork" section for instructions on how to use the fork. |
This comment has been minimized.
This comment has been minimized.
I would suggest replacing |
This comment has been minimized.
This comment has been minimized.
Does anyone else have issues with punctuation in the exported HTML appearing as question marks instead of the correct punctuation? It's tedious to manually search and replace. |
This comment has been minimized.
This comment has been minimized.
Is anyone else having issues with usernames not displaying properly for messages in a given channel? |
This comment has been minimized.
This comment has been minimized.
@hfaran well done! Simple and sweet. |
This comment has been minimized.
This comment has been minimized.
Nice job |
This comment has been minimized.
This comment has been minimized.
Thanks for the great idea, however it would be nice, if a little bit more introduction is provided to use this script for people without php knowledge. There is one other problem that this tool assumes that the input file is standard slack zip export. Normal users do not have the possibility to download slack archives and hence this script is only restricted to admins. Actually, I found several other scripts but each with it's own limitation:
Any help guys.. some newbie instructions would be great |
This comment has been minimized.
This comment has been minimized.
@lukcha The reason for the incorrect characters is due to the line If you're taking the html and pasting it into a website then if that website itself uses utf-8 encoding, for example uxmastery.com, then you won't have a problem. Alternatively anyone using this script and wanting the files to work with UTF-8, you can change the line |
This comment has been minimized.
This comment has been minimized.
Working on the Fedora24, the instruction is as follow:
|
This comment has been minimized.
This comment has been minimized.
Is it possible to use this to instead covert the json to Plain text files? (I.e, merge them into the single json file as in part 1 of the script, then parse all of the user/channel ID's to their actual names, and into a plain text file so it can be logged and searched in a database?) Instead of making them into Html images? |
This comment has been minimized.
This comment has been minimized.
@levelsio this is superb! And it worked like a charm. Thanks a lot! |
This comment has been minimized.
This comment has been minimized.
I have a question! How can I sync all slack history in real time using slack's API method in stead of downloading its backups manually every time? |
This comment has been minimized.
This comment has been minimized.
Is there a way to export the chats in full "Slack view" (i.e. pasted link unfurls, Giphys, pasted images, etc.)? |
This comment has been minimized.
This comment has been minimized.
Found this through a Google search and would like to be able to use this for our Slack team. I've downloaded the Slack archive, but am not a programmer and really do not understand how to proceed. I have no idea how to run a script inside a directory, as the instructions state. Are there any step-by-step instructions available on how to use this for someone without php or programming skills? |
This comment has been minimized.
This comment has been minimized.
Thanks mate! Works great! |
This comment has been minimized.
This comment has been minimized.
@natsandman-rav I'm having the same issue. Did you find a solution? |
This comment has been minimized.
This comment has been minimized.
@github3332 My slack-backup.sh script uses a derivative work of this script. Currently there exists another issue though, where line 53 of this php tool continues to fail.
Can be fixed by installing the |
This comment has been minimized.
This comment has been minimized.
Usernames aren't pulled in to the html. |
This comment has been minimized.
This comment has been minimized.
Is there a way to add a feature that includes threaded messages? This tool works fine even since the threaded message update, but it sorts threaded messages by time in the whole channel vs. with the original message. |
This comment has been minimized.
This comment has been minimized.
Does that work for Windows? |
This comment has been minimized.
This comment has been minimized.
Great post and works like a charm. Well done Sir! I had a memory problem when running is on my mac. The error was: I increased the memory limit on line 50 and it completed with no problems. ini_set('memory_limit;, '2048M'); |
This comment has been minimized.
This comment has been minimized.
For windows:,
Reports are generated now. Hope this helps. |
This comment has been minimized.
This comment has been minimized.
Hi As far as I see, this script does not create code to see pictures on the local page, it renders only a link to the slack url of the picture, doesn't it? |
This comment has been minimized.
This comment has been minimized.
Hey, as I don't want to install PHP on my system I just used a basic PHP docker image. Requirements:
Steps:
mkdir $HOME/slack2html/
wget -O slack2html.php https://gist.githubusercontent.com/levelsio/122907e95956602e5c09/raw/6ea53ecfb936f4f0fbbcbb74bb7b6db5030ec64b/gistfile1.txt
docker run -ti --rm -v $HOME/slack2html:/mnt/slack_data \
-v $HOME/slack2html_output:/mnt/slack2html/ \
php:7 bash -c 'cd /mnt/slack_data && php slack2html.php'
ls -al $HOME/slack2html_output/html/
firefox $HOME/slack2html_output/html/index.html Clean after yourselfIf you are not a PHP-docker-image user, you may wanna remove that previously downloaded image that takes (at this time) ~350MiB of storage. docker rmi php:7 Results
|
This comment has been minimized.
This comment has been minimized.
@mario6097, you may wanna check my gist for replacing those avatars with locally downloaded avatars. |
This comment has been minimized.
This comment has been minimized.
Im noticing that this script skips messages where "text": null. I have many messages where this is the case, usually from a bot. There is message information in the attachment though. Would it be possible to have these messages included? also, would it be possible to keep each day in its own file rather than combining them? a single file for a whole channel can be very unwieldy Great script altogether. |
This comment has been minimized.
This comment has been minimized.
@levelsio I also found I needed to insert the top line as:
On UNIX. |
This comment has been minimized.
This comment has been minimized.
Also needed to add 'php' to the top line, and install |
This comment has been minimized.
This comment has been minimized.
Very nice! This was extremely helpful. Thanks for sharing it! |
This comment has been minimized.
This comment has been minimized.
sadly doesn’t seem to handle emoji reactions .. anyone implemented this already? |
This comment has been minimized.
This comment has been minimized.
Would it be possible to have it maintain the one day per channel structure that Slack provides rather than combining multiple days? |
This comment has been minimized.
This comment has been minimized.
There are updated tools for this -- https://github.com/zach-snell/slack-export and https://github.com/hfaran/slack-export-viewer |
This comment has been minimized.
This comment has been minimized.
I used the slack-export which worked great but slack-export-viewer keeps giving me these errors: MacBook-Pro-17:slack-export-master davis$ slack-export-viewer -p 80 -I 192.168.x.x --debug -z test-export.zip.zip |
This comment has been minimized.
This comment has been minimized.
I keep getting same |
This comment has been minimized.
This comment has been minimized.
I made a PowerShell version: |
This comment has been minimized.
This comment has been minimized.
Here's a challenge - get the PHP script to include bot messages as well. The idea being to have a readable account of alerts dumped into a channel by a bot along with all user posts in response. Would love to see this. |
This comment has been minimized.
This comment has been minimized.
Thank you for the instructions! May somebody please share an example of how the HTML file looks like after the conversion? |
This comment has been minimized.
I am not sure how to run this script. Can anyone give me a quick nudge? I am not a programmer and I don't know how to get the result working.
I did the following,
@echo off
C:\PHP\php.exe "D:\Slack export Aug 28 2015\slack2html.php"
PAUSE
After I run the batch file, all I get is the command line screen filled with code on display, but nothing else happens :-/
I'm no programmer but I really need to backup my slack history. I'd really appreciate if someone told me what should I do here.