cjohnson496
3/12/2017 - 8:24 PM

Convert the messages.json file from an exported Flowdock flow into a static HTML page.

Convert the messages.json file from an exported Flowdock flow into a static HTML page.

#!/usr/bin/env ruby

# Convert an exported Flowdock flow into a static HTML document.
#
# Usage:
#
#    flowdock-archive-to-html messages.json > messages.html
#
# The script assumes that there is a subdirectory `files` containing all files referenced in the exported flow. (This is exactly the
# directory structure you get if you unzip an archive downloaded from Flowdock.)
#
# Only flow events of the types “message”, “comment”, and “file” are handled and thus included in the output. To include
# other events like “user-edit” or “mail”, add the appropriate formatting code to the huge `case` statement at the end of
# this file.

require "bundler/inline"
require "erb"
require "json"

gemfile do
  source "https://rubygems.org"
  gem "redcarpet", "~> 2.3.0"
end

# Add “userid => username” mappings for all users you want to identify by name. Missing users will show up as “User 123456”.
@usernames = {
  "0" => "Flowdock",
}

HTML_HEADER = <<~HTML.freeze
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <style type="text/css">
      html {
        font-family: sans-serif;
        font-size: 14px;
        line-height: 1.5;
      }
      .message {
        display: flex;
        flex-direction: row;
        margin: 0 0 1em;
        padding: 0 0 1em;
        border-bottom: 1px solid #f4f4f4;
      }
      .message p {
        margin: 0;
        overflow-wrap: break-word;
      }
      .message pre {
        background: #eee;
        padding: 0.5em 1em;
        max-width: 100%;
        overflow: scroll;
      }
      .message img {
        max-width: 75%;
        max-height: 50vh;
      }
      .message blockquote {
        background: #f6f6f9;
        padding: 0.5em 1em;
        border-left: 3px solid #9898B0;
        margin: 0 0 1em;
        color: #2E2E75;
      }
      .date {
        flex: 0 14em;
        color: #999;
        font-size: 80%;
        text-align: right;
      }
      .user {
        flex: 0 5em;
        color: #999;
        font-weight: bold;
        text-align: right;
        margin-right: 1em;
      }
      .content {
        min-width: 0; /* To make `max-width: 100%` work in contained elements, see http://stackoverflow.com/a/31972181/566850 */
        flex: 1;
      }
      </style>
    </head>
    <body>
HTML

HTML_FOOTER = <<~HTML.freeze
  </body>
</html>
HTML

@markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(escape_html: true, hard_wrap: true), autolink: true, no_intra_emphasis: true, space_after_headers: true)

def h(str)
  ERB::Util.h(str)
end

def markdown(str)
  @markdown.render(str.to_s)
end

def render_message(message, content)
  date = Time.at(message['sent'] / 1000)
  user = @usernames.fetch(message['user']) do
    fallback_username = "User #{message['user']}"
    $stderr.puts "WARNING: No username mapping for userid #{message['user']} - using '#{fallback_username}' instead."
    @usernames[message['user']] = fallback_username
  end
  puts <<~HTML
    <div class='message'>
      <div class='user'>#{h user}</div>
      <div class='content'>#{content}</div>
      <div class='date'>#{date.strftime('%H:%M – %b. %d, %Y')}</div>
    </div>
  HTML
end

puts HTML_HEADER
JSON.parse(File.read("messages.json")).each do |message|
  event = message["event"]
  case event
  when "message", "comment"
    content =
      if event == "comment"
        message['content']['title'].gsub(/^/, '> \1') + "\n\n" + message['content']['text']
      else
        message['content']
      end
    render_message(message, markdown(content))
  when "file"
    path = "files/" + message["content"]["path"].gsub(%r{\A/files/\d+/}, "").tr("/", "_")
    link_text =
      if message["content"].key?("image")
        "<img src='#{path}'>"
      else
        h(message["content"]["file_name"])
      end
    render_message(message, "<a href='#{path}'>#{link_text}</a>")
  when "action", "user-edit", "mail", "open-invitation-enable", "line", "status", "discussion", "activity"
    # ignore
  else
    $stderr.puts "WARNING: Unknown event type in JSON data: `#{message['event']}`"
    $stderr.puts message.inspect + "\n\n"
  end
end
puts HTML_FOOTER