From 50d904506391968c72da2bdeb3115941dd6377d0 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Tue, 23 Feb 2016 17:57:10 +0100 Subject: [PATCH 01/19] Add HTML formatter. Still WIP, but it works. --- config.yaml | 3 + formatters/html-data/dialog-footer.template | 3 + formatters/html-data/dialog-header.template | 12 +++ formatters/html-data/index.template | 11 +++ .../html-data/telegram-history-dump.css | 68 ++++++++++++++ formatters/html.rb | 93 +++++++++++++++++++ 6 files changed, 190 insertions(+) create mode 100644 formatters/html-data/dialog-footer.template create mode 100644 formatters/html-data/dialog-header.template create mode 100644 formatters/html-data/index.template create mode 100644 formatters/html-data/telegram-history-dump.css create mode 100644 formatters/html.rb diff --git a/config.yaml b/config.yaml index d69dea3..9ec9cd1 100644 --- a/config.yaml +++ b/config.yaml @@ -32,6 +32,9 @@ # date_format: '%Y-%m-%d %H:%M:%S' # bare: # pisg: + # html: + # paginate: 1500 + # use_utc_time: false # Target directory for the backup files # It his is a relative path it will be relative to the script's directory diff --git a/formatters/html-data/dialog-footer.template b/formatters/html-data/dialog-footer.template new file mode 100644 index 0000000..f0ee2ab --- /dev/null +++ b/formatters/html-data/dialog-footer.template @@ -0,0 +1,3 @@ + + + diff --git a/formatters/html-data/dialog-header.template b/formatters/html-data/dialog-header.template new file mode 100644 index 0000000..e9d7286 --- /dev/null +++ b/formatters/html-data/dialog-header.template @@ -0,0 +1,12 @@ + + + + + %s + + + +
+ Back to index +
+

%s

diff --git a/formatters/html-data/index.template b/formatters/html-data/index.template new file mode 100644 index 0000000..6cf6785 --- /dev/null +++ b/formatters/html-data/index.template @@ -0,0 +1,11 @@ + + + + + Telegram History Dump + + + + %s + + diff --git a/formatters/html-data/telegram-history-dump.css b/formatters/html-data/telegram-history-dump.css new file mode 100644 index 0000000..b65cbeb --- /dev/null +++ b/formatters/html-data/telegram-history-dump.css @@ -0,0 +1,68 @@ +body { + width: 700px; + margin: 0 auto 0 auto; +} + +.msg { + box-sizing: border-box; + float: left; + width: auto; + max-width: 80%; + position: relative; + clear: both; + + background: #dcebfe; + background-image: linear-gradient(bottom, #bee2ff 15%%, #dcebfe 100%%); + border: solid 1px rgba(0,0,0,0.5); + border-radius: 20px; + box-shadow: inset 0 8px 5px rgba(255,255,255,0.65), 0 1px 2px rgba(0,0,0,0.2); + + margin-bottom: 10px; + padding: 6px 20px; + word-wrap: break-word; +} + +.msg:before, .msg:after { + border-radius: 20px / 5px; + content: ''; + display: block; + position: absolute; +} +.msg:before { + border: 10px solid transparent; + border-bottom-color: rgba(0,0,0,0.5); + bottom: 0px; + left: -7px; + z-index: -2; +} +.msg:after { + border: 8px solid transparent; + border-bottom-color: #bee2ff; + bottom: 1px; + left: -5px; +} + +.out { + float: right; + background: #bcf6a6; + background-image: linear-gradient(bottom, #bee2ff 15%%, #bcf6a6 100%%); +} +.out:before { + left: auto; + right: -7px; +} +.out:after { + left: auto; + right: -5px; + + border-bottom-color: #d9ffbe; +} + +.dialog-info { + margin-top: 15px; +} + +.navigation { + clear: both; +} + diff --git a/formatters/html.rb b/formatters/html.rb new file mode 100644 index 0000000..4947c57 --- /dev/null +++ b/formatters/html.rb @@ -0,0 +1,93 @@ +require_relative 'lib/formatter_base' +require 'cgi' # For HTML encoding + +class HtmlFormatter < FormatterBase + + NAME = 'html' + + def start_backup(dialogs) + FileUtils.remove_dir(output_dir, true) + FileUtils.mkdir_p(output_dir) + FileUtils.cp('formatters/html-data/telegram-history-dump.css', output_dir) + + @html_template_index = File.read('formatters/html-data/index.template') + @html_template_header = File.read('formatters/html-data/dialog-header.template') + @html_template_footer = File.read('formatters/html-data/dialog-footer.template') + + dialog_list_html = '' + dialogs.each do |dialog| + safe_name = get_safe_name(dialog['print_name']) + # ToDo: replace 'Chat with' and 'Group chat' with icons + if dialog['type'] != 'user' + dialog_title = 'Group chat: %s' % CGI::escapeHTML(dialog['print_name']) + else + dialog_title = 'Chat with: %s' % CGI::escapeHTML(dialog['print_name']) + end + dialog_list_html += '
%s
' % [safe_name, dialog_title] + end + index_file = File.join(output_dir, 'index.html') + File.open(index_file, 'w:UTF-8') do |stream| + stream.puts(@html_template_index % dialog_list_html) + end + end + + def format_dialog(dialog, messages) + if dialog['type'] != 'user' + dialog_title = 'Group chat: %s' % CGI::escapeHTML(dialog['print_name']) + else + dialog_title = 'Chat with: %s' % CGI::escapeHTML(dialog['print_name']) + end + safe_name = get_safe_name(dialog['print_name']) + current_filename = File.join(output_dir, safe_name + '-0.html') + file = File.open(current_filename, 'w:UTF-8') + file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title]) + + message_count = 0 + page_count = 0 + messages.reverse_each do |msg| + if msg['text'] + if not msg['out'] and dialog['type'] != 'user' + author = '
%s
'% msg['from']['print_name'] + else + author = '' + end + + date = Time.at(msg['date']) + if $config['formatters']['html']['use_utc_time'] + date = "#{date.utc} UTC" + end + + file.puts('
%s%s
' % [(msg['out'] ? 'out' : 'in'), date, author, CGI::escapeHTML(msg['text'])]) + else + # TODO: media + end + + message_count += 1 + if message_count > $config['formatters']['html']['paginate'] + # We reached our message limit on this page; paginate! + # Is there a previous page? If yes, link to it. + navigation = '' + if page_count > 0 + navigation += 'Previous page' % current_filename + end + + page_count += 1 + message_count = 0 + + # Link to the next page and end the file + current_filename = File.join(output_dir, "#{safe_name}-%s.html" % page_count) + navigation += 'Next page' % current_filename + file.puts(@html_template_footer % navigation) + file.close() + + # Open a new file and write the header again + file = File.open(current_filename, 'w:UTF-8') + file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), ('Page %i' % (page_count + 1) if page_count > 0), dialog_title]) + end + end + file.puts(@html_template_footer % '') + file.close() + end + +end + From 2e13300041c205e825e5b0de2e543646c51a0db9 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Tue, 23 Feb 2016 22:34:48 +0100 Subject: [PATCH 02/19] Nicer HTML index page --- formatters/html-data/index.template | 2 +- .../html-data/telegram-history-dump.css | 34 +++++++++++++++++-- formatters/html.rb | 15 ++++---- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/formatters/html-data/index.template b/formatters/html-data/index.template index 6cf6785..5fb4110 100644 --- a/formatters/html-data/index.template +++ b/formatters/html-data/index.template @@ -3,7 +3,7 @@ Telegram History Dump - + %s diff --git a/formatters/html-data/telegram-history-dump.css b/formatters/html-data/telegram-history-dump.css index b65cbeb..9d04c9f 100644 --- a/formatters/html-data/telegram-history-dump.css +++ b/formatters/html-data/telegram-history-dump.css @@ -12,7 +12,7 @@ body { clear: both; background: #dcebfe; - background-image: linear-gradient(bottom, #bee2ff 15%%, #dcebfe 100%%); + background-image: linear-gradient(bottom, #bee2ff 15%, #dcebfe 100%); border: solid 1px rgba(0,0,0,0.5); border-radius: 20px; box-shadow: inset 0 8px 5px rgba(255,255,255,0.65), 0 1px 2px rgba(0,0,0,0.2); @@ -45,7 +45,7 @@ body { .out { float: right; background: #bcf6a6; - background-image: linear-gradient(bottom, #bee2ff 15%%, #bcf6a6 100%%); + background-image: linear-gradient(bottom, #bee2ff 15%, #bcf6a6 100%); } .out:before { left: auto; @@ -58,6 +58,16 @@ body { border-bottom-color: #d9ffbe; } +.dialog { + float: left !important; +} + +.dialog .icon { + width: 23px; + height: 20px; + display: inline-block; +} + .dialog-info { margin-top: 15px; } @@ -66,3 +76,23 @@ body { clear: both; } +.icon { + background-size: 100% 100%; +} + +/* Credits: http://www.flaticon.com/free-icon/multiple-users-silhouette_33308 */ +.img-group { + background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgODAuMTMgODAuMTMiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDgwLjEzIDgwLjEzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTQ4LjM1NSwxNy45MjJjMy43MDUsMi4zMjMsNi4zMDMsNi4yNTQsNi43NzYsMTAuODE3YzEuNTExLDAuNzA2LDMuMTg4LDEuMTEyLDQuOTY2LDEuMTEyICAgYzYuNDkxLDAsMTEuNzUyLTUuMjYxLDExLjc1Mi0xMS43NTFjMC02LjQ5MS01LjI2MS0xMS43NTItMTEuNzUyLTExLjc1MkM1My42NjgsNi4zNSw0OC40NTMsMTEuNTE3LDQ4LjM1NSwxNy45MjJ6IE00MC42NTYsNDEuOTg0ICAgYzYuNDkxLDAsMTEuNzUyLTUuMjYyLDExLjc1Mi0xMS43NTJzLTUuMjYyLTExLjc1MS0xMS43NTItMTEuNzUxYy02LjQ5LDAtMTEuNzU0LDUuMjYyLTExLjc1NCwxMS43NTJTMzQuMTY2LDQxLjk4NCw0MC42NTYsNDEuOTg0ICAgeiBNNDUuNjQxLDQyLjc4NWgtOS45NzJjLTguMjk3LDAtMTUuMDQ3LDYuNzUxLTE1LjA0NywxNS4wNDh2MTIuMTk1bDAuMDMxLDAuMTkxbDAuODQsMC4yNjMgICBjNy45MTgsMi40NzQsMTQuNzk3LDMuMjk5LDIwLjQ1OSwzLjI5OWMxMS4wNTksMCwxNy40NjktMy4xNTMsMTcuODY0LTMuMzU0bDAuNzg1LTAuMzk3aDAuMDg0VjU3LjgzMyAgIEM2MC42ODgsNDkuNTM2LDUzLjkzOCw0Mi43ODUsNDUuNjQxLDQyLjc4NXogTTY1LjA4NCwzMC42NTNoLTkuODk1Yy0wLjEwNywzLjk1OS0xLjc5Nyw3LjUyNC00LjQ3LDEwLjA4OCAgIGM3LjM3NSwyLjE5MywxMi43NzEsOS4wMzIsMTIuNzcxLDE3LjExdjMuNzU4YzkuNzctMC4zNTgsMTUuNC0zLjEyNywxNS43NzEtMy4zMTNsMC43ODUtMC4zOThoMC4wODRWNDUuNjk5ICAgQzgwLjEzLDM3LjQwMyw3My4zOCwzMC42NTMsNjUuMDg0LDMwLjY1M3ogTTIwLjAzNSwyOS44NTNjMi4yOTksMCw0LjQzOC0wLjY3MSw2LjI1LTEuODE0YzAuNTc2LTMuNzU3LDIuNTktNy4wNCw1LjQ2Ny05LjI3NiAgIGMwLjAxMi0wLjIyLDAuMDMzLTAuNDM4LDAuMDMzLTAuNjZjMC02LjQ5MS01LjI2Mi0xMS43NTItMTEuNzUtMTEuNzUyYy02LjQ5MiwwLTExLjc1Miw1LjI2MS0xMS43NTIsMTEuNzUyICAgQzguMjgzLDI0LjU5MSwxMy41NDMsMjkuODUzLDIwLjAzNSwyOS44NTN6IE0zMC41ODksNDAuNzQxYy0yLjY2LTIuNTUxLTQuMzQ0LTYuMDk3LTQuNDY3LTEwLjAzMiAgIGMtMC4zNjctMC4wMjctMC43My0wLjA1Ni0xLjEwNC0wLjA1NmgtOS45NzFDNi43NSwzMC42NTMsMCwzNy40MDMsMCw0NS42OTl2MTIuMTk3bDAuMDMxLDAuMTg4bDAuODQsMC4yNjUgICBjNi4zNTIsMS45ODMsMTIuMDIxLDIuODk3LDE2Ljk0NSwzLjE4NXYtMy42ODNDMTcuODE4LDQ5Ljc3MywyMy4yMTIsNDIuOTM2LDMwLjU4OSw0MC43NDF6IiBmaWxsPSIjMDAwMDAwIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==); +} + +/* Credits: http://www.flaticon.com/free-icon/user-black-close-up-shape_32438 */ +.img-single-user { + background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNDY2LjE0NiA0NjYuMTQ2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0NjYuMTQ2IDQ2Ni4xNDY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8cGF0aCBkPSJNMjg5LjI4NSwxOTEuODZjMjguODQ0LTE4LjUzOSw0Ny45OTUtNTAuODMsNDcuOTk1LTg3LjY1NEMzMzcuMjgsNDYuNjU5LDI5MC42MjEsMCwyMzMuMDg4LDAgICBjLTU3LjU1OSwwLTEwNC4yMDcsNDYuNjU5LTEwNC4yMDcsMTA0LjIwN2MwLDM2LjgyNCwxOS4xNTEsNjkuMTIxLDQ3Ljk5Niw4Ny42NTRjLTY3Ljk1OSw2LjA4Mi0xMjEuNDIyLDYzLjMzMS0xMjEuNDIyLDEzMi44NTQgICB2MTA4LjE1NWwwLjI3NCwxLjY5bDcuNDU3LDIuMzI4YzcwLjE5NiwyMS45MjksMTMxLjE5NSwyOS4yNTksMTgxLjQwMSwyOS4yNTljOTguMDQ4LDAsMTU0Ljg4Ni0yNy45NywxNTguNDA4LTI5Ljc0M2w2Ljk2My0zLjUzNCAgIGgwLjczMlYzMjQuNzE0QzQxMC42OTgsMjU1LjE5NywzNTcuMjUzLDE5Ny45NTcsMjg5LjI4NSwxOTEuODZ6IiBmaWxsPSIjMDAwMDAwIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==); +} + +.author { + font-style: italic; + display: inline-block; + color: #666152; +} + diff --git a/formatters/html.rb b/formatters/html.rb index 4947c57..ad2b1ab 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -17,13 +17,12 @@ def start_backup(dialogs) dialog_list_html = '' dialogs.each do |dialog| safe_name = get_safe_name(dialog['print_name']) - # ToDo: replace 'Chat with' and 'Group chat' with icons if dialog['type'] != 'user' - dialog_title = 'Group chat: %s' % CGI::escapeHTML(dialog['print_name']) + dialog_rendering = '' else - dialog_title = 'Chat with: %s' % CGI::escapeHTML(dialog['print_name']) + dialog_rendering = '' end - dialog_list_html += '' % [safe_name, dialog_title] + dialog_list_html += "
#{dialog_rendering} %s
" % [('out' if dialog['type'] == 'user'), CGI::escapeHTML(dialog['print_name'])] end index_file = File.join(output_dir, 'index.html') File.open(index_file, 'w:UTF-8') do |stream| @@ -35,7 +34,7 @@ def format_dialog(dialog, messages) if dialog['type'] != 'user' dialog_title = 'Group chat: %s' % CGI::escapeHTML(dialog['print_name']) else - dialog_title = 'Chat with: %s' % CGI::escapeHTML(dialog['print_name']) + dialog_title = 'Chat with %s' % CGI::escapeHTML(dialog['print_name']) end safe_name = get_safe_name(dialog['print_name']) current_filename = File.join(output_dir, safe_name + '-0.html') @@ -47,7 +46,7 @@ def format_dialog(dialog, messages) messages.reverse_each do |msg| if msg['text'] if not msg['out'] and dialog['type'] != 'user' - author = '
%s
'% msg['from']['print_name'] + author = '
%s:
'% msg['from']['print_name'] else author = '' end @@ -57,7 +56,7 @@ def format_dialog(dialog, messages) date = "#{date.utc} UTC" end - file.puts('
%s%s
' % [(msg['out'] ? 'out' : 'in'), date, author, CGI::escapeHTML(msg['text'])]) + file.puts("
#{author} %s
" % [(msg['out'] ? 'out' : 'in'), CGI::escapeHTML(msg['text'])]) else # TODO: media end @@ -82,7 +81,7 @@ def format_dialog(dialog, messages) # Open a new file and write the header again file = File.open(current_filename, 'w:UTF-8') - file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), ('Page %i' % (page_count + 1) if page_count > 0), dialog_title]) + file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) end end file.puts(@html_template_footer % '') From cfdd8c69806e2bbe1d776bb29ae3cb57ca2b6570 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Tue, 23 Feb 2016 23:50:18 +0100 Subject: [PATCH 03/19] m. Replace tabs with spaces; add "export finished" message on html.end_backup --- formatters/html.rb | 56 +++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index ad2b1ab..568add3 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -52,40 +52,44 @@ def format_dialog(dialog, messages) end date = Time.at(msg['date']) - if $config['formatters']['html']['use_utc_time'] - date = "#{date.utc} UTC" - end + if $config['formatters']['html']['use_utc_time'] + date = "#{date.utc} UTC" + end file.puts("
#{author} %s
" % [(msg['out'] ? 'out' : 'in'), CGI::escapeHTML(msg['text'])]) else # TODO: media end - message_count += 1 - if message_count > $config['formatters']['html']['paginate'] - # We reached our message limit on this page; paginate! - # Is there a previous page? If yes, link to it. - navigation = '' - if page_count > 0 - navigation += 'Previous page' % current_filename - end - - page_count += 1 - message_count = 0 - - # Link to the next page and end the file - current_filename = File.join(output_dir, "#{safe_name}-%s.html" % page_count) - navigation += 'Next page' % current_filename - file.puts(@html_template_footer % navigation) - file.close() - - # Open a new file and write the header again - file = File.open(current_filename, 'w:UTF-8') - file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) - end + message_count += 1 + if message_count > $config['formatters']['html']['paginate'] + # We reached our message limit on this page; paginate! + # Is there a previous page? If yes, link to it. + navigation = '' + if page_count > 0 + navigation += 'Previous page' % current_filename + end + + page_count += 1 + message_count = 0 + + # Link to the next page and end the file + current_filename = File.join(output_dir, "#{safe_name}-%s.html" % page_count) + navigation += 'Next page' % current_filename + file.puts(@html_template_footer % navigation) + file.close() + + # Open a new file and write the header again + file = File.open(current_filename, 'w:UTF-8') + file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) + end end file.puts(@html_template_footer % '') - file.close() + file.close() + end + + def end_backup + $log.info("HTML export finished, see: output/formatted/html/index.html") end end From 9374c4bf1e0cf8ba803f2205aa8566e2011971c4 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Tue, 23 Feb 2016 23:54:38 +0100 Subject: [PATCH 04/19] Allow disabling HTML pagination --- formatters/html.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/formatters/html.rb b/formatters/html.rb index 568add3..02cd97e 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -62,7 +62,8 @@ def format_dialog(dialog, messages) end message_count += 1 - if message_count > $config['formatters']['html']['paginate'] + messages_per_page = $config['formatters']['html']['paginate'] + if messages_per_page and message_count > messages_per_page and messages_per_page > 0 # We reached our message limit on this page; paginate! # Is there a previous page? If yes, link to it. navigation = '' From 71a11ff36883924951c466b086bb252523b55a42 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Fri, 26 Feb 2016 00:01:14 +0100 Subject: [PATCH 05/19] Add file support in HTML (inline images, videos, audio) --- formatters/html-data/dialog-header.template | 2 +- .../html-data/telegram-history-dump.css | 5 ++ formatters/html.rb | 70 +++++++++++++------ 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/formatters/html-data/dialog-header.template b/formatters/html-data/dialog-header.template index e9d7286..0133029 100644 --- a/formatters/html-data/dialog-header.template +++ b/formatters/html-data/dialog-header.template @@ -2,7 +2,7 @@ - %s + Chat: %s diff --git a/formatters/html-data/telegram-history-dump.css b/formatters/html-data/telegram-history-dump.css index 9d04c9f..af912e1 100644 --- a/formatters/html-data/telegram-history-dump.css +++ b/formatters/html-data/telegram-history-dump.css @@ -96,3 +96,8 @@ body { color: #666152; } +.msg img, .msg video { + max-width: 525px; + max-height: 500px; +} + diff --git a/formatters/html.rb b/formatters/html.rb index 02cd97e..88a860b 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -17,12 +17,13 @@ def start_backup(dialogs) dialog_list_html = '' dialogs.each do |dialog| safe_name = get_safe_name(dialog['print_name']) + html_safe_name = CGI::escapeHTML(safe_name) if dialog['type'] != 'user' dialog_rendering = '' else dialog_rendering = '' end - dialog_list_html += "
#{dialog_rendering} %s
" % [('out' if dialog['type'] == 'user'), CGI::escapeHTML(dialog['print_name'])] + dialog_list_html += "
#{dialog_rendering} %s
" % [('out' if dialog['type'] == 'user'), CGI::escapeHTML(dialog['print_name'])] end index_file = File.join(output_dir, 'index.html') File.open(index_file, 'w:UTF-8') do |stream| @@ -38,32 +39,57 @@ def format_dialog(dialog, messages) end safe_name = get_safe_name(dialog['print_name']) current_filename = File.join(output_dir, safe_name + '-0.html') - file = File.open(current_filename, 'w:UTF-8') - file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title]) + backup_file = File.open(current_filename, 'w:UTF-8') + backup_file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title]) message_count = 0 page_count = 0 messages.reverse_each do |msg| + if not msg['out'] and dialog['type'] != 'user' + # If this is an incoming message in a group chat, display the author + author = '
%s:
'% msg['from']['print_name'] + else + author = '' + end + + date = Time.at(msg['date']) + if $config['formatters']['html']['use_utc_time'] + date = "#{date.utc} UTC" + end + if msg['text'] - if not msg['out'] and dialog['type'] != 'user' - author = '
%s:
'% msg['from']['print_name'] + backup_file.puts("
#{author} %s
" % [(msg['out'] ? 'out' : 'in'), CGI::escapeHTML(msg['text'])]) + elsif msg['media'] and msg['media']['file'] # TODO: handle other media types, e.g. webpage + relative_path = File.join("../../media", safe_name, File.basename(msg['media']['file'])) + extension = File.extname(msg['media']['file']) + if msg['media']['type'] == 'photo' or ['png', 'jpg', 'gif', 'svg', 'jpeg', 'bmp', 'webp'].include? extension[1..-1] + # Note: webp is almost certainly a sticker; special support for those is to do (although the need is + # questionable as they are inlined already). + file = "" + if msg['media']['caption'] + file += '
' + msg['media']['caption'] + end else - author = '' - end - - date = Time.at(msg['date']) - if $config['formatters']['html']['use_utc_time'] - date = "#{date.utc} UTC" + if msg['media']['type'] == 'audio' or ['mp3', 'wav', 'ogg'].include? extension[1..-1] + filetype = 'audio' + elsif msg['media']['type'] == 'video' or ['mp4', 'mov', '3gp', 'avi', 'webm'].include? extension[1..-1] + filetype = 'video' + else + # documents + file = "Download %s file" % extension + end + if filetype == 'audio' or filetype == 'video' + file = "<#{filetype} src='#{relative_path}' controls>Your browser does not support inline playback.
Download #{filetype}" + end end - file.puts("
#{author} %s
" % [(msg['out'] ? 'out' : 'in'), CGI::escapeHTML(msg['text'])]) - else - # TODO: media + # Regardless of file type, write this :) + backup_file.puts("
#{author}
#{file}
" % [(msg['out'] ? 'out' : 'in')]) end message_count += 1 - messages_per_page = $config['formatters']['html']['paginate'] - if messages_per_page and message_count > messages_per_page and messages_per_page > 0 + messages_per_page = $config['formatters']['html']['paginate'] + if messages_per_page and messages_per_page > 0 and message_count > messages_per_page # We reached our message limit on this page; paginate! # Is there a previous page? If yes, link to it. navigation = '' @@ -77,16 +103,16 @@ def format_dialog(dialog, messages) # Link to the next page and end the file current_filename = File.join(output_dir, "#{safe_name}-%s.html" % page_count) navigation += 'Next page' % current_filename - file.puts(@html_template_footer % navigation) - file.close() + backup_file.puts(@html_template_footer % navigation) + backup_file.close() # Open a new file and write the header again - file = File.open(current_filename, 'w:UTF-8') - file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) + backup_file = File.open(current_filename, 'w:UTF-8') + backup_file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) end end - file.puts(@html_template_footer % '') - file.close() + backup_file.puts(@html_template_footer % '') + backup_file.close() end def end_backup From 3b1e1fe8d2c44903c14ec8fc11845a29fee73083 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sat, 27 Feb 2016 02:29:36 +0100 Subject: [PATCH 06/19] URL parsing; support geo & contact sharing; use get_full_name instead of [print_name]; fixed index links with hash signs. --- .../html-data/telegram-history-dump.css | 19 ++ formatters/html.rb | 197 ++++++++++++++++-- 2 files changed, 201 insertions(+), 15 deletions(-) diff --git a/formatters/html-data/telegram-history-dump.css b/formatters/html-data/telegram-history-dump.css index af912e1..92b9332 100644 --- a/formatters/html-data/telegram-history-dump.css +++ b/formatters/html-data/telegram-history-dump.css @@ -101,3 +101,22 @@ body { max-height: 500px; } +.msg .webpage, .msg .geo, .msg .contact { + border-left: 3px solid #bbb; + padding-left: 5px; +} + +.msg-service { + clear: both; + text-align: center; +} + +.msg-service .inner { + margin-bottom: 10px; + color: #fff; + background-color: rgba(0, 0, 0, 0.7); + padding: 5px; + display: inline-block; + border-radius: 6px; +} + diff --git a/formatters/html.rb b/formatters/html.rb index 88a860b..c4a2789 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -1,5 +1,6 @@ require_relative 'lib/formatter_base' require 'cgi' # For HTML encoding +require 'uri' # For URL encoding class HtmlFormatter < FormatterBase @@ -17,13 +18,13 @@ def start_backup(dialogs) dialog_list_html = '' dialogs.each do |dialog| safe_name = get_safe_name(dialog['print_name']) - html_safe_name = CGI::escapeHTML(safe_name) + html_safe_url = CGI::escapeHTML(URI.escape(safe_name)) if dialog['type'] != 'user' dialog_rendering = '' else dialog_rendering = '' end - dialog_list_html += "
#{dialog_rendering} %s
" % [('out' if dialog['type'] == 'user'), CGI::escapeHTML(dialog['print_name'])] + dialog_list_html += "
#{dialog_rendering} %s
" % [('out' if dialog['type'] == 'user'), html_safe_url, CGI::escapeHTML(dialog['print_name'])] end index_file = File.join(output_dir, 'index.html') File.open(index_file, 'w:UTF-8') do |stream| @@ -47,7 +48,7 @@ def format_dialog(dialog, messages) messages.reverse_each do |msg| if not msg['out'] and dialog['type'] != 'user' # If this is an incoming message in a group chat, display the author - author = '
%s:
'% msg['from']['print_name'] + author = '
%s:
'% get_full_name(msg['from']) else author = '' end @@ -57,17 +58,34 @@ def format_dialog(dialog, messages) date = "#{date.utc} UTC" end + msg_body = '' if msg['text'] - backup_file.puts("
#{author} %s
" % [(msg['out'] ? 'out' : 'in'), CGI::escapeHTML(msg['text'])]) - elsif msg['media'] and msg['media']['file'] # TODO: handle other media types, e.g. webpage - relative_path = File.join("../../media", safe_name, File.basename(msg['media']['file'])) + msg_body = replace_urls(msg['text']).gsub("\n", '
') + if msg['media'] and msg['media']['type'] == 'webpage' and msg['media']['description'] + # The webpage URL is already included in the message, only need to display the title here. + # Note that there are messages with msg[text], msg[media] and msg[media][type]=webpage, but + # without either msg[media][description] or msg[media][title] or both... + # I think they are to indicate an inline URL, but it doesn't give the url so we have to figure that + # out for ourselves, and there are also messages that have no media tag but still contain a clickable url. + title = msg['media']['title'] + title = "%s" % CGI::escapeHTML(title) if title.to_s != '' + description = msg['media']['description'] + description = "
%s" % CGI::escapeHTML(description) if description.to_s != '' + if title.to_s != '' and description.to_s != '' + title += '
' + end + msg_body += '
%s%s
' % [title, description] + end + author += ' ' if author != '' # In text messages (unlike media), author is followed by a space, not a new line + elsif msg['media'] and msg['media']['file'] + relative_url = URI.escape(File.join("../../media", safe_name, File.basename(msg['media']['file']))) extension = File.extname(msg['media']['file']) if msg['media']['type'] == 'photo' or ['png', 'jpg', 'gif', 'svg', 'jpeg', 'bmp', 'webp'].include? extension[1..-1] # Note: webp is almost certainly a sticker; special support for those is to do (although the need is # questionable as they are inlined already). - file = "" + msg_body = "" if msg['media']['caption'] - file += '
' + msg['media']['caption'] + msg_body += '
' + msg['media']['caption'] end else if msg['media']['type'] == 'audio' or ['mp3', 'wav', 'ogg'].include? extension[1..-1] @@ -76,15 +94,47 @@ def format_dialog(dialog, messages) filetype = 'video' else # documents - file = "Download %s file" % extension + msg_body = "Download #{extension} file" end if filetype == 'audio' or filetype == 'video' - file = "<#{filetype} src='#{relative_path}' controls>Your browser does not support inline playback.
Download #{filetype}" + msg_body = "<#{filetype} src='#{relative_url}' controls>Your browser does not support inline playback.
Download #{filetype}" end end - - # Regardless of file type, write this :) - backup_file.puts("
#{author}
#{file}
" % [(msg['out'] ? 'out' : 'in')]) + author += '
' if author != '' # In file messages (unlike text messages), author is followed by a new line + elsif msg['media'] and msg['media']['type'] == 'geo' + lat = msg['media']['latitude'].to_s + lon = msg['media']['longitude'].to_s + msg_body = "" + elsif msg['media'] and msg['media']['type'] == 'contact' + phone = msg['media']['phone'] + first = msg['media']['first_name'] + last = msg['media']['last_name'] + msg_body = "
Contact: #{first} #{last}, +#{phone}
" + elsif msg['event'] == 'service' or msg['service'] + if get_full_name(msg['from']) != '' # Some messages have no properly filled 'from' + text = CGI::escapeHTML(get_full_name(msg['from'])) + else + text = '(Unknown user)' + end + text += ' ' + if msg['action']['type'] == 'chat_add_user' + text += "added %s" % CGI::escapeHTML(get_full_name(msg['action']['user'])) + elsif msg['action']['type'] == 'chat_rename' + text += "changed group name to «%s»" % CGI::escapeHTML(msg['action']['title']) + elsif msg['action']['type'] == 'chat_change_photo' + text += "updated group photo" + elsif msg['action']['type'] == 'chat_created' + text += "created group «%s»" % CGI::escapeHTML(msg['action']['title']) + elsif msg['action']['type'] == 'chat_del_user' + text += "removed %s" % CGI::escapeHTML(get_full_name(msg['action']['user'])) + else + text += CGI::escapeHTML(msg['action'].to_s) + end + backup_file.puts("
#{text}
") + end + if msg_body != '' + in_out = (msg['out'] ? 'out' : 'in') + backup_file.puts("
#{author}#{msg_body}
") end message_count += 1 @@ -94,14 +144,14 @@ def format_dialog(dialog, messages) # Is there a previous page? If yes, link to it. navigation = '' if page_count > 0 - navigation += 'Previous page' % current_filename + navigation += 'Previous page' % CGI::escapeHTML(URI.escape(safe_name)) end page_count += 1 message_count = 0 # Link to the next page and end the file - current_filename = File.join(output_dir, "#{safe_name}-%s.html" % page_count) + current_filename = File.join(output_dir, "%s-%s.html" % [CGI::escapeHTML(URI.escape(safe_name)), page_count]) navigation += 'Next page' % current_filename backup_file.puts(@html_template_footer % navigation) backup_file.close() @@ -119,5 +169,122 @@ def end_backup $log.info("HTML export finished, see: output/formatted/html/index.html") end + def replace_urls(text) + # This function does not recognize geo: 'urls', which is too bad, but sort of by + # design. They do not look like URLs with that comma in there and I did not want + # to get into the business of keeping track of every arcane format out there. + + # This function returns HTML-encoded text with all identifiable URLs made linkable. + # The reason it also does HTML encoding is because it can't be done afterwards (that + # would escape the a-href tags) and if it would require the text to be escaped + # beforehand then it might as well do it by itself. + + # We don't use Ruby's URI.extract because it matches only URLs that include + # a protocol. Telegram also recognizes URLs like example.com or + # example.com/search?q=x, so we will try to replicate that behavior at the trade off + # of some false positives. + + # The function also replaces @usernames and email addresses. Regarding the latter, + # at least it makes a reasonable attempt. + + # The regex looks, case-insensitive, for: an optional protocol, then an optional + # user:password part followed by an @-sign, then example.gtld, optionally a port, and + # optionally /something?query&morequery of any length and containing any character, + # until a space character appears. It should have some support for IDN domains as + # well, but it's almost certainly not perfect if you try something beyond basic + # accents or circumflexes or something, and even then. + + # TODO: IP addresses. IPv6 addresses. I completely forgot about those. + + urls = text.scan(/(^|\s|<)(([a-zA-Z]{1,25}:)?([^@\s]{1,200}@)?(\/\/)?([a-zA-Z][a-zA-Z0-9-]{0,63}\.){0,125}[a-zA-Z][a-zA-Z0-9-]{0,63}\.([a-zA-Z]{2,63}|xn--[a-zA-Z0-9]{1,60})(:[1-9][0-9]{0,4})?(\/[!-~]*)?(\s|>|$|\)|\.))/) + + text = CGI::escapeHTML(text) + + urls.each do |url| + url = url[1] + escaped_url = CGI::escapeHTML(url) + + if text.index(escaped_url) == nil + # If there are duplicate URLs, we might already have replaced it. + next + end + + # The last character is a dot followed by a space or end of message, the person probably just ended the sentence. + at_end = (text.index(escaped_url) + url.length) == text.length + if url[-2..-1] =~ /\.\s/ + url = url[0..-3] + elsif url[-1] == '.' and at_end + url = url[0..-2] + end + + # The last character is a ), unless the URL also contains a ( or the text + # message contains no (, let's assume the URL was between parenthesis + if (url.index('(') == nil and text.gsub(escaped_url, '').index('(') != nil) + if url[-2..-1] =~ /\)\s/ + url = url[0..-3] + elsif url[-1] == ')' and at_end + url = url[0..-2] + end + end + + if url[-1] =~ /\s/ # Remove trailing whitespace, if any + url = url[0..-2] + end + + # Email addresses and URLs are still fairly often enclosed in + # . Catch this. + if url[0] == '<' + url = url[1..-1] + end + if url[-1] == '>' + url = url[0..-2] + end + + # If there is no protocol part, check whether it's an email + # address. Otherwise default to http. + if url[0..26].index(':') == nil + if url.index('@') != nil + new_url = "mailto:#{url}" + else + new_url = "http://#{url}" + end + else + new_url = url + end + + # No URI.escape here because it would replace the hash sign + escaped_url = CGI::escapeHTML(url) + new_escaped_url = CGI::escapeHTML(new_url) + text = text.gsub(escaped_url, "#{escaped_url}") + end + + usernames = text.scan(/(@[a-zA-Z0-9_]{5,32})([^a-zA-Z0-9_]|$)/) + usernames.each do |username| + username = username[0] + + # Check whether this username is part of a URL, in which case it's probably part of an + # email address or something like ftp://user:pass@example.net. + should_skip = false + urls.each do |url| + url = url[1] + if url.index(username) != nil + should_skip = true + break + end + end + + next if should_skip + + if text.index(username) == nil + # If there are duplicate URLs, we might already have replaced it. + next + end + + text = text.gsub(username, "#{username}") + end + + return text + end + end From 6f525f0165e492b8b59ba7c8a8fb2249a03b46f0 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sat, 27 Feb 2016 02:35:59 +0100 Subject: [PATCH 07/19] Relative path for prev/next page --- formatters/html.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index c4a2789..0466562 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -144,7 +144,7 @@ def format_dialog(dialog, messages) # Is there a previous page? If yes, link to it. navigation = '' if page_count > 0 - navigation += 'Previous page' % CGI::escapeHTML(URI.escape(safe_name)) + navigation += 'Previous page' % [CGI::escapeHTML(URI.escape(safe_name)), page_count] end page_count += 1 @@ -152,7 +152,7 @@ def format_dialog(dialog, messages) # Link to the next page and end the file current_filename = File.join(output_dir, "%s-%s.html" % [CGI::escapeHTML(URI.escape(safe_name)), page_count]) - navigation += 'Next page' % current_filename + navigation += 'Next page' % File.basename(current_filename) backup_file.puts(@html_template_footer % navigation) backup_file.close() From b569714e8feb2af37f4af71b5da9d92b8f13ccb3 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sat, 27 Feb 2016 02:46:26 +0100 Subject: [PATCH 08/19] Fix dialog titles for humans --- formatters/html.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index 0466562..79be36e 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -18,13 +18,15 @@ def start_backup(dialogs) dialog_list_html = '' dialogs.each do |dialog| safe_name = get_safe_name(dialog['print_name']) + title = safe_name html_safe_url = CGI::escapeHTML(URI.escape(safe_name)) if dialog['type'] != 'user' dialog_rendering = '' + title = dialog['title'] else dialog_rendering = '' end - dialog_list_html += "
#{dialog_rendering} %s
" % [('out' if dialog['type'] == 'user'), html_safe_url, CGI::escapeHTML(dialog['print_name'])] + dialog_list_html += "
#{dialog_rendering} %s
" % [('out' if dialog['type'] == 'user'), html_safe_url, CGI::escapeHTML(title)] end index_file = File.join(output_dir, 'index.html') File.open(index_file, 'w:UTF-8') do |stream| @@ -34,14 +36,16 @@ def start_backup(dialogs) def format_dialog(dialog, messages) if dialog['type'] != 'user' - dialog_title = 'Group chat: %s' % CGI::escapeHTML(dialog['print_name']) + dialog_title = 'Group chat: %s' % CGI::escapeHTML(dialog['title']) + html_title = CGI::escapeHTML(dialog['title']) else dialog_title = 'Chat with %s' % CGI::escapeHTML(dialog['print_name']) + html_title = CGI::escapeHTML(dialog['print_name']) end safe_name = get_safe_name(dialog['print_name']) current_filename = File.join(output_dir, safe_name + '-0.html') backup_file = File.open(current_filename, 'w:UTF-8') - backup_file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title]) + backup_file.puts(@html_template_header % [html_title, dialog_title]) message_count = 0 page_count = 0 @@ -158,7 +162,7 @@ def format_dialog(dialog, messages) # Open a new file and write the header again backup_file = File.open(current_filename, 'w:UTF-8') - backup_file.puts(@html_template_header % [CGI::escapeHTML(dialog['print_name']), dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) + backup_file.puts(@html_template_header % [html_title, dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) end end backup_file.puts(@html_template_footer % '') From c52a0a70f1615d4e94c2c4319a5bd1278773c614 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sat, 27 Feb 2016 14:44:57 +0100 Subject: [PATCH 09/19] Fix crash on dateless messages --- formatters/html.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index 79be36e..922a7b3 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -57,9 +57,13 @@ def format_dialog(dialog, messages) author = '' end - date = Time.at(msg['date']) - if $config['formatters']['html']['use_utc_time'] - date = "#{date.utc} UTC" + if msg['date'] + date = Time.at(msg['date']) + if $config['formatters']['html']['use_utc_time'] + date = "#{date.utc} UTC" + end + else + date = 'Unknown' end msg_body = '' From 472784bd57625ba89635427e8dfb75d8c0e0d580 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sat, 27 Feb 2016 23:28:20 +0100 Subject: [PATCH 10/19] Improved pagination & timestamps every N messages --- config.yaml | 3 +- formatters/html-data/dialog-footer.template | 2 +- formatters/html-data/dialog-header.template | 4 +- formatters/html-data/index.template | 1 + .../html-data/telegram-history-dump.css | 11 ++- formatters/html.rb | 93 ++++++++++++------- 6 files changed, 69 insertions(+), 45 deletions(-) diff --git a/config.yaml b/config.yaml index 9ec9cd1..50c21cb 100644 --- a/config.yaml +++ b/config.yaml @@ -33,8 +33,9 @@ # bare: # pisg: # html: - # paginate: 1500 + # paginate: 1500 # messages per page # use_utc_time: false + # timestamps_every: 50 # messages # Target directory for the backup files # It his is a relative path it will be relative to the script's directory diff --git a/formatters/html-data/dialog-footer.template b/formatters/html-data/dialog-footer.template index f0ee2ab..29d08a4 100644 --- a/formatters/html-data/dialog-footer.template +++ b/formatters/html-data/dialog-footer.template @@ -1,3 +1,3 @@ - + diff --git a/formatters/html-data/dialog-header.template b/formatters/html-data/dialog-header.template index 0133029..74a2cf9 100644 --- a/formatters/html-data/dialog-header.template +++ b/formatters/html-data/dialog-header.template @@ -6,7 +6,5 @@ - +

%s

diff --git a/formatters/html-data/index.template b/formatters/html-data/index.template index 5fb4110..8adf9a7 100644 --- a/formatters/html-data/index.template +++ b/formatters/html-data/index.template @@ -6,6 +6,7 @@ +

Telegram History Dump

%s diff --git a/formatters/html-data/telegram-history-dump.css b/formatters/html-data/telegram-history-dump.css index 92b9332..f127cf0 100644 --- a/formatters/html-data/telegram-history-dump.css +++ b/formatters/html-data/telegram-history-dump.css @@ -1,6 +1,6 @@ body { width: 700px; - margin: 0 auto 0 auto; + margin: 15px auto 150px auto; } .msg { @@ -68,10 +68,6 @@ body { display: inline-block; } -.dialog-info { - margin-top: 15px; -} - .navigation { clear: both; } @@ -120,3 +116,8 @@ body { border-radius: 6px; } +.navigation-bottom { + clear: both; + padding-top: 15px; +} + diff --git a/formatters/html.rb b/formatters/html.rb index 922a7b3..edbcead 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -35,6 +35,9 @@ def start_backup(dialogs) end def format_dialog(dialog, messages) + message_count = 0 + page_count = 0 + if dialog['type'] != 'user' dialog_title = 'Group chat: %s' % CGI::escapeHTML(dialog['title']) html_title = CGI::escapeHTML(dialog['title']) @@ -43,12 +46,16 @@ def format_dialog(dialog, messages) html_title = CGI::escapeHTML(dialog['print_name']) end safe_name = get_safe_name(dialog['print_name']) + escaped_name = CGI::escapeHTML(URI.escape(safe_name)) current_filename = File.join(output_dir, safe_name + '-0.html') + backup_file = File.open(current_filename, 'w:UTF-8') - backup_file.puts(@html_template_header % [html_title, dialog_title]) + navigation = pagination(escaped_name, page_count, messages.length) + backup_file.puts(@html_template_header % [html_title, navigation, dialog_title]) + + timestamps_every = $config['formatters']['html']['timestamps_every'].to_i + messages_per_page = $config['formatters']['html']['paginate'] - message_count = 0 - page_count = 0 messages.reverse_each do |msg| if not msg['out'] and dialog['type'] != 'user' # If this is an incoming message in a group chat, display the author @@ -118,25 +125,30 @@ def format_dialog(dialog, messages) first = msg['media']['first_name'] last = msg['media']['last_name'] msg_body = "
Contact: #{first} #{last}, +#{phone}
" - elsif msg['event'] == 'service' or msg['service'] - if get_full_name(msg['from']) != '' # Some messages have no properly filled 'from' - text = CGI::escapeHTML(get_full_name(msg['from'])) - else - text = '(Unknown user)' - end - text += ' ' - if msg['action']['type'] == 'chat_add_user' - text += "added %s" % CGI::escapeHTML(get_full_name(msg['action']['user'])) - elsif msg['action']['type'] == 'chat_rename' - text += "changed group name to «%s»" % CGI::escapeHTML(msg['action']['title']) - elsif msg['action']['type'] == 'chat_change_photo' - text += "updated group photo" - elsif msg['action']['type'] == 'chat_created' - text += "created group «%s»" % CGI::escapeHTML(msg['action']['title']) - elsif msg['action']['type'] == 'chat_del_user' - text += "removed %s" % CGI::escapeHTML(get_full_name(msg['action']['user'])) + end + if msg['event'] == 'service' or msg['service'] or (message_count % timestamps_every == 0 and timestamps_every > 0) + if message_count % timestamps_every == 0 + text = date.strftime('%a %Y-%m-%d, %H:%I') else - text += CGI::escapeHTML(msg['action'].to_s) + if get_full_name(msg['from']) != '' # Some messages have no properly filled 'from' + text = CGI::escapeHTML(get_full_name(msg['from'])) + else + text = '(Unknown user)' + end + text += ' ' + if msg['action']['type'] == 'chat_add_user' + text += "added %s" % CGI::escapeHTML(get_full_name(msg['action']['user'])) + elsif msg['action']['type'] == 'chat_rename' + text += "changed group name to «%s»" % CGI::escapeHTML(msg['action']['title']) + elsif msg['action']['type'] == 'chat_change_photo' + text += "updated group photo" + elsif msg['action']['type'] == 'chat_created' + text += "created group «%s»" % CGI::escapeHTML(msg['action']['title']) + elsif msg['action']['type'] == 'chat_del_user' + text += "removed %s" % CGI::escapeHTML(get_full_name(msg['action']['user'])) + else + text += CGI::escapeHTML(msg['action'].to_s) + end end backup_file.puts("
#{text}
") end @@ -146,30 +158,25 @@ def format_dialog(dialog, messages) end message_count += 1 - messages_per_page = $config['formatters']['html']['paginate'] if messages_per_page and messages_per_page > 0 and message_count > messages_per_page # We reached our message limit on this page; paginate! - # Is there a previous page? If yes, link to it. - navigation = '' - if page_count > 0 - navigation += 'Previous page' % [CGI::escapeHTML(URI.escape(safe_name)), page_count] - end - - page_count += 1 - message_count = 0 - # Link to the next page and end the file - current_filename = File.join(output_dir, "%s-%s.html" % [CGI::escapeHTML(URI.escape(safe_name)), page_count]) - navigation += 'Next page' % File.basename(current_filename) + navigation = pagination(escaped_name, page_count, messages.length) backup_file.puts(@html_template_footer % navigation) backup_file.close() + page_count += 1 + message_count = 0 + # Open a new file and write the header again + current_filename = File.join(output_dir, "%s-%s.html" % [escaped_name, page_count]) backup_file = File.open(current_filename, 'w:UTF-8') - backup_file.puts(@html_template_header % [html_title, dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) + navigation = pagination(escaped_name, page_count, messages.length) + backup_file.puts(@html_template_header % [html_title, navigation, dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) end end - backup_file.puts(@html_template_footer % '') + navigation = pagination(escaped_name, page_count, messages.length) + backup_file.puts(@html_template_footer % navigation) backup_file.close() end @@ -177,6 +184,22 @@ def end_backup $log.info("HTML export finished, see: output/formatted/html/index.html") end + def pagination(escaped_name, current_page, total_messages) + messages_per_page = $config['formatters']['html']['paginate'] + total_pages = (total_messages / messages_per_page).ceil + + navigation = '' + navigation += "First | " % escaped_name if current_page > 1 + navigation += "3 back | " % [escaped_name, current_page - 3] if current_page > 5 + navigation += "Previous | " % [escaped_name, current_page - 1] if current_page > 0 + navigation += "Page #{current_page + 1} | " + navigation += "Next | " % [escaped_name, current_page + 1] if current_page < total_pages - 3 + navigation += "3 forward | " % [escaped_name, current_page + 3] if current_page + 5 < total_pages - 1 + navigation += "Last |" % [escaped_name, total_pages - 3] if current_page < total_pages - 4 + + return navigation + end + def replace_urls(text) # This function does not recognize geo: 'urls', which is too bad, but sort of by # design. They do not look like URLs with that comma in there and I did not want From 55426a6c7da69155e691cf02d728c134c4292c90 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sun, 28 Feb 2016 17:57:45 +0100 Subject: [PATCH 11/19] Fixed UTC time crash; fix pagination off-by-one --- formatters/html.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index edbcead..379a103 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -7,6 +7,10 @@ class HtmlFormatter < FormatterBase NAME = 'html' def start_backup(dialogs) + if not $config['copy_media'] or not $config['download_media'] or not $config['download_media']['document'] or not $config['download_media']['photo'] + $log.warn('Config options "copy_media" or "download_media.document|photo|..." not enabled. HTML dump might be missing images and/or files.') + end + FileUtils.remove_dir(output_dir, true) FileUtils.mkdir_p(output_dir) FileUtils.cp('formatters/html-data/telegram-history-dump.css', output_dir) @@ -66,11 +70,14 @@ def format_dialog(dialog, messages) if msg['date'] date = Time.at(msg['date']) + date_message = date.strftime('%a %Y-%m-%d, %H:%I') if $config['formatters']['html']['use_utc_time'] + date_message = date.utc.strftime('%a %Y-%m-%d, %H:%I') + " UTC" date = "#{date.utc} UTC" end else date = 'Unknown' + date_message = '' end msg_body = '' @@ -128,7 +135,7 @@ def format_dialog(dialog, messages) end if msg['event'] == 'service' or msg['service'] or (message_count % timestamps_every == 0 and timestamps_every > 0) if message_count % timestamps_every == 0 - text = date.strftime('%a %Y-%m-%d, %H:%I') + text = date_message else if get_full_name(msg['from']) != '' # Some messages have no properly filled 'from' text = CGI::escapeHTML(get_full_name(msg['from'])) @@ -150,7 +157,7 @@ def format_dialog(dialog, messages) text += CGI::escapeHTML(msg['action'].to_s) end end - backup_file.puts("
#{text}
") + backup_file.puts("
#{text}
") if text != '' end if msg_body != '' in_out = (msg['out'] ? 'out' : 'in') @@ -158,7 +165,7 @@ def format_dialog(dialog, messages) end message_count += 1 - if messages_per_page and messages_per_page > 0 and message_count > messages_per_page + if messages_per_page and messages_per_page > 0 and message_count >= messages_per_page # We reached our message limit on this page; paginate! navigation = pagination(escaped_name, page_count, messages.length) @@ -181,7 +188,7 @@ def format_dialog(dialog, messages) end def end_backup - $log.info("HTML export finished, see: output/formatted/html/index.html") + $log.info('HTML export finished, see: output/formatted/html/index.html') end def pagination(escaped_name, current_page, total_messages) From 855e8f886f0531e113b4dda3e5f06d359df431c3 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sun, 28 Feb 2016 18:36:56 +0100 Subject: [PATCH 12/19] Fix divby0 when config.pagination=0 --- formatters/html-data/dialog-footer.template | 2 +- formatters/html-data/dialog-header.template | 2 +- formatters/html.rb | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/formatters/html-data/dialog-footer.template b/formatters/html-data/dialog-footer.template index 29d08a4..0879448 100644 --- a/formatters/html-data/dialog-footer.template +++ b/formatters/html-data/dialog-footer.template @@ -1,3 +1,3 @@ - + diff --git a/formatters/html-data/dialog-header.template b/formatters/html-data/dialog-header.template index 74a2cf9..7da90ae 100644 --- a/formatters/html-data/dialog-header.template +++ b/formatters/html-data/dialog-header.template @@ -6,5 +6,5 @@ - +

%s

diff --git a/formatters/html.rb b/formatters/html.rb index 379a103..3c37bfe 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -193,9 +193,12 @@ def end_backup def pagination(escaped_name, current_page, total_messages) messages_per_page = $config['formatters']['html']['paginate'] + if messages_per_page == 0 + return '' + end total_pages = (total_messages / messages_per_page).ceil - navigation = '' + navigation = ' | ' navigation += "First | " % escaped_name if current_page > 1 navigation += "3 back | " % [escaped_name, current_page - 3] if current_page > 5 navigation += "Previous | " % [escaped_name, current_page - 1] if current_page > 0 From 0e8e177cb9f1ac04c6a1e0bd7d557be7f9020523 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sun, 28 Feb 2016 20:21:47 +0100 Subject: [PATCH 13/19] Force pagination option to be int --- formatters/html.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index 3c37bfe..433610b 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -58,7 +58,7 @@ def format_dialog(dialog, messages) backup_file.puts(@html_template_header % [html_title, navigation, dialog_title]) timestamps_every = $config['formatters']['html']['timestamps_every'].to_i - messages_per_page = $config['formatters']['html']['paginate'] + messages_per_page = $config['formatters']['html']['paginate'].to_i messages.reverse_each do |msg| if not msg['out'] and dialog['type'] != 'user' @@ -192,7 +192,7 @@ def end_backup end def pagination(escaped_name, current_page, total_messages) - messages_per_page = $config['formatters']['html']['paginate'] + messages_per_page = $config['formatters']['html']['paginate'].to_i if messages_per_page == 0 return '' end From a40c51f4b77653fa53d571280ffc08f47331dbb6 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sun, 28 Feb 2016 20:24:37 +0100 Subject: [PATCH 14/19] Fix inconsistency in filenames (unnecessarily encoded filename) --- formatters/html.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formatters/html.rb b/formatters/html.rb index 433610b..48b8dcd 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -176,7 +176,7 @@ def format_dialog(dialog, messages) message_count = 0 # Open a new file and write the header again - current_filename = File.join(output_dir, "%s-%s.html" % [escaped_name, page_count]) + current_filename = File.join(output_dir, "%s-%s.html" % [safe_name, page_count]) backup_file = File.open(current_filename, 'w:UTF-8') navigation = pagination(escaped_name, page_count, messages.length) backup_file.puts(@html_template_header % [html_title, navigation, dialog_title + (' - page %i' % (page_count + 1) if page_count > 0)]) From 656f1d112b082f4ee82495db0f5538cd421b2bbf Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sun, 28 Feb 2016 20:25:07 +0100 Subject: [PATCH 15/19] m. Tabs to spaces, again... --- formatters/html.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index 48b8dcd..76a367c 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -70,14 +70,14 @@ def format_dialog(dialog, messages) if msg['date'] date = Time.at(msg['date']) - date_message = date.strftime('%a %Y-%m-%d, %H:%I') + date_message = date.strftime('%a %Y-%m-%d, %H:%I') if $config['formatters']['html']['use_utc_time'] - date_message = date.utc.strftime('%a %Y-%m-%d, %H:%I') + " UTC" + date_message = date.utc.strftime('%a %Y-%m-%d, %H:%I') + " UTC" date = "#{date.utc} UTC" end else date = 'Unknown' - date_message = '' + date_message = '' end msg_body = '' @@ -194,8 +194,8 @@ def end_backup def pagination(escaped_name, current_page, total_messages) messages_per_page = $config['formatters']['html']['paginate'].to_i if messages_per_page == 0 - return '' - end + return '' + end total_pages = (total_messages / messages_per_page).ceil navigation = ' | ' From 0b0c7059bd9129308838852953af59cc70c75738 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sun, 28 Feb 2016 20:55:20 +0100 Subject: [PATCH 16/19] Updating pagination calculations again. Apparently 1/2==0 in Ruby (integer divisions). --- formatters/html.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index 76a367c..834b10f 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -196,16 +196,17 @@ def pagination(escaped_name, current_page, total_messages) if messages_per_page == 0 return '' end - total_pages = (total_messages / messages_per_page).ceil + total_pages = (total_messages.to_f / messages_per_page).ceil + last_page = total_pages - 1 navigation = ' | ' navigation += "First | " % escaped_name if current_page > 1 - navigation += "3 back | " % [escaped_name, current_page - 3] if current_page > 5 + navigation += "3 back | " % [escaped_name, current_page - 3] if current_page > 4 navigation += "Previous | " % [escaped_name, current_page - 1] if current_page > 0 navigation += "Page #{current_page + 1} | " - navigation += "Next | " % [escaped_name, current_page + 1] if current_page < total_pages - 3 - navigation += "3 forward | " % [escaped_name, current_page + 3] if current_page + 5 < total_pages - 1 - navigation += "Last |" % [escaped_name, total_pages - 3] if current_page < total_pages - 4 + navigation += "Next | " % [escaped_name, current_page + 1] if current_page < last_page + navigation += "3 forward | " % [escaped_name, current_page + 3] if current_page + 4 < last_page + navigation += "Last |" % [escaped_name, last_page] if current_page < last_page - 1 return navigation end From 1bddd4532576bbfc99ca0d6b0ebd4fbfb7537c29 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Sun, 28 Feb 2016 22:48:55 +0100 Subject: [PATCH 17/19] HTML link parser improvement: subdomains can start with digits and be digits-only, e.g. http://2.example.com/. Changed regex to look for this. --- formatters/html.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formatters/html.rb b/formatters/html.rb index 834b10f..16ff77d 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -238,7 +238,7 @@ def replace_urls(text) # TODO: IP addresses. IPv6 addresses. I completely forgot about those. - urls = text.scan(/(^|\s|<)(([a-zA-Z]{1,25}:)?([^@\s]{1,200}@)?(\/\/)?([a-zA-Z][a-zA-Z0-9-]{0,63}\.){0,125}[a-zA-Z][a-zA-Z0-9-]{0,63}\.([a-zA-Z]{2,63}|xn--[a-zA-Z0-9]{1,60})(:[1-9][0-9]{0,4})?(\/[!-~]*)?(\s|>|$|\)|\.))/) + urls = text.scan(/(^|\s|<)(([a-zA-Z]{1,25}:)?([^@\s]{1,200}@)?(\/\/)?([0-9a-zA-Z][a-zA-Z0-9-]{0,63}\.){0,125}[a-zA-Z][a-zA-Z0-9-]{0,63}\.([a-zA-Z]{2,63}|xn--[a-zA-Z0-9]{1,60})(:[1-9][0-9]{0,4})?(\/[!-~]*)?(\s|>|$|\)|\.))/) text = CGI::escapeHTML(text) From d9642be222af3f11ae026172936f045481d57c95 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Mon, 29 Feb 2016 10:16:16 +0100 Subject: [PATCH 18/19] m. Tabs to spaces --- formatters/html-data/dialog-footer.template | 4 +- formatters/html-data/dialog-header.template | 16 +-- formatters/html-data/index.template | 18 +-- .../html-data/telegram-history-dump.css | 124 +++++++++--------- formatters/html.rb | 2 +- 5 files changed, 82 insertions(+), 82 deletions(-) diff --git a/formatters/html-data/dialog-footer.template b/formatters/html-data/dialog-footer.template index 0879448..2bea4cf 100644 --- a/formatters/html-data/dialog-footer.template +++ b/formatters/html-data/dialog-footer.template @@ -1,3 +1,3 @@ - - + + diff --git a/formatters/html-data/dialog-header.template b/formatters/html-data/dialog-header.template index 7da90ae..1aa14e4 100644 --- a/formatters/html-data/dialog-header.template +++ b/formatters/html-data/dialog-header.template @@ -1,10 +1,10 @@ - - - Chat: %s - - - - -

%s

+ + + Chat: %s + + + + +

%s

diff --git a/formatters/html-data/index.template b/formatters/html-data/index.template index 8adf9a7..dfbc8e6 100644 --- a/formatters/html-data/index.template +++ b/formatters/html-data/index.template @@ -1,12 +1,12 @@ - - - Telegram History Dump - - - -

Telegram History Dump

- %s - + + + Telegram History Dump + + + +

Telegram History Dump

+ %s + diff --git a/formatters/html-data/telegram-history-dump.css b/formatters/html-data/telegram-history-dump.css index f127cf0..4c26158 100644 --- a/formatters/html-data/telegram-history-dump.css +++ b/formatters/html-data/telegram-history-dump.css @@ -1,123 +1,123 @@ body { - width: 700px; - margin: 15px auto 150px auto; + width: 700px; + margin: 15px auto 150px auto; } .msg { - box-sizing: border-box; - float: left; - width: auto; - max-width: 80%; - position: relative; - clear: both; + box-sizing: border-box; + float: left; + width: auto; + max-width: 80%; + position: relative; + clear: both; - background: #dcebfe; - background-image: linear-gradient(bottom, #bee2ff 15%, #dcebfe 100%); - border: solid 1px rgba(0,0,0,0.5); - border-radius: 20px; - box-shadow: inset 0 8px 5px rgba(255,255,255,0.65), 0 1px 2px rgba(0,0,0,0.2); + background: #dcebfe; + background-image: linear-gradient(bottom, #bee2ff 15%, #dcebfe 100%); + border: solid 1px rgba(0,0,0,0.5); + border-radius: 20px; + box-shadow: inset 0 8px 5px rgba(255,255,255,0.65), 0 1px 2px rgba(0,0,0,0.2); - margin-bottom: 10px; - padding: 6px 20px; - word-wrap: break-word; + margin-bottom: 10px; + padding: 6px 20px; + word-wrap: break-word; } .msg:before, .msg:after { - border-radius: 20px / 5px; - content: ''; - display: block; - position: absolute; + border-radius: 20px / 5px; + content: ''; + display: block; + position: absolute; } .msg:before { - border: 10px solid transparent; - border-bottom-color: rgba(0,0,0,0.5); - bottom: 0px; - left: -7px; - z-index: -2; + border: 10px solid transparent; + border-bottom-color: rgba(0,0,0,0.5); + bottom: 0px; + left: -7px; + z-index: -2; } .msg:after { - border: 8px solid transparent; - border-bottom-color: #bee2ff; - bottom: 1px; - left: -5px; + border: 8px solid transparent; + border-bottom-color: #bee2ff; + bottom: 1px; + left: -5px; } .out { - float: right; - background: #bcf6a6; - background-image: linear-gradient(bottom, #bee2ff 15%, #bcf6a6 100%); + float: right; + background: #bcf6a6; + background-image: linear-gradient(bottom, #bee2ff 15%, #bcf6a6 100%); } .out:before { - left: auto; - right: -7px; + left: auto; + right: -7px; } .out:after { - left: auto; - right: -5px; + left: auto; + right: -5px; - border-bottom-color: #d9ffbe; + border-bottom-color: #d9ffbe; } .dialog { - float: left !important; + float: left !important; } .dialog .icon { - width: 23px; - height: 20px; - display: inline-block; + width: 23px; + height: 20px; + display: inline-block; } .navigation { - clear: both; + clear: both; } .icon { - background-size: 100% 100%; + background-size: 100% 100%; } /* Credits: http://www.flaticon.com/free-icon/multiple-users-silhouette_33308 */ .img-group { - background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgODAuMTMgODAuMTMiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDgwLjEzIDgwLjEzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTQ4LjM1NSwxNy45MjJjMy43MDUsMi4zMjMsNi4zMDMsNi4yNTQsNi43NzYsMTAuODE3YzEuNTExLDAuNzA2LDMuMTg4LDEuMTEyLDQuOTY2LDEuMTEyICAgYzYuNDkxLDAsMTEuNzUyLTUuMjYxLDExLjc1Mi0xMS43NTFjMC02LjQ5MS01LjI2MS0xMS43NTItMTEuNzUyLTExLjc1MkM1My42NjgsNi4zNSw0OC40NTMsMTEuNTE3LDQ4LjM1NSwxNy45MjJ6IE00MC42NTYsNDEuOTg0ICAgYzYuNDkxLDAsMTEuNzUyLTUuMjYyLDExLjc1Mi0xMS43NTJzLTUuMjYyLTExLjc1MS0xMS43NTItMTEuNzUxYy02LjQ5LDAtMTEuNzU0LDUuMjYyLTExLjc1NCwxMS43NTJTMzQuMTY2LDQxLjk4NCw0MC42NTYsNDEuOTg0ICAgeiBNNDUuNjQxLDQyLjc4NWgtOS45NzJjLTguMjk3LDAtMTUuMDQ3LDYuNzUxLTE1LjA0NywxNS4wNDh2MTIuMTk1bDAuMDMxLDAuMTkxbDAuODQsMC4yNjMgICBjNy45MTgsMi40NzQsMTQuNzk3LDMuMjk5LDIwLjQ1OSwzLjI5OWMxMS4wNTksMCwxNy40NjktMy4xNTMsMTcuODY0LTMuMzU0bDAuNzg1LTAuMzk3aDAuMDg0VjU3LjgzMyAgIEM2MC42ODgsNDkuNTM2LDUzLjkzOCw0Mi43ODUsNDUuNjQxLDQyLjc4NXogTTY1LjA4NCwzMC42NTNoLTkuODk1Yy0wLjEwNywzLjk1OS0xLjc5Nyw3LjUyNC00LjQ3LDEwLjA4OCAgIGM3LjM3NSwyLjE5MywxMi43NzEsOS4wMzIsMTIuNzcxLDE3LjExdjMuNzU4YzkuNzctMC4zNTgsMTUuNC0zLjEyNywxNS43NzEtMy4zMTNsMC43ODUtMC4zOThoMC4wODRWNDUuNjk5ICAgQzgwLjEzLDM3LjQwMyw3My4zOCwzMC42NTMsNjUuMDg0LDMwLjY1M3ogTTIwLjAzNSwyOS44NTNjMi4yOTksMCw0LjQzOC0wLjY3MSw2LjI1LTEuODE0YzAuNTc2LTMuNzU3LDIuNTktNy4wNCw1LjQ2Ny05LjI3NiAgIGMwLjAxMi0wLjIyLDAuMDMzLTAuNDM4LDAuMDMzLTAuNjZjMC02LjQ5MS01LjI2Mi0xMS43NTItMTEuNzUtMTEuNzUyYy02LjQ5MiwwLTExLjc1Miw1LjI2MS0xMS43NTIsMTEuNzUyICAgQzguMjgzLDI0LjU5MSwxMy41NDMsMjkuODUzLDIwLjAzNSwyOS44NTN6IE0zMC41ODksNDAuNzQxYy0yLjY2LTIuNTUxLTQuMzQ0LTYuMDk3LTQuNDY3LTEwLjAzMiAgIGMtMC4zNjctMC4wMjctMC43My0wLjA1Ni0xLjEwNC0wLjA1NmgtOS45NzFDNi43NSwzMC42NTMsMCwzNy40MDMsMCw0NS42OTl2MTIuMTk3bDAuMDMxLDAuMTg4bDAuODQsMC4yNjUgICBjNi4zNTIsMS45ODMsMTIuMDIxLDIuODk3LDE2Ljk0NSwzLjE4NXYtMy42ODNDMTcuODE4LDQ5Ljc3MywyMy4yMTIsNDIuOTM2LDMwLjU4OSw0MC43NDF6IiBmaWxsPSIjMDAwMDAwIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==); + background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgODAuMTMgODAuMTMiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDgwLjEzIDgwLjEzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTQ4LjM1NSwxNy45MjJjMy43MDUsMi4zMjMsNi4zMDMsNi4yNTQsNi43NzYsMTAuODE3YzEuNTExLDAuNzA2LDMuMTg4LDEuMTEyLDQuOTY2LDEuMTEyICAgYzYuNDkxLDAsMTEuNzUyLTUuMjYxLDExLjc1Mi0xMS43NTFjMC02LjQ5MS01LjI2MS0xMS43NTItMTEuNzUyLTExLjc1MkM1My42NjgsNi4zNSw0OC40NTMsMTEuNTE3LDQ4LjM1NSwxNy45MjJ6IE00MC42NTYsNDEuOTg0ICAgYzYuNDkxLDAsMTEuNzUyLTUuMjYyLDExLjc1Mi0xMS43NTJzLTUuMjYyLTExLjc1MS0xMS43NTItMTEuNzUxYy02LjQ5LDAtMTEuNzU0LDUuMjYyLTExLjc1NCwxMS43NTJTMzQuMTY2LDQxLjk4NCw0MC42NTYsNDEuOTg0ICAgeiBNNDUuNjQxLDQyLjc4NWgtOS45NzJjLTguMjk3LDAtMTUuMDQ3LDYuNzUxLTE1LjA0NywxNS4wNDh2MTIuMTk1bDAuMDMxLDAuMTkxbDAuODQsMC4yNjMgICBjNy45MTgsMi40NzQsMTQuNzk3LDMuMjk5LDIwLjQ1OSwzLjI5OWMxMS4wNTksMCwxNy40NjktMy4xNTMsMTcuODY0LTMuMzU0bDAuNzg1LTAuMzk3aDAuMDg0VjU3LjgzMyAgIEM2MC42ODgsNDkuNTM2LDUzLjkzOCw0Mi43ODUsNDUuNjQxLDQyLjc4NXogTTY1LjA4NCwzMC42NTNoLTkuODk1Yy0wLjEwNywzLjk1OS0xLjc5Nyw3LjUyNC00LjQ3LDEwLjA4OCAgIGM3LjM3NSwyLjE5MywxMi43NzEsOS4wMzIsMTIuNzcxLDE3LjExdjMuNzU4YzkuNzctMC4zNTgsMTUuNC0zLjEyNywxNS43NzEtMy4zMTNsMC43ODUtMC4zOThoMC4wODRWNDUuNjk5ICAgQzgwLjEzLDM3LjQwMyw3My4zOCwzMC42NTMsNjUuMDg0LDMwLjY1M3ogTTIwLjAzNSwyOS44NTNjMi4yOTksMCw0LjQzOC0wLjY3MSw2LjI1LTEuODE0YzAuNTc2LTMuNzU3LDIuNTktNy4wNCw1LjQ2Ny05LjI3NiAgIGMwLjAxMi0wLjIyLDAuMDMzLTAuNDM4LDAuMDMzLTAuNjZjMC02LjQ5MS01LjI2Mi0xMS43NTItMTEuNzUtMTEuNzUyYy02LjQ5MiwwLTExLjc1Miw1LjI2MS0xMS43NTIsMTEuNzUyICAgQzguMjgzLDI0LjU5MSwxMy41NDMsMjkuODUzLDIwLjAzNSwyOS44NTN6IE0zMC41ODksNDAuNzQxYy0yLjY2LTIuNTUxLTQuMzQ0LTYuMDk3LTQuNDY3LTEwLjAzMiAgIGMtMC4zNjctMC4wMjctMC43My0wLjA1Ni0xLjEwNC0wLjA1NmgtOS45NzFDNi43NSwzMC42NTMsMCwzNy40MDMsMCw0NS42OTl2MTIuMTk3bDAuMDMxLDAuMTg4bDAuODQsMC4yNjUgICBjNi4zNTIsMS45ODMsMTIuMDIxLDIuODk3LDE2Ljk0NSwzLjE4NXYtMy42ODNDMTcuODE4LDQ5Ljc3MywyMy4yMTIsNDIuOTM2LDMwLjU4OSw0MC43NDF6IiBmaWxsPSIjMDAwMDAwIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==); } /* Credits: http://www.flaticon.com/free-icon/user-black-close-up-shape_32438 */ .img-single-user { - background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNDY2LjE0NiA0NjYuMTQ2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0NjYuMTQ2IDQ2Ni4xNDY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8cGF0aCBkPSJNMjg5LjI4NSwxOTEuODZjMjguODQ0LTE4LjUzOSw0Ny45OTUtNTAuODMsNDcuOTk1LTg3LjY1NEMzMzcuMjgsNDYuNjU5LDI5MC42MjEsMCwyMzMuMDg4LDAgICBjLTU3LjU1OSwwLTEwNC4yMDcsNDYuNjU5LTEwNC4yMDcsMTA0LjIwN2MwLDM2LjgyNCwxOS4xNTEsNjkuMTIxLDQ3Ljk5Niw4Ny42NTRjLTY3Ljk1OSw2LjA4Mi0xMjEuNDIyLDYzLjMzMS0xMjEuNDIyLDEzMi44NTQgICB2MTA4LjE1NWwwLjI3NCwxLjY5bDcuNDU3LDIuMzI4YzcwLjE5NiwyMS45MjksMTMxLjE5NSwyOS4yNTksMTgxLjQwMSwyOS4yNTljOTguMDQ4LDAsMTU0Ljg4Ni0yNy45NywxNTguNDA4LTI5Ljc0M2w2Ljk2My0zLjUzNCAgIGgwLjczMlYzMjQuNzE0QzQxMC42OTgsMjU1LjE5NywzNTcuMjUzLDE5Ny45NTcsMjg5LjI4NSwxOTEuODZ6IiBmaWxsPSIjMDAwMDAwIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==); + background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNDY2LjE0NiA0NjYuMTQ2IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0NjYuMTQ2IDQ2Ni4xNDY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8cGF0aCBkPSJNMjg5LjI4NSwxOTEuODZjMjguODQ0LTE4LjUzOSw0Ny45OTUtNTAuODMsNDcuOTk1LTg3LjY1NEMzMzcuMjgsNDYuNjU5LDI5MC42MjEsMCwyMzMuMDg4LDAgICBjLTU3LjU1OSwwLTEwNC4yMDcsNDYuNjU5LTEwNC4yMDcsMTA0LjIwN2MwLDM2LjgyNCwxOS4xNTEsNjkuMTIxLDQ3Ljk5Niw4Ny42NTRjLTY3Ljk1OSw2LjA4Mi0xMjEuNDIyLDYzLjMzMS0xMjEuNDIyLDEzMi44NTQgICB2MTA4LjE1NWwwLjI3NCwxLjY5bDcuNDU3LDIuMzI4YzcwLjE5NiwyMS45MjksMTMxLjE5NSwyOS4yNTksMTgxLjQwMSwyOS4yNTljOTguMDQ4LDAsMTU0Ljg4Ni0yNy45NywxNTguNDA4LTI5Ljc0M2w2Ljk2My0zLjUzNCAgIGgwLjczMlYzMjQuNzE0QzQxMC42OTgsMjU1LjE5NywzNTcuMjUzLDE5Ny45NTcsMjg5LjI4NSwxOTEuODZ6IiBmaWxsPSIjMDAwMDAwIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==); } .author { - font-style: italic; - display: inline-block; - color: #666152; + font-style: italic; + display: inline-block; + color: #666152; } .msg img, .msg video { - max-width: 525px; - max-height: 500px; + max-width: 525px; + max-height: 500px; } .msg .webpage, .msg .geo, .msg .contact { - border-left: 3px solid #bbb; - padding-left: 5px; + border-left: 3px solid #bbb; + padding-left: 5px; } .msg-service { - clear: both; - text-align: center; + clear: both; + text-align: center; } .msg-service .inner { - margin-bottom: 10px; - color: #fff; - background-color: rgba(0, 0, 0, 0.7); - padding: 5px; - display: inline-block; - border-radius: 6px; + margin-bottom: 10px; + color: #fff; + background-color: rgba(0, 0, 0, 0.7); + padding: 5px; + display: inline-block; + border-radius: 6px; } .navigation-bottom { - clear: both; - padding-top: 15px; + clear: both; + padding-top: 15px; } diff --git a/formatters/html.rb b/formatters/html.rb index 16ff77d..22ecaf1 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -197,7 +197,7 @@ def pagination(escaped_name, current_page, total_messages) return '' end total_pages = (total_messages.to_f / messages_per_page).ceil - last_page = total_pages - 1 + last_page = total_pages - 1 navigation = ' | ' navigation += "First | " % escaped_name if current_page > 1 From 957e59dba878b2a42ee581c9206773ddb375ec82 Mon Sep 17 00:00:00 2001 From: Luc Gommans Date: Mon, 29 Feb 2016 10:30:40 +0100 Subject: [PATCH 19/19] Use File.expand_path --- formatters/html.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/formatters/html.rb b/formatters/html.rb index 22ecaf1..b3c53f6 100644 --- a/formatters/html.rb +++ b/formatters/html.rb @@ -13,11 +13,11 @@ def start_backup(dialogs) FileUtils.remove_dir(output_dir, true) FileUtils.mkdir_p(output_dir) - FileUtils.cp('formatters/html-data/telegram-history-dump.css', output_dir) + FileUtils.cp(File.expand_path('../html-data/telegram-history-dump.css', __FILE__), output_dir) - @html_template_index = File.read('formatters/html-data/index.template') - @html_template_header = File.read('formatters/html-data/dialog-header.template') - @html_template_footer = File.read('formatters/html-data/dialog-footer.template') + @html_template_index = File.read(File.expand_path('../html-data/index.template', __FILE__)) + @html_template_header = File.read(File.expand_path('../html-data/dialog-header.template', __FILE__)) + @html_template_footer = File.read(File.expand_path('../html-data/dialog-footer.template', __FILE__)) dialog_list_html = '' dialogs.each do |dialog|