* Released under the MIT license * * This script reads the JSON file created by ffexp.php >= 1.1 and creates * a fully functional (with CSSand a little JavaScript) HTML page * * As for ffexp.php you need to set the username and the media directory * (this is needed to create the correct images and file url) * * You can chose the format of post's author/feeds, too * * The HTML file will not use external resources (needed images, css and js are all self contained). * * This script does not need an active network connection * */ /******************************** * Begin of configuration options */ # Your friendfeed username $username = ""; # The directory where images and files are stored $media_dir = "ff_media"; /* Set a limit to the number of processed entries. "0" means "no limit" */ $limit = 0; # The format of post's author/feeds # "" : Useful for user feed (default) # "direct" : Useful for direct messages # "group" : Useful for groups/lists $fromto = ""; /** * End of configuration options *******************************/ if (empty($username)) { notify("You need to provide the script with your Friendfeed username.\n"); exit; } $media_dir .= "/{$username}"; $file = @$argv[1]; if (empty($file) || !is_readable($file)) { notify("Please specify a readable file in JSON format\n"); exit(); } $fh = fopen($file,"r"); $row = fgets($fh); if ($row != "[\n") { notify("The file is not in the correct format. You should have created the export file with ffexp version 1.1 or higher.\n"); exit(); } if (empty($fromto)) $fromto = ''; # Set default value # Let's go fancy print "\n"; $head = $body = ""; $head = html("meta", null, array('charset' => 'utf-8')); # Give a chance to the dumbest too $head .= html("meta", null, array('http-equiv' => "Content-Type", 'content' => "text/html;charset=utf-8")); $head .= html('style', get_css(), array('type'=>'text/css')); $n = 0; while ($row = fgets($fh)) { $row = rtrim($row); if (substr($row, -1) == ',') { $row = substr($row, 0, -1); } $entry = json_decode($row); if ($entry) { $n++; if ($limit && ($n == ($limit + 1))) { break; } $from = ''; switch ($fromto) { case 'direct': if ($entry->from->id == $username) { $to = ''; foreach($entry->to as $dest) { if ($to) $to .= ', '; $to .= html('a', NULL, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$dest->id}")) . ($dest->name ? $dest->name : '???') . ''; } $from = html('p', 'To ' . $to, array('class' => 'e_from')); } else { if (count($entry->to) == 1) { $from = html('p', html('a', $entry->from->name, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$entry->from->id}")), array('class' => 'e_from')); } else { $to = ''; foreach($entry->to as $dest) { if ($to) $to .= ', '; $to .= html('a', NULL, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$dest->id}")) . ($dest->name ? $dest->name : '???') . ''; } $from = html('p', html('a', $entry->from->name, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$entry->from->id}")) . ' to ' . $to, array('class' => 'e_from')); } } break; case 'group': if (property_exists($entry, "to")) { if (count($entry->to) == 1) { $from = html('p', html('a', $entry->from->name, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$entry->from->id}")), array('class' => 'e_from')); } else { $to = ''; foreach($entry->to as $dest) { if ($to) $to .= ', '; $to .= html('a', NULL, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$dest->id}")) . ($dest->name ? $dest->name : '???') . ''; } $from = html('p', html('a', $entry->from->name, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$entry->from->id}")) . ' to ' . $to, array('class' => 'e_from')); } } break; default: if (property_exists($entry, "to")) { if (count($entry->to) == 1) { $from = ''; } else { $to = ''; foreach($entry->to as $dest) { if ($to) $to .= ', '; $to .= html('a', NULL, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$dest->id}")) . ($dest->name ? $dest->name : '???') . ''; } $from = html('p', 'To ' . $to, array('class' => 'e_from')); } } break; } $comments = ''; if (isset($entry->comments)) { foreach ($entry->comments as $comment) { $comments .= html('li', $comment->body . ' – ' . html('a', $comment->from->name, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$comment->from->id}")), array('class' => 'e_comment')); } $comments = html('ol', $comments, array('class' => 'e_comments')); } $likes = ''; if (isset($entry->likes)) { $likes = array(); foreach ($entry->likes as $like) { $likes[] = html('span', html('a', $like->from->name, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$like->from->id}")), array('class' => 'e_like')); } $likes = html('p', join(', ', $likes), array('class' => 'e_likes')); } $thumbnails = ''; if (isset($entry->thumbnails)) { $thumbnails = array(); foreach ($entry->thumbnails as $idx => $thumbnail) { $src = get_img_src(str_replace('e/', 't_', $entry->id) . "." . ($idx + 1)); if (FALSE !== strpos($thumbnail->link, '/m.friendfeed-media.com/')) { $href = get_img_src(str_replace('e/', 'i_', $entry->id) . "." . ($idx + 1)); } else { $href = $thumbnail->link; } $thumbnails[] = html('a', html('img', null, array('src' => $src, 'class' => 'e_tn', 'title' => isset($thumbnail->filename) ? $thumbnail->filename : false)), array('href' => $href)); } if (count($thumbnails) > 1) { $thumbnails[] = html('span', null, array('class' => 'e_more')); } $thumbnails = html('p', join(' ', $thumbnails), array('class' => 'e_thumbnails')); } $files = ''; if (isset($entry->files)) { $files = array(); foreach ($entry->files as $idx => $file) { $href = get_file_href(str_replace('e/', 'f_', $entry->id) . "." . $file->name); $files[] = html('li', html('a', $file->name, array('href' => $href)), array('class' => 'e_file')); } $files = html('ul', join('', $files), array('class' => 'e_files')); } $via = ''; if (isset($entry->via)) { $via = 'from ' . html('a', $entry->via->name, array('href' => $entry->via->url, 'class' => 'e_via')); } $nc = (empty($comments) ? '0' : count($entry->comments)); $nl = (empty($likes) ? '0' : count($entry->likes)); $body .= html('li', $from . html('p', $entry->body, array('class' => 'e_body')) . html('p', html('span', html('a', $entry->from->name, array('class' => 'e_person' , 'href' => "http://friendfeed.com/{$entry->from->id}"))) . ' – ' . html('span', html('a', $entry->date, array('class' => 'e_url', 'href' => $entry->url)), array('class' => 'e_date')) . $via . ' – ' . html('span', "{$nc} comment" . ((!$nc || $nc > 1) ? 's' : ''), array('class' => 'e_nc ' . (empty($comments) ? '' : 'open_comments'))) . ' – ' . html('span', "{$nl} like" . ((!$nl || $nl > 1) ? 's' : ''), array('class' => 'e_nl ' . (empty($likes) ? '' : 'open_likes'))) , array('class' => 'e_meta') ) . $files . $thumbnails . $likes . $comments ); } } $limit_notice = ''; if ($limit) { $limit_notice = "(only the first {$limit} are shown)"; } print html("html", html('head', $head) . html('body', html('div', html('p', "{$username}'s {$n} Friendfeed posts {$limit_notice}") . html('p', html('span', 'Show all comments', array('class' => 'open_comments')) . ' – ' . html('span', 'Show all likes', array('class' => 'open_likes')) . ' – ' . html('span', 'Show all images', array('class' => 'e_more')) , array('id' => 'ff_tools')) . html('ul', $body, array('id' => 'ff_entries')), array('id' => 'ff_main')) . html('script', get_js()) ) ); fclose($fh); exit(); function html($tag, $content=NULL, $attr=array()) { $output = "<{$tag}"; if (!empty($attr)) { $attrs=array(); foreach($attr as $k => $v) { $attrs[] = "{$k}=\"" . htmlspecialchars($v) . "\""; } $output .= " " . join(' ', $attrs); } $output .= ">"; if (NULL !== $content) { $output .= "{$content}\n"; } else { $output .= "\n"; } return $output; } function get_img_src($filename, $fuzzy=TRUE) { global $media_dir; $candidates = glob("{$media_dir}/{$filename}" . ($fuzzy ? ".*" : "")); return $candidates ? $candidates[0] : ''; } function get_file_href($filename) { global $media_dir; return "{$media_dir}/{$filename}"; } function notify($m) { file_put_contents("php://stderr", $m); flush(); } function get_css() { return << li { list-style-type: none; margin-bottom: 10px; border-bottom: 1px solid silver; } .e_tn { border: 1px solid white; margin-right: 5px; } a .e_tn:hover { border: 1px solid silver; } p { margin: 0 0 5px 0; } p.e_body { font-size: 16px; font-weight: bold; margin-bottom: 5px; } p.e_body a { font-weight: normal; color: #666; } span.e_date { color: gray; } a.e_url { color: gray; } #ff_tools .e_more, span.open_comments, span.open_likes { color: blue; cursor: pointer; } a.e_person { text-decoration: none; } #ff_tools, .e_comments, .e_likes { display: none; } #ff_tools { border-bottom: 1px solid silver; } .e_from { font-weight: bold; } .e_comment { margin-bottom: 5px; } .e_comments { margin: 0; padding: 0; } .e_likes { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNqkk89LFGEYx7/vzLrq7raullAEykYUGVJR7HqRjSiSpIMeuniyWx0XvGT9BUL3TkURHQJFSDCKDTskGCFhYf5qXVfQcG3NVtfZd955+85oWyR2qIHheWbm/X7e7/M+zwitNf7nEn8C7FcixFdJ7aDNcRBzPzMfYz7MeHffVV3YE0Bxgovui4aeqIjEoP11VCvoYg4qPw5rpi9NUHdthx7ZBbBTokNHEv3GwS7oQA3U5ic41iq0vcVVVTCqjkJIA9bCU1hfRjrrr+mBMkCmRB1Dxmx+EnKMdaitNCC/w7EtGtjahjgaorIBZmUzvo1eL9BJ46Eu/dVwKbSdNBp7Q9rPdRbF9gZSgzk+lABVwusXTGUB9voElDWHwInekKOQdLUegLR2URuHw521XSRQ4vyVai9qJdGaKDCnGxey9g6+yDEQ0P47oAmVYVpeK4u8aJeYl3Zy6eWORWewoBSiZQBLKHp1KhsvB95gfnpxW6jkTrSQmVtF6vmiBwMPjJuavxwopCGLzPxoSRzHzMcssnPLntAVLKTzmJ3KIxYPQxgBrvdBWph1tb6dEoZkbvy0EWlAILiEltYoJt7OY/J9lq4kgtUCsVgNggETvvApFHMZyBKGym3M94t6Qj6H4o9CjpyCKkyyATwPttGxf7YRMIOchfBlZJ7dKPBIjpy5pVe8Emo79Yo7YZuzD1nfYZjhOIRZR/va67/hP4CK+oscsAtY/TDo7t7tineN8tJjcYnncS/Y1BP17z8Jo4J8x4ZtKWwsTWN5tC/NnW+eva2H9/yZsg+EOyQ9dNTGVsXYSVA0xnuYd9+5O3/5mf7l+iHAAP3UjrcWL0PwAAAAAElFTkSuQmCC); background-repeat: no-repeat; padding-left: 20px; } .e_comment { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXRJREFUeNpi/P//PwMlgImBQsACY0R27BQCUpOBOASI2fDo2Q3EWcsr3O+AeSAvgHBE+46NKw/e+v/7z9//+MCWk/f/A9XeBmI2kD4WqO08QMo7xFaF4fMPBoaff/4z/AUGzb9/DGD6L4j+95+BEaTITIHh+PUXKneffzQAck/BvCAkwsfBzMTIyPDj93+Gf/+RNIIN+s/w5x/EQBDg4wL7UBQlDGAApvEfks0IV+COhXdvPv0AqgX6iQmi8A/YVojNMAwMMbDiLz9+g/XADQCG6BcgtXXN4TsMIsDQUBBmZFAWYWJQE2Ni4GFnZPj7l4FBko+RQU+amWHrqQcMt59+AMXABZBeRlhCQorGcCBmBom5GsoyJLlrgeU/fPnJsO7oXYbd5x9fAXIDMaIRGwZG1Z3n777+n7vjKijqvgNxLboaRlxJGeiiXkd9maL9F598BXJrgTb2Y1OH1QCgZiUgtQeI24Aa5+BLyowDnpkoNgAgwACtnv0bwuZFOAAAAABJRU5ErkJggg%3D%3D); background-repeat: no-repeat; padding-left: 20px; list-style-type: none; } #ff_entries .e_more { display: inline-block; cursor: pointer; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJYSURBVHjanFRLTxNRFD4zfdFhtNjII0VNgQbjRrEx1YUuEHGhLgy6gJWuhBj/AhqDe3fGGDe40Shx4WM1mGoiUdFoWFhj01TSlAqIpa3ttNOZtp5zuVNaU5PCl3wzc++533fPmfsQhq6/g38gIc8hB5EBpI/3R5DzyCDyBVKtFVnpMXvzGGucuvF+2GETJ3o7pRFvhwQetwN2yXYWW88W/Ymk5l9cVSeiK+pTTS/fRZ3CdRtGvDGGBlP7u+W+vi6pOlOhWGJvp90C1E/0Lqsj35eyh1AziWYPKS7w0ob3tTvvHO7Z6XO12qAZpHM6fPmRicR+5a9iU6GMJKtFGO90OXyCIEBGNZoyorGkSSQL40apMifSj3XL9guy0wJpVWc8e6QDrp3x4mAAt2xj30MHd1fjJklDWvIgo8EWu8gyMflqYY3Nej7QBQuLGfgQTsGBPTJj7TgiacmDSgvgxPAnv1nS52gavsWzTDjQ44IHr+NwtL8NTg+0w/OPK7CU1KBSqdRWGiA7X9EoQ65QwiXWYTmlQWwtD9PBOBsxdsKDM+vw8tMqyC1WuHxyL5TLFSAfk+TBlj+VM8DAoFEqmwE22MQOp7Xhdy0oo0ge94pubJoQRjETwn0lxsqmBaD37WfRRj4RkW/7Ovh7XYzhRA4evU3A6PHuDdPZGPxc1xoZzYv87NTBzGbqcRj6Pa0smzdff7P/9B8EaWfTeZhGXoTtYQZ5SeSn+B4ytA2TENeqIu+gU3xri2YhrlGq1wgHnWLa0leaKHOGZ6LU3Uc1oMAc8slWL7a/AgwAc7UDNrBJ/NQAAAAASUVORK5CYII%3D); background-repeat: no-repeat; width: 18px; height: 18px; } .e_files { margin-bottom: 5px; } .e_thumbnails > a { display: none; } .e_thumbnails.open > a, .e_thumbnails > a:first-child { display: inline; } EOCSS; } function get_js() { return <<