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