diff --git a/src/main/java/de/fabianonline/telegram_backup/Database.java b/src/main/java/de/fabianonline/telegram_backup/Database.java index e32a050..58fa2bd 100644 --- a/src/main/java/de/fabianonline/telegram_backup/Database.java +++ b/src/main/java/de/fabianonline/telegram_backup/Database.java @@ -414,24 +414,31 @@ public class Database { return list; } catch (SQLException e) { throw new RuntimeException(e); } } - public HashMap getMessageTypesWithCount() { + return getMessageTypesWithCount(new GlobalChat()); + } + + public HashMap getMessageTypesWithCount(AbstractChat c) { HashMap map = new HashMap(); try { - ResultSet rs = stmt.executeQuery("SELECT message_type, COUNT(id) FROM messages GROUP BY message_type"); + ResultSet rs = stmt.executeQuery("SELECT message_type, COUNT(id) FROM messages WHERE " + c.getQuery() + " GROUP BY message_type"); while (rs.next()) { - map.put(rs.getString(1), rs.getInt(2)); + map.put("count.messages.type." + rs.getString(1), rs.getInt(2)); } return map; } catch (Exception e) { throw new RuntimeException(e); } } public HashMap getMessageMediaTypesWithCount() { + return getMessageMediaTypesWithCount(new GlobalChat()); + } + + public HashMap getMessageMediaTypesWithCount(AbstractChat c) { HashMap map = new HashMap(); try { int count = 0; - ResultSet rs = stmt.executeQuery("SELECT media_type, COUNT(id) FROM messages GROUP BY media_type"); + ResultSet rs = stmt.executeQuery("SELECT media_type, COUNT(id) FROM messages WHERE " + c.getQuery() + " GROUP BY media_type"); while (rs.next()) { String s = rs.getString(1); if (s==null) { @@ -439,19 +446,49 @@ public class Database { } else { count += rs.getInt(2); } - map.put(s, rs.getInt(2)); + map.put("count.messages.media_type." + s, rs.getInt(2)); } - map.put("any", count); + map.put("count.messages.media_type.any", count); return map; } catch (Exception e) { throw new RuntimeException(e); } } + + public HashMap getMessageAuthorsWithCount() { + return getMessageAuthorsWithCount(new GlobalChat()); + } + + public HashMap getMessageAuthorsWithCount(AbstractChat c) { + HashMap map = new HashMap(); + HashMap user_map = new HashMap(); + int count_others = 0; + try { + ResultSet rs = stmt.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) "+ + "FROM messages, users WHERE users.id=messages.sender_id AND " + c.getQuery() + " GROUP BY sender_id"); + while (rs.next()) { + User u = new User(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4)); + if (u.isMe) { + map.put("authors.count.me", rs.getInt(5)); + } else { + user_map.put(u, rs.getInt(5)); + count_others += rs.getInt(5); + } + } + map.put("authors.count.others", count_others); + map.put("authors.all", user_map.entrySet()); + return map; + } catch (Exception e) { throw new RuntimeException(e); } + } + + public int[][] getMessageTimesMatrix() { + return getMessageTimesMatrix(new GlobalChat()); + } - public int[][] getMessageTimesMatrix(Integer dialog_id, Integer chat_id) { + public int[][] getMessageTimesMatrix(AbstractChat c) { int result[][] = new int[7][24]; try { ResultSet rs = stmt.executeQuery("SELECT STRFTIME('%w', time, 'unixepoch') as DAY, " + "STRFTIME('%H', time, 'unixepoch') AS hour, " + - "COUNT(id) FROM messages GROUP BY hour, day " + + "COUNT(id) FROM messages WHERE " + c.getQuery() + " GROUP BY hour, day " + "ORDER BY hour, day"); while (rs.next()) { result[rs.getInt(1) == 0 ? 6 : rs.getInt(1)-1][rs.getInt(2)] = rs.getInt(3); @@ -497,29 +534,25 @@ public class Database { } } - public LinkedList> getMessagesForExport(Dialog d) { - return getMessagesForExport("dialog_id", d.id); - } - - public LinkedList> getMessagesForExport(Chat c) { - return getMessagesForExport("chat_id", c.id); - } - - private LinkedList> getMessagesForExport(String type, Integer id) { + public LinkedList> getMessagesForExport(AbstractChat c) { try { + ResultSet rs = stmt.executeQuery("SELECT messages.id as message_id, text, time*1000 as time, has_media, " + "media_type, media_file, media_size, users.first_name as user_first_name, users.last_name as user_last_name, " + "users.username as user_username, users.id as user_id, " + "users_fwd.first_name as user_fwd_first_name, users_fwd.last_name as user_fwd_last_name, users_fwd.username as user_fwd_username " + "FROM messages, users LEFT JOIN users AS users_fwd ON users_fwd.id=fwd_from_id WHERE " + - "users.id=messages.sender_id AND " + type + "=" + id + " " + + "users.id=messages.sender_id AND " + c.getQuery() + " " + "ORDER BY messages.id"); SimpleDateFormat format_time = new SimpleDateFormat("HH:mm:ss"); SimpleDateFormat format_date = new SimpleDateFormat("d MMM yy"); ResultSetMetaData meta = rs.getMetaData(); int columns = meta.getColumnCount(); LinkedList> list = new LinkedList>(); + + Integer count=0; String old_date = null; + Integer old_user = null; while (rs.next()) { HashMap h = new HashMap(columns); for (int i=1; i<=columns; i++) { @@ -535,9 +568,13 @@ public class Database { } h.put("from_me", rs.getInt("user_id")==user_manager.getUser().getId()); h.put("is_new_date", !date.equals(old_date)); + h.put("odd_even", (count%2==0) ? "even" : "odd"); + h.put("same_user", old_user!=null && rs.getInt("user_id")==old_user); + old_user = rs.getInt("user_id"); old_date = date; list.add(h); + count++; } rs.close(); return list; @@ -564,9 +601,11 @@ public class Database { + public abstract class AbstractChat { + public abstract String getQuery(); + } - - public class Dialog { + public class Dialog extends AbstractChat{ public int id; public String first_name; public String last_name; @@ -580,9 +619,11 @@ public class Database { this.username = username; this.count = count; } + + public String getQuery() { return "dialog_id=" + id; } } - public class Chat { + public class Chat extends AbstractChat { public int id; public String name; public int count; @@ -592,5 +633,24 @@ public class Database { this.name = name; this.count = count; } + + public String getQuery() {return "chat_id=" + id; } + } + + public class User { + public String name; + public boolean isMe; + + public User(int id, String first_name, String last_name, String username) { + isMe = id==user_manager.getUser().getId(); + StringBuilder s = new StringBuilder(); + if (first_name!=null) s.append(first_name + " "); + if (last_name!=null) s.append(last_name); + name = s.toString().trim(); + } + } + + public class GlobalChat extends AbstractChat { + public String getQuery() { return "1=1"; } } } diff --git a/src/main/java/de/fabianonline/telegram_backup/exporter/HTMLExporter.java b/src/main/java/de/fabianonline/telegram_backup/exporter/HTMLExporter.java index d854abb..3c20e68 100644 --- a/src/main/java/de/fabianonline/telegram_backup/exporter/HTMLExporter.java +++ b/src/main/java/de/fabianonline/telegram_backup/exporter/HTMLExporter.java @@ -24,30 +24,41 @@ import java.io.PrintWriter; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; +import java.net.URL; +import org.apache.commons.io.FileUtils; import java.util.LinkedList; import java.util.HashMap; import java.util.Map; +import java.util.Set; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class HTMLExporter { + private static Logger logger = LoggerFactory.getLogger(HTMLExporter.class); + public void export(UserManager user) { try { Database db = new Database(user, null); // Create base dir + logger.debug("Creating base dir"); String base = user.getFileBase() + "files" + File.separatorChar; new File(base).mkdirs(); new File(base + "dialogs").mkdirs(); + logger.debug("Fetching dialogs"); LinkedList dialogs = db.getListOfDialogsForExport(); + logger.debug("Fetching chats"); LinkedList chats = db.getListOfChatsForExport(); - + logger.debug("Generating index.html"); HashMap scope = new HashMap(); - scope.put("user", user.getUser()); + scope.put("user", user); scope.put("dialogs", dialogs); scope.put("chats", chats); @@ -66,18 +77,11 @@ public class HTMLExporter { scope.put("count.messages.from_me", db.getMessagesFromUserCount()); - scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(null, null))); + scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix())); - HashMap types; - types = db.getMessageTypesWithCount(); - for (Map.Entry entry : types.entrySet()) { - scope.put("count.messages.type." + entry.getKey(), entry.getValue()); - } - - types = db.getMessageMediaTypesWithCount(); - for (Map.Entry entry : types.entrySet()) { - scope.put("count.messages.media_type." + entry.getKey(), entry.getValue()); - } + scope.putAll(db.getMessageAuthorsWithCount()); + scope.putAll(db.getMessageTypesWithCount()); + scope.putAll(db.getMessageMediaTypesWithCount()); MustacheFactory mf = new DefaultMustacheFactory(); Mustache mustache = mf.compile("templates/html/index.mustache"); @@ -87,27 +91,44 @@ public class HTMLExporter { mustache = mf.compile("templates/html/chat.mustache"); + logger.debug("Generating dialog pages"); for (Database.Dialog d : dialogs) { LinkedList> messages = db.getMessagesForExport(d); scope.clear(); scope.put("dialog", d); scope.put("messages", messages); + scope.putAll(db.getMessageAuthorsWithCount(d)); + scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(d))); + scope.putAll(db.getMessageTypesWithCount(d)); + scope.putAll(db.getMessageMediaTypesWithCount(d)); + w = new FileWriter(base + "dialogs" + File.separatorChar + "user_" + d.id + ".html"); mustache.execute(w, scope); w.close(); } + logger.debug("Generating chat pages"); for (Database.Chat c : chats) { LinkedList> messages = db.getMessagesForExport(c); scope.clear(); scope.put("chat", c); scope.put("messages", messages); + scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(c))); + scope.putAll(db.getMessageTypesWithCount(c)); + scope.putAll(db.getMessageMediaTypesWithCount(c)); + w = new FileWriter(base + "dialogs" + File.separatorChar + "chat_" + c.id + ".html"); mustache.execute(w, scope); w.close(); } + + logger.debug("Generating additional files"); + // Copy CSS + URL cssFile = getClass().getResource("/templates/html/style.css"); + File dest = new File(base + "style.css"); + FileUtils.copyURLToFile(cssFile, dest); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("Exception above!"); @@ -126,4 +147,13 @@ public class HTMLExporter { sb.append("]"); return sb.toString(); } + + private String mapToString(Map map) { + StringBuilder sb = new StringBuilder("["); + for (Map.Entry entry : map.entrySet()) { + sb.append("['" + entry.getKey() + "', " + entry.getValue() + "],"); + } + sb.append("]"); + return sb.toString(); + } } diff --git a/src/main/java/de/fabianonline/telegram_backup/exporter/StatsExporter.java b/src/main/java/de/fabianonline/telegram_backup/exporter/StatsExporter.java deleted file mode 100644 index a53f702..0000000 --- a/src/main/java/de/fabianonline/telegram_backup/exporter/StatsExporter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* Telegram_Backup - * Copyright (C) 2016 Fabian Schlenz - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . */ - -package de.fabianonline.telegram_backup.exporter; - -import de.fabianonline.telegram_backup.UserManager; -import de.fabianonline.telegram_backup.Database; - -import java.io.File; -import java.io.PrintWriter; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.util.LinkedList; -import java.util.HashMap; -import java.util.Map; - -import com.github.mustachejava.DefaultMustacheFactory; -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; - -public class StatsExporter { - public void export(UserManager user) { - try { - Database db = new Database(user, null); - - // Create base dir - String base = user.getFileBase() + "export" + File.separatorChar + "stats" + File.separatorChar; - new File(base).mkdirs(); - - HashMap scope = new HashMap(); - - // Collect data - LinkedList dialogs = db.getListOfDialogsForExport(); - LinkedList chats = db.getListOfChatsForExport(); - - scope.put("count.chats", chats.size()); - scope.put("count.dialogs", dialogs.size()); - - int count_messages_chats = 0; - int count_messages_dialogs = 0; - for (Database.Chat c : chats) count_messages_chats += c.count; - for (Database.Dialog d : dialogs) count_messages_dialogs += d.count; - - scope.put("count.messages", count_messages_chats + count_messages_dialogs); - scope.put("count.messages.chats", count_messages_chats); - scope.put("count.messages.dialogs", count_messages_dialogs); - - scope.put("count.messages.from_me", db.getMessagesFromUserCount()); - - HashMap types; - types = db.getMessageTypesWithCount(); - for (Map.Entry entry : types.entrySet()) { - scope.put("count.messages.type." + entry.getKey(), entry.getValue()); - } - - types = db.getMessageMediaTypesWithCount(); - for (Map.Entry entry : types.entrySet()) { - scope.put("count.messages.media_type." + entry.getKey(), entry.getValue()); - } - - MustacheFactory mf = new DefaultMustacheFactory(); - Mustache mustache = mf.compile("templates/stats/index.mustache"); - FileWriter w = new FileWriter(base + "index.html"); - mustache.execute(w, scope); - w.close(); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException("Exception above!"); - } - } -} diff --git a/src/main/resources/templates/html/_stats.mustache b/src/main/resources/templates/html/_stats.mustache new file mode 100644 index 0000000..71bc326 --- /dev/null +++ b/src/main/resources/templates/html/_stats.mustache @@ -0,0 +1,160 @@ + + + + + + + + + +{{#count.dialogs}}{{/count.dialogs}} + + + diff --git a/src/main/resources/templates/html/chat.mustache b/src/main/resources/templates/html/chat.mustache index ecc004b..fb2baf7 100644 --- a/src/main/resources/templates/html/chat.mustache +++ b/src/main/resources/templates/html/chat.mustache @@ -2,6 +2,8 @@ Telegram Backup for {{user.getUserString}} + + @@ -22,9 +24,9 @@ {{formatted_date}} {{/is_new_date}} -
  • - {{user_first_name}} +
  • {{formatted_time}} + {{user_first_name}} {{#text}}{{text}}{{/text}} {{#media_sticker}}{{/media_sticker}} {{#media_photo}}{{/media_photo}} @@ -35,5 +37,7 @@ Back to the overview + {{> _stats }} + diff --git a/src/main/resources/templates/html/index.mustache b/src/main/resources/templates/html/index.mustache index 3d8eecb..f4ecf3e 100644 --- a/src/main/resources/templates/html/index.mustache +++ b/src/main/resources/templates/html/index.mustache @@ -5,108 +5,7 @@ - - - - - - + @@ -138,11 +37,7 @@ {{/chats}} - - + {{> _stats }} - - - diff --git a/src/main/resources/templates/html/style.css b/src/main/resources/templates/html/style.css new file mode 100644 index 0000000..42a40ff --- /dev/null +++ b/src/main/resources/templates/html/style.css @@ -0,0 +1,48 @@ +body { font-family: Verdana, sans-serif; } + +ul.messages { + padding-left: 0px; +} + +li.date, li.message { + list-style-type: none; +} + +li.message { padding-left: 20px; } + +li.date { + font-weight: bold; + font-size: bigger; + margin-top: 10px; + text-transform: uppercase; + border-bottom: 1px dotted #888; +} + +li.message.from-me { + background-color: #eef !important; +} + +li .sender { + font-weight: bold; + min-width: 10em; + display: inline-block; +} + +li.message .photo { + vertical-align: top; +} + +li.message .photo img { + max-width: 250px; + max-height: 250px; +} + +li.message.same-user .sender { visibility: hidden; } + +li .time { + font-size: smaller; +} + +li.message.even { + background-color: #f8f8f8; +} diff --git a/src/main/resources/templates/stats/index.mustache b/src/main/resources/templates/stats/index.mustache deleted file mode 100644 index 21453d6..0000000 --- a/src/main/resources/templates/stats/index.mustache +++ /dev/null @@ -1,86 +0,0 @@ -count.chats: {{count.chats}} -count.dialogs: {{count.dialogs}} -count.messages: {{count.messages}} -count.messages.chats: {{count.messages.chats}} -count.messages.dialogs: {{count.messages.dialogs}} -count.messages.from_me: {{count.messages.from_me}} - -count.messages.type.message: {{count.messages.type.message}} -count.messages.type.empty_message: {{count.messages.type.empty_message}} -count.messages.type.service_message: {{count.messages.type.service_message}} - -count.messages.media_type.null: {{count.messages.media_type.null}} -count.messages.media_type.photo:{{count.messages.media_type.photo}} -count.messages.media_type.audio:{{count.messages.media_type.audio}} -count.messages.media_type.video:{{count.messages.media_type.video}} -count.messages.media_type.geo:{{count.messages.media_type.geo}} -count.messages.media_type.document:{{count.messages.media_type.document}} -count.messages.media_type.sticker:{{count.messages.media_type.sticker}} -count.messages.media_type.venue:{{count.messages.media_type.venue}} -count.messages.media_type.contact:{{count.messages.media_type.contact}} - - - - - - - - - -
    -
    -
    - -