From 7bf22f469226948726b62586018293fd251fdf77 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Thu, 30 Nov 2017 13:33:36 +0100 Subject: [PATCH 1/4] Implemented backing up of supergroups and channels added. Finally. Yaaaay. Be aware: There will possibly be a lot of bugs! --- .../CommandLineController.java | 76 +++++---- .../telegram_backup/CommandLineOptions.java | 56 +++--- .../telegram_backup/Database.java | 159 ++++++++++-------- .../telegram_backup/DatabaseUpdates.java | 84 ++++++--- .../telegram_backup/DownloadManager.java | 116 +++++++++---- .../exporter/HTMLExporter.java | 46 ++--- .../AbstractMediaFileManager.java | 25 +-- 7 files changed, 340 insertions(+), 222 deletions(-) diff --git a/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java b/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java index 732eb66..5a2291b 100644 --- a/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java +++ b/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -39,11 +39,11 @@ public class CommandLineController { private static Logger logger = LoggerFactory.getLogger(CommandLineController.class); private ApiStorage storage; public TelegramApp app; - + public CommandLineController() { logger.info("CommandLineController started. App version {}", Config.APP_APPVER); this.printHeader(); - + if (CommandLineOptions.cmd_version) { System.exit(0); } else if (CommandLineOptions.cmd_help) { @@ -53,20 +53,20 @@ public class CommandLineController { this.show_license(); System.exit(0); } - + this.setupFileBase(); - + if (CommandLineOptions.cmd_list_accounts) { this.list_accounts(); System.exit(0); } - + logger.debug("Initializing TelegramApp"); app = new TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG); logger.trace("Checking accounts"); String account = this.selectAccount(); - + logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login); logger.info("Initializing ApiStorage"); @@ -75,12 +75,12 @@ public class CommandLineController { TelegramUpdateHandler handler = new TelegramUpdateHandler(); logger.info("Creating Client"); TelegramClient client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler); - + try { logger.info("Initializing UserManager"); UserManager.init(client); Database.init(client); - + UserManager user = UserManager.getInstance(); if (!CommandLineOptions.cmd_login && !user.isLoggedIn()) { @@ -93,12 +93,12 @@ public class CommandLineController { throw new RuntimeException("Account / User mismatch"); } } - + if (CommandLineOptions.cmd_stats) { cmd_stats(); System.exit(0); } - + if (CommandLineOptions.val_test != null) { if (CommandLineOptions.val_test == 1) { TestFeatures.test1(); @@ -109,7 +109,7 @@ public class CommandLineController { } System.exit(1); } - + logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export); if (CommandLineOptions.val_export != null) { if (CommandLineOptions.val_export.toLowerCase().equals("html")) { @@ -119,25 +119,25 @@ public class CommandLineController { show_error("Unknown export format."); } } - + logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login); if (CommandLineOptions.cmd_login) { cmd_login(account); System.exit(0); } - + if (user.isLoggedIn()) { System.out.println("You are logged in as " + Utils.anonymize(user.getUserString())); } else { System.out.println("You are not logged in."); System.exit(1); } - + logger.info("Initializing Download Manager"); DownloadManager d = new DownloadManager(client, new CommandLineDownloadProgress()); logger.debug("Calling DownloadManager.downloadMessages with limit {}", CommandLineOptions.val_limit_messages); d.downloadMessages(CommandLineOptions.val_limit_messages); - + logger.debug("CommandLineOptions.cmd_no_media: {}", CommandLineOptions.cmd_no_media); if (!CommandLineOptions.cmd_no_media) { logger.debug("Calling DownloadManager.downloadMedia"); @@ -160,7 +160,7 @@ public class CommandLineController { } } } - + private void printHeader() { System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz"); System.out.println(); @@ -168,17 +168,17 @@ public class CommandLineController { System.out.println("welcome to redistribute it under certain conditions; run it with '--license' for details."); System.out.println(); } - + private void setupFileBase() { logger.debug("Target dir at startup: {}", Utils.anonymize(Config.FILE_BASE)); if (CommandLineOptions.val_target != null) { Config.FILE_BASE = CommandLineOptions.val_target; } logger.debug("Target dir after options: {}", Utils.anonymize(Config.FILE_BASE)); - + System.out.println("Base directory for files: " + Utils.anonymize(Config.FILE_BASE)); } - + private String selectAccount() { String account = null; Vector accounts = Utils.getAccounts(); @@ -216,7 +216,7 @@ public class CommandLineController { logger.debug("account: {}", Utils.anonymize(account)); return account; } - + private void cmd_stats() { System.out.println(); System.out.println("Stats:"); @@ -226,20 +226,20 @@ public class CommandLineController { System.out.format(format, "Number of chats", Database.getInstance().getChatCount()); System.out.format(format, "Number of users", Database.getInstance().getUserCount()); System.out.format(format, "Top message ID", Database.getInstance().getTopMessageID()); - + System.out.println(); System.out.println("Media Types:"); for(Map.Entry pair : Database.getInstance().getMessageMediaTypesWithCount().entrySet()) { System.out.format(format, pair.getKey(), pair.getValue()); } - + System.out.println(); System.out.println("Api layers of messages:"); for(Map.Entry pair : Database.getInstance().getMessageApiLayerWithCount().entrySet()) { System.out.format(format, pair.getKey(), pair.getValue()); } } - + private void cmd_login(String phone) throws RpcErrorException, IOException { UserManager user = UserManager.getInstance(); if (phone==null) { @@ -248,21 +248,21 @@ public class CommandLineController { phone = getLine(); } user.sendCodeToPhoneNumber(phone); - + System.out.println("Telegram sent you a code. Please enter it here."); String code = getLine(); user.verifyCode(code); - + if (user.isPasswordNeeded()) { System.out.println("We also need your account password. Please enter it now. It should not be printed, so it's okay if you see nothing while typing it."); String pw = getPassword(); user.verifyPassword(pw); } storage.setPrefix("+" + user.getUser().getPhone()); - + System.out.println("Everything seems fine. Please run this tool again with '--account +" + Utils.anonymize(user.getUser().getPhone()) + " to use this account."); } - + private String getLine() { if (System.console()!=null) { return System.console().readLine("> "); @@ -271,7 +271,7 @@ public class CommandLineController { return new Scanner(System.in).nextLine(); } } - + private String getPassword() { if (System.console()!=null) { return String.valueOf(System.console().readPassword("> ")); @@ -279,7 +279,7 @@ public class CommandLineController { return getLine(); } } - + private void show_help() { System.out.println("Valid options are:"); System.out.println(" -h, --help Shows this help."); @@ -298,8 +298,10 @@ public class CommandLineController { System.out.println(" -d, --daemon Keep running and automatically save new messages."); System.out.println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support."); System.out.println(" --stats Print some usage statistics."); + System.out.println(" --with-channels-and-supergroups"); + System.out.println(" Backup channels and supergroups as well."); } - + private void list_accounts() { System.out.println("List of available accounts:"); List accounts = Utils.getAccounts(); @@ -313,13 +315,13 @@ public class CommandLineController { System.out.println("Use '--login' to login to a telegram account."); } } - + public static void show_error(String error) { logger.error(error); System.out.println("ERROR: " + error); System.exit(1); } - + public static void show_license() { System.out.println( "GNU GENERAL PUBLIC LICENSE\n" + @@ -942,7 +944,7 @@ public class CommandLineController { "Program, unless a warranty or assumption of liability accompanies a\n" + "copy of the Program in return for a fee.\n" + "\n" + - " END OF TERMS AND CONDITIONS"); + " END OF TERMS AND CONDITIONS"); } - + } diff --git a/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java b/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java index bf217c4..d206343 100644 --- a/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java +++ b/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -30,7 +30,8 @@ class CommandLineOptions { public static boolean cmd_no_media = false; public static boolean cmd_anonymize = false; public static boolean cmd_stats = false; - + public static boolean cmd_channels_and_supergroups = false; + public static String val_account = null; public static Integer val_limit_messages = null; public static String val_target = null; @@ -45,16 +46,16 @@ class CommandLineOptions { switch (last_cmd) { case "--account": val_account = arg; break; - + case "--limit-messages": val_limit_messages = Integer.parseInt(arg); break; - + case "--target": val_target = arg; break; - + case "--export": val_export = arg; break; - + case "--test": val_test = Integer.parseInt(arg); break; } @@ -65,58 +66,61 @@ class CommandLineOptions { switch (arg) { case "-a": case "--account": last_cmd = "--account"; continue; - + case "-h": case "--help": cmd_help = true; break; - + case "-l": case "--login": cmd_login = true; break; - + case "--debug": cmd_debug = true; break; - + case "--trace": cmd_trace = true; break; - + case "--trace-telegram": cmd_trace_telegram = true; break; - + case "-A": case "--list-accounts": cmd_list_accounts = true; break; - + case "--limit-messages": last_cmd = arg; continue; - + case "--console": cmd_console = true; break; - + case "-t": case "--target": last_cmd = "--target"; continue; - + case "-V": case "--version": cmd_version = true; break; - + case "-e": case "--export": last_cmd = "--export"; continue; - + case "--license": cmd_license = true; break; - + case "-d": case "--daemon": cmd_daemon = true; break; - + case "--no-media": cmd_no_media = true; break; - + case "--test": last_cmd = "--test"; continue; - + case "--anonymize": cmd_anonymize = true; break; - + case "--stats": cmd_stats = true; break; - + + case "--with-channels-and-supergroups": + cmd_channels_and_supergroups = true; break; + default: throw new RuntimeException("Unknown command " + arg); } diff --git a/src/main/java/de/fabianonline/telegram_backup/Database.java b/src/main/java/de/fabianonline/telegram_backup/Database.java index b715e97..c3ef3df 100644 --- a/src/main/java/de/fabianonline/telegram_backup/Database.java +++ b/src/main/java/de/fabianonline/telegram_backup/Database.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -54,7 +54,7 @@ public class Database { public TelegramClient client; private final static Logger logger = LoggerFactory.getLogger(Database.class); private static Database instance = null; - + private Database(TelegramClient client) { this.user_manager = UserManager.getInstance(); this.client = client; @@ -64,34 +64,34 @@ public class Database { } catch(ClassNotFoundException e) { CommandLineController.show_error("Could not load jdbc-sqlite class."); } - + String path = "jdbc:sqlite:" + user_manager.getFileBase() + Config.FILE_NAME_DB; - + try { conn = DriverManager.getConnection(path); stmt = conn.createStatement(); } catch (SQLException e) { CommandLineController.show_error("Could not connect to SQLITE database."); } - + // Run updates DatabaseUpdates updates = new DatabaseUpdates(conn, this); updates.doUpdates(); - + System.out.println("Database is ready."); } - + public static void init(TelegramClient c) { instance = new Database(c); } - + public static Database getInstance() { if (instance == null) throw new RuntimeException("Database is not initialized but getInstance() was called."); return instance; } - + public void backupDatabase(int currentVersion) { String filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion); System.out.println(" Creating a backup of your database as " + filename); @@ -109,17 +109,21 @@ public class Database { throw new RuntimeException("Could not create backup."); } } - + public int getTopMessageID() { try { - ResultSet rs = stmt.executeQuery("SELECT MAX(id) FROM messages"); + ResultSet rs = stmt.executeQuery("SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog')"); rs.next(); return rs.getInt(1); } catch (SQLException e) { return 0; } } - + + public int getTopMessageIDForChannel(int id) { + return queryInt("SELECT MAX(message_id) FROM messages WHERE source_id=" + id + " AND source_type IN('channel', 'supergroup')"); + } + public void logRun(int start_id, int end_id, int count) { try { PreparedStatement ps = conn.prepareStatement("INSERT INTO runs "+ @@ -132,7 +136,7 @@ public class Database { ps.execute(); } catch (SQLException e) {} } - + public int queryInt(String query) { try { ResultSet rs = stmt.executeQuery(query); @@ -142,16 +146,16 @@ public class Database { throw new RuntimeException("Could not get count of messages."); } } - + public int getMessageCount() { return queryInt("SELECT COUNT(*) FROM messages"); } public int getChatCount() { return queryInt("SELECT COUNT(*) FROM chats"); } public int getUserCount() { return queryInt("SELECT COUNT(*) FROM users"); } - + public LinkedList getMissingIDs() { try { LinkedList missing = new LinkedList(); int max = getTopMessageID(); - ResultSet rs = stmt.executeQuery("SELECT id FROM messages ORDER BY id"); + ResultSet rs = stmt.executeQuery("SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id"); rs.next(); int id=rs.getInt(1); for (int i=1; i<=max; i++) { @@ -172,17 +176,17 @@ public class Database { throw new RuntimeException("Could not get list of ids."); } } - + public synchronized void saveMessages(TLVector all, Integer api_layer) { try { //"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " + //"VALUES " + //"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); String columns = - "(id, message_type, dialog_id, chat_id, sender_id, fwd_from_id, text, time, has_media, media_type, media_file, media_size, data, api_layer) "+ + "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type, media_file, media_size, data, api_layer) "+ "VALUES " + - "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - //1 2 3 4 5 6 7 8 9 10 11 12 13 14 + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + //1 2 3 4 5 6 7 8 9 10 11 12 13 14 PreparedStatement ps = conn.prepareStatement("INSERT OR REPLACE INTO messages " + columns); PreparedStatement ps_insert_or_ignore = conn.prepareStatement("INSERT OR IGNORE INTO messages " + columns); @@ -193,20 +197,29 @@ public class Database { ps.setString(2, "message"); TLAbsPeer peer = msg.getToId(); if (peer instanceof TLPeerChat) { - ps.setNull(3, Types.INTEGER); + ps.setString(3, "group"); ps.setInt(4, ((TLPeerChat)peer).getChatId()); } else if (peer instanceof TLPeerUser) { int id = ((TLPeerUser)peer).getUserId(); if (id==this.user_manager.getUser().getId()) { id = msg.getFromId(); } - ps.setInt(3, id); - ps.setNull(4, Types.INTEGER); + ps.setString(3, "dialog"); + ps.setInt(4, id); + } else if (peer instanceof TLPeerChannel) { + ps.setString(3, "channel"); + ps.setInt(4, ((TLPeerChannel)peer).getChannelId()); } else { throw new RuntimeException("Unexpected Peer type: " + peer.getClass().getName()); } - ps.setInt(5, msg.getFromId()); - + + if (peer instanceof TLPeerChannel) { + // Message in a channel don't have a sender -> insert a null + ps.setNull(5, Types.INTEGER); + } else { + ps.setInt(5, msg.getFromId()); + } + if (msg.getFwdFrom() != null && msg.getFwdFrom().getFromId() != null) { ps.setInt(6, msg.getFwdFrom().getFromId()); } else { @@ -388,7 +401,7 @@ public class Database { throw new RuntimeException("Exception shown above happened."); } } - + public LinkedList getMessagesWithMedia() { try { LinkedList list = new LinkedList(); @@ -403,7 +416,7 @@ public class Database { throw new RuntimeException("Exception occured. See above."); } } - + public int getMessagesFromUserCount() { try { ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM messages WHERE sender_id=" + user_manager.getUser().getId()); @@ -413,7 +426,7 @@ public class Database { throw new RuntimeException(e); } } - + public LinkedList getIdsFromQuery(String query) { try { LinkedList list = new LinkedList(); @@ -423,31 +436,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 WHERE " + c.getQuery() + " GROUP BY message_type"); + ResultSet rs = stmt.executeQuery("SELECT message_type, COUNT(message_id) FROM messages WHERE " + c.getQuery() + " GROUP BY message_type"); while (rs.next()) { 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 WHERE " + c.getQuery() + " GROUP BY media_type"); + ResultSet rs = stmt.executeQuery("SELECT media_type, COUNT(message_id) FROM messages WHERE " + c.getQuery() + " GROUP BY media_type"); while (rs.next()) { String s = rs.getString(1); if (s==null) { @@ -461,7 +474,7 @@ public class Database { return map; } catch (Exception e) { throw new RuntimeException(e); } } - + public HashMap getMessageApiLayerWithCount() { HashMap map = new HashMap(); try { @@ -475,11 +488,11 @@ public class Database { 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(); @@ -501,7 +514,7 @@ public class Database { return map; } catch (Exception e) { throw new RuntimeException(e); } } - + public int[][] getMessageTimesMatrix() { return getMessageTimesMatrix(new GlobalChat()); } @@ -530,13 +543,13 @@ public class Database { return "unknown"; } } - - + + public LinkedList getListOfChatsForExport() { LinkedList list = new LinkedList(); try { ResultSet rs = stmt.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c "+ - "FROM chats, messages WHERE messages.chat_id IS NOT NULL AND messages.chat_id=chats.id "+ + "FROM chats, messages WHERE messages.source_type IN('group', 'supergroup', 'channel') AND messages.source_id=chats.id "+ "GROUP BY chats.id ORDER BY c DESC"); while (rs.next()) { list.add(new Chat(rs.getInt(1), rs.getString(2), rs.getInt(3))); @@ -548,14 +561,14 @@ public class Database { throw new RuntimeException("Exception above!"); } } - - + + public LinkedList getListOfDialogsForExport() { LinkedList list = new LinkedList(); try { ResultSet rs = stmt.executeQuery( "SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " + - "FROM users, messages WHERE messages.dialog_id IS NOT NULL AND messages.dialog_id=users.id " + + "FROM users, messages WHERE messages.source_type='dialog' AND messages.source_id=users.id " + "GROUP BY users.id ORDER BY c DESC"); while (rs.next()) { list.add(new Dialog(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getInt(5))); @@ -567,23 +580,25 @@ public class Database { throw new RuntimeException("Exception above!"); } } - + public LinkedList> getMessagesForExport(AbstractChat c) { try { - - ResultSet rs = stmt.executeQuery("SELECT messages.id as message_id, text, time*1000 as time, has_media, " + + + ResultSet rs = stmt.executeQuery("SELECT messages.message_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 " + c.getQuery() + " " + - "ORDER BY messages.id"); + "FROM messages " + + "LEFT JOIN users ON users.id=messages.sender_id " + + "LEFT JOIN users AS users_fwd ON users_fwd.id=fwd_from_id WHERE " + + c.getQuery() + " " + + "ORDER BY messages.message_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; @@ -606,7 +621,7 @@ public class Database { 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++; } @@ -617,7 +632,7 @@ public class Database { throw new RuntimeException("Exception above!"); } } - + public static TLMessage bytesToTLMessage(byte[] b) { try { if (b==null) return null; @@ -630,22 +645,22 @@ public class Database { throw new RuntimeException("Could not deserialize message."); } } - - - - - + + + + + public abstract class AbstractChat { public abstract String getQuery(); } - + public class Dialog extends AbstractChat{ public int id; public String first_name; public String last_name; public String username; public int count; - + public Dialog (int id, String first_name, String last_name, String username, int count) { this.id = id; this.first_name = first_name; @@ -653,28 +668,28 @@ public class Database { this.username = username; this.count = count; } - - public String getQuery() { return "dialog_id=" + id; } + + public String getQuery() { return "source_type='dialog' AND source_id=" + id; } } - + public class Chat extends AbstractChat { public int id; public String name; public int count; - + public Chat(int id, String name, int count) { this.id = id; this.name = name; this.count = count; } - - public String getQuery() {return "chat_id=" + id; } + + public String getQuery() {return "source_type IN('group', 'supergroup', 'channel') AND source_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(); @@ -683,7 +698,7 @@ public class Database { 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/DatabaseUpdates.java b/src/main/java/de/fabianonline/telegram_backup/DatabaseUpdates.java index 8de519b..9fece25 100644 --- a/src/main/java/de/fabianonline/telegram_backup/DatabaseUpdates.java +++ b/src/main/java/de/fabianonline/telegram_backup/DatabaseUpdates.java @@ -20,7 +20,7 @@ public class DatabaseUpdates { protected Database db; private static final Logger logger = LoggerFactory.getLogger(DatabaseUpdates.class); private static LinkedList updates = new LinkedList(); - + public DatabaseUpdates(Connection conn, Database db) { this.conn = conn; this.db = db; @@ -32,8 +32,9 @@ public class DatabaseUpdates { register(new DB_Update_5(conn, db)); register(new DB_Update_6(conn, db)); register(new DB_Update_7(conn, db)); + register(new DB_Update_8(conn, db)); } - + public void doUpdates() { try { Statement stmt = conn.createStatement(); @@ -59,7 +60,7 @@ public class DatabaseUpdates { logger.debug("version: {}", version); System.out.println("Database version: " + version); logger.debug("Max available database version is {}", getMaxPossibleVersion()); - + if (version < getMaxPossibleVersion()) { logger.debug("Update is necessary. {} => {}.", version, getMaxPossibleVersion()); boolean backup = false; @@ -77,7 +78,7 @@ public class DatabaseUpdates { logger.debug("NOT performing a backup, because we are creating a fresh database and don't need a backup of that."); } } - + logger.debug("Applying updates"); try { for (int i=version+1; i<=getMaxPossibleVersion(); i++) { @@ -87,18 +88,18 @@ public class DatabaseUpdates { } else { logger.debug("No update necessary."); } - + } catch (SQLException e) { throw new RuntimeException(e); } } - + private DatabaseUpdate getUpdateToVersion(int i) { return updates.get(i-1); } - + private int getMaxPossibleVersion() { return updates.size(); } - + private void register(DatabaseUpdate d) { logger.debug("Registering {} as update to version {}", d.getClass().getName(), d.getVersion()); if (d.getVersion() != updates.size()+1) { @@ -119,7 +120,7 @@ abstract class DatabaseUpdate { stmt = conn.createStatement(); } catch (SQLException e) { throw new RuntimeException(e); } this.db = db; - + } public void doUpdate() throws SQLException { logger.debug("Applying update to version {}", getVersion()); @@ -128,16 +129,20 @@ abstract class DatabaseUpdate { logger.debug("Saving current database version to the db"); stmt.executeUpdate("INSERT INTO database_versions (version) VALUES (" + getVersion() + ")"); } - + protected abstract void _doUpdate() throws SQLException; public abstract int getVersion(); public boolean needsBackup() { return false; } + protected void execute(String sql) throws SQLException { + logger.debug("Executing: {}", sql); + stmt.executeUpdate(sql); + } } class DB_Update_1 extends DatabaseUpdate { public int getVersion() { return 1; } public DB_Update_1(Connection conn, Database db) { super(conn, db); } - + protected void _doUpdate() throws SQLException { stmt.executeUpdate("CREATE TABLE messages (" + "id INTEGER PRIMARY KEY ASC, " @@ -162,14 +167,14 @@ class DB_Update_1 extends DatabaseUpdate { + "username TEXT, " + "type TEXT)"); stmt.executeUpdate("CREATE TABLE database_versions (" - + "version INTEGER)"); + + "version INTEGER)"); } } class DB_Update_2 extends DatabaseUpdate { public int getVersion() { return 2; } public DB_Update_2(Connection conn, Database db) { super(conn, db); } - + protected void _doUpdate() throws SQLException { stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'"); stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT"); @@ -179,7 +184,7 @@ class DB_Update_2 extends DatabaseUpdate { class DB_Update_3 extends DatabaseUpdate { public int getVersion() { return 3; } public DB_Update_3(Connection conn, Database db) { super(conn, db); } - + protected void _doUpdate() throws SQLException { stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'"); } @@ -188,7 +193,7 @@ class DB_Update_3 extends DatabaseUpdate { class DB_Update_4 extends DatabaseUpdate { public int getVersion() { return 4; } public DB_Update_4(Connection conn, Database db) { super(conn, db); } - + protected void _doUpdate() throws SQLException { stmt.executeUpdate("CREATE TABLE messages_new (id INTEGER PRIMARY KEY ASC, dialog_id INTEGER, to_id INTEGER, from_id INTEGER, from_type TEXT, text TEXT, time INTEGER, has_media BOOLEAN, sticker TEXT, data BLOB, type TEXT);"); stmt.executeUpdate("INSERT INTO messages_new SELECT * FROM messages"); @@ -210,7 +215,7 @@ class DB_Update_6 extends DatabaseUpdate { public int getVersion() { return 6; } public DB_Update_6(Connection conn, Database db) { super(conn, db); } public boolean needsBackup() { return true; } - + protected void _doUpdate() throws SQLException { stmt.executeUpdate( "CREATE TABLE messages_new (\n" + @@ -256,7 +261,7 @@ class DB_Update_6 extends DatabaseUpdate { } query.append("\nFROM messages"); stmt.executeUpdate(query.toString()); - + System.out.println(" Updating the data (this might take some time)..."); ResultSet rs = stmt.executeQuery("SELECT id, data FROM messages_new"); PreparedStatement ps = conn.prepareStatement("UPDATE messages_new SET fwd_from_id=?, media_type=?, media_file=?, media_size=? WHERE id=?"); @@ -294,10 +299,51 @@ class DB_Update_7 extends DatabaseUpdate { public int getVersion() { return 7; } public boolean needsBackup() { return true; } public DB_Update_7(Connection conn, Database db) { super(conn, db); } - + protected void _doUpdate() throws SQLException { stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER"); - + stmt.executeUpdate("UPDATE messages SET api_layer=51"); } } + +class DB_Update_8 extends DatabaseUpdate { + public int getVersion() { return 8; } + public DB_Update_8(Connection conn, Database db) { super(conn, db); } + public boolean needsBackup() { return true; } + + protected void _doUpdate() throws SQLException { + execute("ALTER TABLE messages ADD COLUMN source_type TEXT"); + execute("ALTER TABLE messages ADD COLUMN source_id INTEGER"); + execute("update messages set source_type='dialog', source_id=dialog_id where dialog_id is not null"); + execute("update messages set source_type='group', source_id=chat_id where chat_id is not null"); + + execute("CREATE TABLE messages_new (" + + "id INTEGER PRIMARY KEY AUTOINCREMENT," + + "message_id INTEGER," + + "message_type TEXT," + + "source_type TEXT," + + "source_id INTEGER," + + "sender_id INTEGER," + + "fwd_from_id INTEGER," + + "text TEXT," + + "time INTEGER," + + "has_media BOOLEAN," + + "media_type TEXT," + + "media_file TEXT," + + "media_size INTEGER," + + "media_json TEXT," + + "markup_json TEXT," + + "data BLOB," + + "api_layer INTEGER)"); + execute("INSERT INTO messages_new" + + "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + + "media_file, media_size, media_json, markup_json, data, api_layer)" + + "SELECT " + + "id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + + "media_file, media_size, media_json, markup_json, data, api_layer FROM messages"); + execute("DROP TABLE messages"); + execute("ALTER TABLE messages_new RENAME TO 'messages'"); + execute("CREATE UNIQUE INDEX unique_messages ON messages (source_type, source_id, message_id)"); + } +} diff --git a/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java b/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java index c448e48..948f904 100644 --- a/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java +++ b/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -42,6 +42,7 @@ import java.io.FileOutputStream; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.HashMap; import java.util.Random; import java.net.URL; import java.util.concurrent.TimeoutException; @@ -59,14 +60,15 @@ public class DownloadManager { static TelegramClient download_client; static boolean last_download_succeeded = true; static final Logger logger = LoggerFactory.getLogger(DownloadManager.class); - + static public enum DownloadType { NORMAL, CHANNELS_AND_SUPERGROUPS } + public DownloadManager(TelegramClient c, DownloadProgressInterface p) { this.user = UserManager.getInstance(); this.client = c; this.prog = p; this.db = Database.getInstance(); } - + public void downloadMessages(Integer limit) throws RpcErrorException, IOException { boolean completed = true; do { @@ -90,7 +92,7 @@ public class DownloadManager { } } while (!completed); } - + public void _downloadMessages(Integer limit) throws RpcErrorException, IOException, TimeoutException { logger.info("This is _downloadMessages with limit {}", limit); int dialog_limit = 100; @@ -103,6 +105,7 @@ public class DownloadManager { new TLInputPeerEmpty(), dialog_limit); logger.debug("Got {} dialogs", dialogs.getDialogs().size()); + for (TLDialog d : dialogs.getDialogs()) { if (d.getTopMessage() > max_message_id && ! (d.getPeer() instanceof TLPeerChannel)) { logger.trace("Updating top message id: {} => {}. Dialog type: {}", max_message_id, d.getTopMessage(), d.getPeer().getClass().getName()); @@ -131,11 +134,11 @@ public class DownloadManager { } else { int start_id = max_database_id + 1; int end_id = max_message_id; - + List ids = makeIdList(start_id, end_id); - downloadMessages(ids); + downloadMessages(ids, null); } - + logger.info("Searching for missing messages in the db"); int count_missing = 0; System.out.println("Checking message database for completeness..."); @@ -143,8 +146,8 @@ public class DownloadManager { int db_max = db.getTopMessageID(); logger.debug("db_count: {}", db_count); logger.debug("db_max: {}", db_max); - - if (db_count != db_max) { + + /*if (db_count != db_max) { if (limit != null) { System.out.println("You are missing messages in your database. But since you're using '--limit-messages', I won't download these now."); } else { @@ -156,19 +159,57 @@ public class DownloadManager { count_missing = all_missing_ids.size(); System.out.println("" + all_missing_ids.size() + " messages are missing in your Database."); System.out.println("I can (and will) download " + downloadable_missing_ids.size() + " of them."); - - downloadMessages(downloadable_missing_ids); + + downloadMessages(downloadable_missing_ids, null); + } + + logger.info("Logging this run"); + db.logRun(Math.min(max_database_id + 1, max_message_id), max_message_id, count_missing); + } + */ + + if (CommandLineOptions.cmd_channels_and_supergroups) { + logger.info("Processing channels and supergroups..."); + + HashMap channel_access_hashes = new HashMap(); + + // TODO Add chat title (and other stuff?) to the database + for (TLAbsChat c : dialogs.getChats()) { + if (c instanceof TLChannel) { + TLChannel ch = (TLChannel)c; + channel_access_hashes.put(c.getId(), ch.getAccessHash()); + // Channel: TLChannel + // Supergroup: getMegagroup()==true + System.out.println("" + c.getId() + " - " + (ch.getMegagroup() ? "Supergroup" : "Channel") +": " + ch.getTitle()); + } + } + + + + for (TLDialog d : dialogs.getDialogs()) { + if (d.getPeer() instanceof TLPeerChannel) { + int channel_id = ((TLPeerChannel)d.getPeer()).getChannelId(); + int max_known_id = db.getTopMessageIDForChannel(channel_id); + if (d.getTopMessage() > max_known_id) { + List ids = makeIdList(max_known_id+1, d.getTopMessage()); + //messagesPerChannel.put(id, makeIdList(max_known_id+1, d.getTopMessage())); + Long access_hash = channel_access_hashes.get(channel_id); + if (access_hash==null) { + throw new RuntimeException("AccessHash for Channel missing."); + } + TLInputChannel channel = new TLInputChannel(channel_id, access_hash); + downloadMessages(ids, channel); + } + System.out.println("" + channel_id + " - Known: " + max_known_id + " Availalble: " + d.getTopMessage()); + } } } - - logger.info("Logging this run"); - db.logRun(Math.min(max_database_id + 1, max_message_id), max_message_id, count_missing); } - - private void downloadMessages(List ids) throws RpcErrorException, IOException { + + private void downloadMessages(List ids, TLInputChannel channel) throws RpcErrorException, IOException { prog.onMessageDownloadStart(ids.size()); boolean has_seen_flood_wait_message = false; - + logger.debug("Entering download loop"); while (ids.size()>0) { logger.trace("Loop"); @@ -181,7 +222,7 @@ public class DownloadManager { } logger.trace("vector.size(): {}", vector.size()); logger.trace("ids.size(): {}", ids.size()); - + TLAbsMessages response; int tries = 0; while(true) { @@ -191,7 +232,11 @@ public class DownloadManager { } tries++; try { - response = client.messagesGetMessages(vector); + if (channel == null) { + response = client.messagesGetMessages(vector); + } else { + response = client.channelsGetMessages(channel, vector); + } break; } catch (RpcErrorException e) { if (e.getCode()==420) { // FLOOD_WAIT @@ -206,6 +251,7 @@ public class DownloadManager { if (response.getMessages().size() != vector.size()) { CommandLineController.show_error("Requested " + vector.size() + " messages, but got " + response.getMessages().size() + ". That is unexpected. Quitting."); } + prog.onMessageDownloaded(response.getMessages().size()); db.saveMessages(response.getMessages(), Kotlogram.API_LAYER); db.saveChats(response.getChats()); @@ -216,10 +262,10 @@ public class DownloadManager { } catch (InterruptedException e) {} } logger.debug("Finished."); - + prog.onMessageDownloadFinished(); } - + public void downloadMedia() throws RpcErrorException, IOException { download_client = client.getDownloaderClient(); boolean completed = true; @@ -245,7 +291,7 @@ public class DownloadManager { }*/ } while (!completed); } - + private void _downloadMedia() throws RpcErrorException, IOException { logger.info("This is _downloadMedia"); logger.info("Checking if there are messages in the DB with a too old API layer"); @@ -253,9 +299,9 @@ public class DownloadManager { if (ids.size()>0) { System.out.println("You have " + ids.size() + " messages in your db that need an update. Doing that now."); logger.debug("Found {} messages", ids.size()); - downloadMessages(ids); + downloadMessages(ids, null); } - + LinkedList messages = this.db.getMessagesWithMedia(); logger.debug("Database returned {} messages with media", messages.size()); prog.onMediaDownloadStart(messages.size()); @@ -283,30 +329,30 @@ public class DownloadManager { } prog.onMediaDownloadFinished(); } - + private List makeIdList(int start, int end) { LinkedList a = new LinkedList(); for (int i=start; i<=end; i++) a.add(i); - return a; + return a; } - + public static void downloadFile(TelegramClient client, String targetFilename, int size, int dcId, long volumeId, int localId, long secret) throws RpcErrorException, IOException, TimeoutException { TLInputFileLocation loc = new TLInputFileLocation(volumeId, localId, secret); downloadFileFromDc(client, targetFilename, loc, dcId, size); } - + public static void downloadFile(TelegramClient client, String targetFilename, int size, int dcId, long id, long accessHash) throws RpcErrorException, IOException, TimeoutException { TLInputDocumentFileLocation loc = new TLInputDocumentFileLocation(id, accessHash); downloadFileFromDc(client, targetFilename, loc, dcId, size); } - + private static boolean downloadFileFromDc(TelegramClient client, String target, TLAbsInputFileLocation loc, Integer dcID, int size) throws RpcErrorException, IOException, TimeoutException { FileOutputStream fos = null; try { String temp_filename = target + ".downloading"; logger.debug("Downloading file {}", target); logger.trace("Temporary filename: {}", temp_filename); - + int offset = 0; if (new File(temp_filename).isFile()) { logger.info("Temporary filename already exists; continuing this file"); @@ -340,10 +386,10 @@ public class DownloadManager { throw e; } } - + offset += response.getBytes().getData().length; logger.trace("response: {} total size: {}", response.getBytes().getData().length, offset); - + fos.write(response.getBytes().getData()); fos.flush(); try { TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_FILE); } catch(InterruptedException e) {} @@ -394,7 +440,7 @@ public class DownloadManager { throw ex; } } - + public static boolean downloadExternalFile(String target, String url) throws IOException { FileUtils.copyURLToFile(new URL(url), new File(target), 5000, 5000); return true; 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 1935fef..961c83e 100644 --- a/src/main/java/de/fabianonline/telegram_backup/exporter/HTMLExporter.java +++ b/src/main/java/de/fabianonline/telegram_backup/exporter/HTMLExporter.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -44,60 +44,60 @@ import org.slf4j.LoggerFactory; public class HTMLExporter { private static Logger logger = LoggerFactory.getLogger(HTMLExporter.class); - + public void export() throws IOException { try { UserManager user = UserManager.getInstance(); Database db = Database.getInstance(); - + // 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.trace("Got {} dialogs", dialogs.size()); logger.debug("Fetching chats"); LinkedList chats = db.getListOfChatsForExport(); logger.trace("Got {} chats", chats.size()); - + logger.debug("Generating index.html"); HashMap scope = new HashMap(); scope.put("user", user); scope.put("dialogs", dialogs); scope.put("chats", chats); - + // Collect stats data 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()); scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix())); - + scope.putAll(db.getMessageAuthorsWithCount()); scope.putAll(db.getMessageTypesWithCount()); scope.putAll(db.getMessageMediaTypesWithCount()); - + MustacheFactory mf = new DefaultMustacheFactory(); Mustache mustache = mf.compile("templates/html/index.mustache"); OutputStreamWriter w = getWriter(base + "index.html"); mustache.execute(w, scope); w.close(); - + mustache = mf.compile("templates/html/chat.mustache"); - + int i=0; logger.debug("Generating {} dialog pages", dialogs.size()); for (Database.Dialog d : dialogs) { @@ -108,17 +108,17 @@ public class HTMLExporter { scope.put("user", user); 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 = getWriter(base + "dialogs" + File.separatorChar + "user_" + d.id + ".html"); mustache.execute(w, scope); w.close(); } - + i=0; logger.debug("Generating {} chat pages", chats.size()); for (Database.Chat c : chats) { @@ -129,17 +129,17 @@ public class HTMLExporter { scope.put("user", user); scope.put("chat", c); scope.put("messages", messages); - + scope.putAll(db.getMessageAuthorsWithCount(c)); scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(c))); scope.putAll(db.getMessageTypesWithCount(c)); scope.putAll(db.getMessageMediaTypesWithCount(c)); - + w = getWriter(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"); @@ -152,7 +152,7 @@ public class HTMLExporter { throw e; } } - + private OutputStreamWriter getWriter(String filename) throws FileNotFoundException { logger.trace("Creating writer for file {}", Utils.anonymize(filename)); return new OutputStreamWriter(new FileOutputStream(filename), Charset.forName("UTF-8").newEncoder()); @@ -170,7 +170,7 @@ public class HTMLExporter { sb.append("]"); return sb.toString(); } - + private String mapToString(Map map) { StringBuilder sb = new StringBuilder("["); for (Map.Entry entry : map.entrySet()) { diff --git a/src/main/java/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.java b/src/main/java/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.java index 106551a..f8e3e5a 100644 --- a/src/main/java/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.java +++ b/src/main/java/de/fabianonline/telegram_backup/mediafilemanager/AbstractMediaFileManager.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -48,7 +48,7 @@ public abstract class AbstractMediaFileManager { protected TLMessage message; protected TelegramClient client; protected boolean isEmpty = false; - + public AbstractMediaFileManager(TLMessage msg, UserManager user, TelegramClient client) {this.user = user; this.message = msg; this.client = client;}; public abstract int getSize(); public abstract String getExtension(); @@ -64,22 +64,27 @@ public abstract class AbstractMediaFileManager { new File(path).mkdirs(); return path; } - public String getTargetFilename() { return "" + message.getId() + "." + getExtension(); } + public String getTargetFilename() { + if (message.getToId() instanceof TLPeerChannel) { + return "channel_" + ((TLPeerChannel)message.getToId()).getChannelId() + "_" + message.getId() + "." + getExtension(); + } + return "" + message.getId() + "." + getExtension(); + } public String getTargetPathAndFilename() { return getTargetPath() + getTargetFilename(); } - + protected String extensionFromMimetype(String mime) { switch(mime) { case "text/plain": return "txt"; } - + int i = mime.lastIndexOf('/'); String ext = mime.substring(i+1).toLowerCase(); - + if (ext=="unknown") return "dat"; - + return ext; } - + public abstract String getLetter(); public abstract String getName(); public abstract String getDescription(); From b8d9e2a1b692601b2d905134d069619c574dcc0b Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Fri, 1 Dec 2017 14:13:58 +0100 Subject: [PATCH 2/4] Some improvements in the output. --- .../CommandLineDownloadProgress.java | 30 +++++++++++-------- .../telegram_backup/DownloadManager.java | 25 +++++++++------- .../DownloadProgressInterface.java | 10 +++---- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/main/java/de/fabianonline/telegram_backup/CommandLineDownloadProgress.java b/src/main/java/de/fabianonline/telegram_backup/CommandLineDownloadProgress.java index d56a4c8..1e1c138 100644 --- a/src/main/java/de/fabianonline/telegram_backup/CommandLineDownloadProgress.java +++ b/src/main/java/de/fabianonline/telegram_backup/CommandLineDownloadProgress.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -22,11 +22,18 @@ import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager class CommandLineDownloadProgress implements DownloadProgressInterface { private int mediaCount = 0; private int i = 0; - - public void onMessageDownloadStart(int count) { i=0; System.out.println("Downloading " + count + " messages."); } + + public void onMessageDownloadStart(int count, String source) { + i=0; + if (source==null) { + System.out.println("Downloading " + count + " messages."); + } else { + System.out.println("Downloading " + count + " messages from " + Utils.anonymize(source)); + } + } public void onMessageDownloaded(int number) { i+=number; System.out.print("..." + i); } public void onMessageDownloadFinished() { System.out.println(" done."); } - + public void onMediaDownloadStart(int count) { i = 0; mediaCount = count; @@ -37,22 +44,21 @@ class CommandLineDownloadProgress implements DownloadProgressInterface { System.out.println("'.' - Previously downloaded file 'e' - Empty file"); System.out.println("' ' - Ignored media type (weblinks or contacts, for example)"); System.out.println("'x' - File skipped because of timeout errors"); - System.out.println("" + count + " Files to check / download"); + System.out.println("" + count + " Files to check / download"); } - + public void onMediaDownloaded(AbstractMediaFileManager fm) { show(fm.getLetter().toUpperCase()); } - + public void onMediaDownloadedEmpty() { show("e"); } public void onMediaAlreadyPresent(AbstractMediaFileManager fm) { show("."); } public void onMediaSkipped() { show("x"); } - + public void onMediaDownloadFinished() { showNewLine(); System.out.println("Done."); } - + private void show(String letter) { System.out.print(letter); i++; if (i % 100 == 0) showNewLine();} private void showNewLine() { System.out.println(" - " + i + "/" + mediaCount); } } - diff --git a/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java b/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java index 948f904..c433418 100644 --- a/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java +++ b/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java @@ -35,6 +35,7 @@ import com.github.badoualy.telegram.tl.exception.RpcErrorException; import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile; import org.slf4j.LoggerFactory; import org.slf4j.Logger; +import com.google.gson.Gson; import java.io.IOException; import java.io.File; @@ -60,7 +61,7 @@ public class DownloadManager { static TelegramClient download_client; static boolean last_download_succeeded = true; static final Logger logger = LoggerFactory.getLogger(DownloadManager.class); - static public enum DownloadType { NORMAL, CHANNELS_AND_SUPERGROUPS } + boolean has_seen_flood_wait_message = false; public DownloadManager(TelegramClient c, DownloadProgressInterface p) { this.user = UserManager.getInstance(); @@ -136,7 +137,7 @@ public class DownloadManager { int end_id = max_message_id; List ids = makeIdList(start_id, end_id); - downloadMessages(ids, null); + downloadMessages(ids, null, null); } logger.info("Searching for missing messages in the db"); @@ -169,18 +170,19 @@ public class DownloadManager { */ if (CommandLineOptions.cmd_channels_and_supergroups) { - logger.info("Processing channels and supergroups..."); + System.out.println("Processing channels and supergroups..."); HashMap channel_access_hashes = new HashMap(); + HashMap channel_names = new HashMap(); // TODO Add chat title (and other stuff?) to the database for (TLAbsChat c : dialogs.getChats()) { if (c instanceof TLChannel) { TLChannel ch = (TLChannel)c; channel_access_hashes.put(c.getId(), ch.getAccessHash()); + channel_names.put(c.getId(), ch.getTitle()); // Channel: TLChannel // Supergroup: getMegagroup()==true - System.out.println("" + c.getId() + " - " + (ch.getMegagroup() ? "Supergroup" : "Channel") +": " + ch.getTitle()); } } @@ -192,23 +194,24 @@ public class DownloadManager { int max_known_id = db.getTopMessageIDForChannel(channel_id); if (d.getTopMessage() > max_known_id) { List ids = makeIdList(max_known_id+1, d.getTopMessage()); - //messagesPerChannel.put(id, makeIdList(max_known_id+1, d.getTopMessage())); Long access_hash = channel_access_hashes.get(channel_id); if (access_hash==null) { throw new RuntimeException("AccessHash for Channel missing."); } + String channel_name = channel_names.get(channel_id); + if (channel_name == null) { + channel_name = "?"; + } TLInputChannel channel = new TLInputChannel(channel_id, access_hash); - downloadMessages(ids, channel); + downloadMessages(ids, channel, "channel " + channel_name); } - System.out.println("" + channel_id + " - Known: " + max_known_id + " Availalble: " + d.getTopMessage()); } } } } - private void downloadMessages(List ids, TLInputChannel channel) throws RpcErrorException, IOException { - prog.onMessageDownloadStart(ids.size()); - boolean has_seen_flood_wait_message = false; + private void downloadMessages(List ids, TLInputChannel channel, String source_string) throws RpcErrorException, IOException { + prog.onMessageDownloadStart(ids.size(), source_string); logger.debug("Entering download loop"); while (ids.size()>0) { @@ -299,7 +302,7 @@ public class DownloadManager { if (ids.size()>0) { System.out.println("You have " + ids.size() + " messages in your db that need an update. Doing that now."); logger.debug("Found {} messages", ids.size()); - downloadMessages(ids, null); + downloadMessages(ids, null, null); } LinkedList messages = this.db.getMessagesWithMedia(); diff --git a/src/main/java/de/fabianonline/telegram_backup/DownloadProgressInterface.java b/src/main/java/de/fabianonline/telegram_backup/DownloadProgressInterface.java index 6924e59..e760205 100644 --- a/src/main/java/de/fabianonline/telegram_backup/DownloadProgressInterface.java +++ b/src/main/java/de/fabianonline/telegram_backup/DownloadProgressInterface.java @@ -1,16 +1,16 @@ /* 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 . */ @@ -19,10 +19,10 @@ package de.fabianonline.telegram_backup; import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager; public interface DownloadProgressInterface { - public void onMessageDownloadStart(int count); + public void onMessageDownloadStart(int count, String source); public void onMessageDownloaded(int number); public void onMessageDownloadFinished(); - + public void onMediaDownloadStart(int count); public void onMediaDownloaded(AbstractMediaFileManager a); public void onMediaDownloadedEmpty(); From 99574c6e6e5268fa240acc2c3a8f0d11e78f5b9f Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Fri, 1 Dec 2017 21:17:13 +0100 Subject: [PATCH 3/4] Split --with-channels-and-supergroups into seperate switches --with-channels and --with-supergroups. --- .../CommandLineController.java | 4 ++-- .../telegram_backup/CommandLineOptions.java | 10 +++++++--- .../telegram_backup/DownloadManager.java | 20 +++++++++++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java b/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java index 5a2291b..6e92ac1 100644 --- a/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java +++ b/src/main/java/de/fabianonline/telegram_backup/CommandLineController.java @@ -298,8 +298,8 @@ public class CommandLineController { System.out.println(" -d, --daemon Keep running and automatically save new messages."); System.out.println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support."); System.out.println(" --stats Print some usage statistics."); - System.out.println(" --with-channels-and-supergroups"); - System.out.println(" Backup channels and supergroups as well."); + System.out.println(" --with-channels Backup channels as well."); + System.out.println(" --with-supergroups Backup supergroups as well."); } private void list_accounts() { diff --git a/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java b/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java index d206343..f355a7d 100644 --- a/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java +++ b/src/main/java/de/fabianonline/telegram_backup/CommandLineOptions.java @@ -30,7 +30,8 @@ class CommandLineOptions { public static boolean cmd_no_media = false; public static boolean cmd_anonymize = false; public static boolean cmd_stats = false; - public static boolean cmd_channels_and_supergroups = false; + public static boolean cmd_channels = false; + public static boolean cmd_supergroups = false; public static String val_account = null; public static Integer val_limit_messages = null; @@ -118,8 +119,11 @@ class CommandLineOptions { case "--stats": cmd_stats = true; break; - case "--with-channels-and-supergroups": - cmd_channels_and_supergroups = true; break; + case "--with-channels": + cmd_channels = true; break; + + case "--with-supergroups": + cmd_supergroups = true; break; default: throw new RuntimeException("Unknown command " + arg); diff --git a/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java b/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java index c433418..eb1f624 100644 --- a/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java +++ b/src/main/java/de/fabianonline/telegram_backup/DownloadManager.java @@ -169,11 +169,14 @@ public class DownloadManager { } */ - if (CommandLineOptions.cmd_channels_and_supergroups) { - System.out.println("Processing channels and supergroups..."); + if (CommandLineOptions.cmd_channels || CommandLineOptions.cmd_supergroups) { + System.out.println("Processing channels and/or supergroups..."); + System.out.println("Please note that only channels/supergroups in the last 100 active chats are processed."); HashMap channel_access_hashes = new HashMap(); HashMap channel_names = new HashMap(); + LinkedList channels = new LinkedList(); + LinkedList supergroups = new LinkedList(); // TODO Add chat title (and other stuff?) to the database for (TLAbsChat c : dialogs.getChats()) { @@ -181,6 +184,11 @@ public class DownloadManager { TLChannel ch = (TLChannel)c; channel_access_hashes.put(c.getId(), ch.getAccessHash()); channel_names.put(c.getId(), ch.getTitle()); + if (ch.getMegagroup()) { + supergroups.add(c.getId()); + } else { + channels.add(c.getId()); + } // Channel: TLChannel // Supergroup: getMegagroup()==true } @@ -191,6 +199,14 @@ public class DownloadManager { for (TLDialog d : dialogs.getDialogs()) { if (d.getPeer() instanceof TLPeerChannel) { int channel_id = ((TLPeerChannel)d.getPeer()).getChannelId(); + + // If this is a channel and we don't want to download channels OR + // it is a supergroups and we don't want to download supergroups, then + if ((channels.contains(channel_id) && !CommandLineOptions.cmd_channels) || + (supergroups.contains(channel_id) && !CommandLineOptions.cmd_supergroups)) { + // Skip this chat. + continue; + } int max_known_id = db.getTopMessageIDForChannel(channel_id); if (d.getTopMessage() > max_known_id) { List ids = makeIdList(max_known_id+1, d.getTopMessage()); From fff3483e63f1f04320b40d925c5e4af3da399462 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sat, 2 Dec 2017 15:05:04 +0100 Subject: [PATCH 4/4] Fixed stats in the HTML export for channels. --- .../de/fabianonline/telegram_backup/Database.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/fabianonline/telegram_backup/Database.java b/src/main/java/de/fabianonline/telegram_backup/Database.java index c3ef3df..3562984 100644 --- a/src/main/java/de/fabianonline/telegram_backup/Database.java +++ b/src/main/java/de/fabianonline/telegram_backup/Database.java @@ -497,11 +497,21 @@ public class Database { HashMap map = new HashMap(); HashMap user_map = new HashMap(); int count_others = 0; + // Set a default value for 'me' to fix the charts for channels - cause I + // possibly didn't send any messages there. + map.put("authors.count.me", 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"); + "FROM messages " + + "LEFT JOIN users ON users.id=messages.sender_id " + + "WHERE " + 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)); + User u; + if (rs.getString(2)!=null || rs.getString(3)!=null || rs.getString(4)!=null) { + u = new User(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4)); + } else { + u = new User(rs.getInt(1), "Unknown", "", ""); + } if (u.isMe) { map.put("authors.count.me", rs.getInt(5)); } else {