mirror of
https://github.com/fabianonline/telegram_backup.git
synced 2024-11-23 01:06:17 +00:00
Complete conversion.
This commit is contained in:
parent
166df82ac1
commit
3309ae1338
@ -1,140 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramApiStorage;
|
|
||||||
import com.github.badoualy.telegram.mtproto.model.DataCenter;
|
|
||||||
import com.github.badoualy.telegram.mtproto.auth.AuthKey;
|
|
||||||
import com.github.badoualy.telegram.mtproto.model.MTSession;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
class ApiStorage implements TelegramApiStorage {
|
|
||||||
private String prefix = null;
|
|
||||||
private boolean do_save = false;
|
|
||||||
private AuthKey auth_key = null;
|
|
||||||
private DataCenter dc = null;
|
|
||||||
private File file_auth_key = null;
|
|
||||||
private File file_dc = null;
|
|
||||||
|
|
||||||
public ApiStorage(String prefix) {
|
|
||||||
this.setPrefix(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrefix(String prefix) {
|
|
||||||
this.prefix = prefix;
|
|
||||||
this.do_save = (this.prefix!=null);
|
|
||||||
if (this.do_save) {
|
|
||||||
String base = Config.FILE_BASE +
|
|
||||||
File.separatorChar +
|
|
||||||
this.prefix +
|
|
||||||
File.separatorChar;
|
|
||||||
this.file_auth_key = new File(base + Config.FILE_NAME_AUTH_KEY);
|
|
||||||
this.file_dc = new File(base + Config.FILE_NAME_DC);
|
|
||||||
this._saveAuthKey();
|
|
||||||
this._saveDc();
|
|
||||||
} else {
|
|
||||||
this.file_auth_key = null;
|
|
||||||
this.file_dc = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveAuthKey(AuthKey authKey) {
|
|
||||||
this.auth_key = authKey;
|
|
||||||
this._saveAuthKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _saveAuthKey() {
|
|
||||||
if (this.do_save && this.auth_key!=null) {
|
|
||||||
try {
|
|
||||||
FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key.getKey());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthKey loadAuthKey() {
|
|
||||||
if (this.auth_key != null) return this.auth_key;
|
|
||||||
if (this.file_auth_key != null) {
|
|
||||||
try {
|
|
||||||
return new AuthKey(FileUtils.readFileToByteArray(this.file_auth_key));
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (!(e instanceof FileNotFoundException)) e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveDc(DataCenter dc) {
|
|
||||||
this.dc = dc;
|
|
||||||
this._saveDc();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _saveDc() {
|
|
||||||
if (this.do_save && this.dc != null) {
|
|
||||||
try {
|
|
||||||
FileUtils.write(this.file_dc, this.dc.toString());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataCenter loadDc() {
|
|
||||||
if (this.dc != null) return this.dc;
|
|
||||||
if (this.file_dc != null) {
|
|
||||||
try {
|
|
||||||
String[] infos = FileUtils.readFileToString(this.file_dc).split(":");
|
|
||||||
return new DataCenter(infos[0], Integer.parseInt(infos[1]));
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (!(e instanceof FileNotFoundException)) e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteAuthKey() {
|
|
||||||
if (this.do_save) {
|
|
||||||
try {
|
|
||||||
FileUtils.forceDelete(this.file_auth_key);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteDc() {
|
|
||||||
if (this.do_save) {
|
|
||||||
try {
|
|
||||||
FileUtils.forceDelete(this.file_dc);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveSession(MTSession session) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public MTSession loadSession() { return null; }
|
|
||||||
}
|
|
@ -1,957 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.TelegramUpdateHandler;
|
|
||||||
import de.fabianonline.telegram_backup.exporter.HTMLExporter;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.Kotlogram;
|
|
||||||
import com.github.badoualy.telegram.api.TelegramApp;
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
this.show_help();
|
|
||||||
System.exit(0);
|
|
||||||
} else if (CommandLineOptions.cmd_license) {
|
|
||||||
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");
|
|
||||||
storage = new ApiStorage(account);
|
|
||||||
logger.info("Initializing TelegramUpdateHandler");
|
|
||||||
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);
|
|
||||||
|
|
||||||
UserManager user = UserManager.getInstance();
|
|
||||||
|
|
||||||
if (!CommandLineOptions.cmd_login && !user.isLoggedIn()) {
|
|
||||||
System.out.println("Your authorization data is invalid or missing. You will have to login with Telegram again.");
|
|
||||||
CommandLineOptions.cmd_login = true;
|
|
||||||
}
|
|
||||||
if (account!=null && user.isLoggedIn()) {
|
|
||||||
if (!account.equals("+" + user.getUser().getPhone())) {
|
|
||||||
logger.error("Account: {}, user.getUser().getPhone(): +{}", Utils.anonymize(account), Utils.anonymize(user.getUser().getPhone()));
|
|
||||||
throw new RuntimeException("Account / User mismatch");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login);
|
|
||||||
if (CommandLineOptions.cmd_login) {
|
|
||||||
cmd_login(account);
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we reach this point, we can assume that there is an account and a database can be loaded / created.
|
|
||||||
|
|
||||||
Database.init(client);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (CommandLineOptions.cmd_stats) {
|
|
||||||
cmd_stats();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CommandLineOptions.val_test != null) {
|
|
||||||
if (CommandLineOptions.val_test == 1) {
|
|
||||||
TestFeatures.test1();
|
|
||||||
} else if (CommandLineOptions.val_test == 2) {
|
|
||||||
TestFeatures.test2(user, client);
|
|
||||||
} else {
|
|
||||||
System.out.println("Unknown test " + CommandLineOptions.val_test);
|
|
||||||
}
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export);
|
|
||||||
if (CommandLineOptions.val_export != null) {
|
|
||||||
if (CommandLineOptions.val_export.toLowerCase().equals("html")) {
|
|
||||||
(new HTMLExporter()).export();
|
|
||||||
System.exit(0);
|
|
||||||
} else {
|
|
||||||
show_error("Unknown export format.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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");
|
|
||||||
d.downloadMedia();
|
|
||||||
} else {
|
|
||||||
System.out.println("Skipping media download because --no-media is set.");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("Exception caught!", e);
|
|
||||||
} finally {
|
|
||||||
if (CommandLineOptions.cmd_daemon) {
|
|
||||||
handler.activate();
|
|
||||||
System.out.println("DAEMON mode requested - keeping running.");
|
|
||||||
} else {
|
|
||||||
client.close();
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("----- EXIT -----");
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printHeader() {
|
|
||||||
System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz");
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Telegram_Backup comes with ABSOLUTELY NO WARRANTY. This is free software, and you are");
|
|
||||||
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<String> accounts = Utils.getAccounts();
|
|
||||||
if (CommandLineOptions.cmd_login) {
|
|
||||||
logger.debug("Login requested, doing nothing.");
|
|
||||||
// do nothing
|
|
||||||
} else if (CommandLineOptions.val_account!=null) {
|
|
||||||
logger.debug("Account requested: {}", Utils.anonymize(CommandLineOptions.val_account));
|
|
||||||
logger.trace("Checking accounts for match.");
|
|
||||||
boolean found = false;
|
|
||||||
for (String acc : accounts) {
|
|
||||||
logger.trace("Checking {}", Utils.anonymize(acc));
|
|
||||||
if (acc.equals(CommandLineOptions.val_account)) {
|
|
||||||
found=true;
|
|
||||||
logger.trace("Matches.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
show_error("Couldn't find account '" + Utils.anonymize(CommandLineOptions.val_account) + "'. Maybe you want to use '--login' first?");
|
|
||||||
}
|
|
||||||
account = CommandLineOptions.val_account;
|
|
||||||
} else if (accounts.size()==0) {
|
|
||||||
System.out.println("No accounts found. Starting login process...");
|
|
||||||
CommandLineOptions.cmd_login = true;
|
|
||||||
} else if (accounts.size()==1) {
|
|
||||||
account = accounts.firstElement();
|
|
||||||
System.out.println("Using only available account: " + Utils.anonymize(account));
|
|
||||||
} else {
|
|
||||||
show_error("You didn't specify which account to use.\n" +
|
|
||||||
"Use '--account <x>' to use account <x>.\n" +
|
|
||||||
"Use '--list-accounts' to see all available accounts.");
|
|
||||||
}
|
|
||||||
logger.debug("accounts.size(): {}", accounts.size());
|
|
||||||
logger.debug("account: {}", Utils.anonymize(account));
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cmd_stats() {
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Stats:");
|
|
||||||
String format = "%40s: %d%n";
|
|
||||||
System.out.format(format, "Number of accounts", Utils.getAccounts().size());
|
|
||||||
System.out.format(format, "Number of messages", Database.getInstance().getMessageCount());
|
|
||||||
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<String, Integer> 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<String, Integer> 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) {
|
|
||||||
System.out.println("Please enter your phone number in international format.");
|
|
||||||
System.out.println("Example: +4917077651234");
|
|
||||||
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("> ");
|
|
||||||
} else {
|
|
||||||
System.out.print("> ");
|
|
||||||
return new Scanner(System.in).nextLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPassword() {
|
|
||||||
if (System.console()!=null) {
|
|
||||||
return String.valueOf(System.console().readPassword("> "));
|
|
||||||
} else {
|
|
||||||
return getLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void show_help() {
|
|
||||||
System.out.println("Valid options are:");
|
|
||||||
System.out.println(" -h, --help Shows this help.");
|
|
||||||
System.out.println(" -a, --account <x> Use account <x>.");
|
|
||||||
System.out.println(" -l, --login Login to an existing telegram account.");
|
|
||||||
System.out.println(" --debug Shows some debug information.");
|
|
||||||
System.out.println(" --trace Shows lots of debug information. Overrides --debug.");
|
|
||||||
System.out.println(" --trace-telegram Shows lots of debug messages from the library used to access Telegram.");
|
|
||||||
System.out.println(" -A, --list-accounts List all existing accounts ");
|
|
||||||
System.out.println(" --limit-messages <x> Downloads at most the most recent <x> messages.");
|
|
||||||
System.out.println(" --no-media Do not download media files.");
|
|
||||||
System.out.println(" -t, --target <x> Target directory for the files.");
|
|
||||||
System.out.println(" -e, --export <format> Export the database. Valid formats are:");
|
|
||||||
System.out.println(" html - Creates HTML files.");
|
|
||||||
System.out.println(" --license Displays the license of this program.");
|
|
||||||
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 Backup channels as well.");
|
|
||||||
System.out.println(" --with-supergroups Backup supergroups as well.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void list_accounts() {
|
|
||||||
System.out.println("List of available accounts:");
|
|
||||||
List<String> accounts = Utils.getAccounts();
|
|
||||||
if (accounts.size()>0) {
|
|
||||||
for (String str : accounts) {
|
|
||||||
System.out.println(" " + Utils.anonymize(str));
|
|
||||||
}
|
|
||||||
System.out.println("Use '--account <x>' to use one of those accounts.");
|
|
||||||
} else {
|
|
||||||
System.out.println("NO ACCOUNTS FOUND");
|
|
||||||
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" +
|
|
||||||
" Version 3, 29 June 2007\n" +
|
|
||||||
"\n" +
|
|
||||||
"Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n" +
|
|
||||||
"Everyone is permitted to copy and distribute verbatim copies\n" +
|
|
||||||
"of this license document, but changing it is not allowed.\n" +
|
|
||||||
"\n" +
|
|
||||||
" Preamble\n" +
|
|
||||||
"\n" +
|
|
||||||
"The GNU General Public License is a free, copyleft license for\n" +
|
|
||||||
"software and other kinds of works.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The licenses for most software and other practical works are designed\n" +
|
|
||||||
"to take away your freedom to share and change the works. By contrast,\n" +
|
|
||||||
"the GNU General Public License is intended to guarantee your freedom to\n" +
|
|
||||||
"share and change all versions of a program--to make sure it remains free\n" +
|
|
||||||
"software for all its users. We, the Free Software Foundation, use the\n" +
|
|
||||||
"GNU General Public License for most of our software; it applies also to\n" +
|
|
||||||
"any other work released this way by its authors. You can apply it to\n" +
|
|
||||||
"your programs, too.\n" +
|
|
||||||
"\n" +
|
|
||||||
"When we speak of free software, we are referring to freedom, not\n" +
|
|
||||||
"price. Our General Public Licenses are designed to make sure that you\n" +
|
|
||||||
"have the freedom to distribute copies of free software (and charge for\n" +
|
|
||||||
"them if you wish), that you receive source code or can get it if you\n" +
|
|
||||||
"want it, that you can change the software or use pieces of it in new\n" +
|
|
||||||
"free programs, and that you know you can do these things.\n" +
|
|
||||||
"\n" +
|
|
||||||
"To protect your rights, we need to prevent others from denying you\n" +
|
|
||||||
"these rights or asking you to surrender the rights. Therefore, you have\n" +
|
|
||||||
"certain responsibilities if you distribute copies of the software, or if\n" +
|
|
||||||
"you modify it: responsibilities to respect the freedom of others.\n" +
|
|
||||||
"\n" +
|
|
||||||
"For example, if you distribute copies of such a program, whether\n" +
|
|
||||||
"gratis or for a fee, you must pass on to the recipients the same\n" +
|
|
||||||
"freedoms that you received. You must make sure that they, too, receive\n" +
|
|
||||||
"or can get the source code. And you must show them these terms so they\n" +
|
|
||||||
"know their rights.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Developers that use the GNU GPL protect your rights with two steps:\n" +
|
|
||||||
"(1) assert copyright on the software, and (2) offer you this License\n" +
|
|
||||||
"giving you legal permission to copy, distribute and/or modify it.\n" +
|
|
||||||
"\n" +
|
|
||||||
"For the developers' and authors' protection, the GPL clearly explains\n" +
|
|
||||||
"that there is no warranty for this free software. For both users' and\n" +
|
|
||||||
"authors' sake, the GPL requires that modified versions be marked as\n" +
|
|
||||||
"changed, so that their problems will not be attributed erroneously to\n" +
|
|
||||||
"authors of previous versions.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Some devices are designed to deny users access to install or run\n" +
|
|
||||||
"modified versions of the software inside them, although the manufacturer\n" +
|
|
||||||
"can do so. This is fundamentally incompatible with the aim of\n" +
|
|
||||||
"protecting users' freedom to change the software. The systematic\n" +
|
|
||||||
"pattern of such abuse occurs in the area of products for individuals to\n" +
|
|
||||||
"use, which is precisely where it is most unacceptable. Therefore, we\n" +
|
|
||||||
"have designed this version of the GPL to prohibit the practice for those\n" +
|
|
||||||
"products. If such problems arise substantially in other domains, we\n" +
|
|
||||||
"stand ready to extend this provision to those domains in future versions\n" +
|
|
||||||
"of the GPL, as needed to protect the freedom of users.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Finally, every program is threatened constantly by software patents.\n" +
|
|
||||||
"States should not allow patents to restrict development and use of\n" +
|
|
||||||
"software on general-purpose computers, but in those that do, we wish to\n" +
|
|
||||||
"avoid the special danger that patents applied to a free program could\n" +
|
|
||||||
"make it effectively proprietary. To prevent this, the GPL assures that\n" +
|
|
||||||
"patents cannot be used to render the program non-free.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The precise terms and conditions for copying, distribution and\n" +
|
|
||||||
"modification follow.\n" +
|
|
||||||
"\n" +
|
|
||||||
" TERMS AND CONDITIONS\n" +
|
|
||||||
"\n" +
|
|
||||||
"0. Definitions.\n" +
|
|
||||||
"\n" +
|
|
||||||
"\"This License\" refers to version 3 of the GNU General Public License.\n" +
|
|
||||||
"\n" +
|
|
||||||
"\"Copyright\" also means copyright-like laws that apply to other kinds of\n" +
|
|
||||||
"works, such as semiconductor masks.\n" +
|
|
||||||
"\n" +
|
|
||||||
"\"The Program\" refers to any copyrightable work licensed under this\n" +
|
|
||||||
"License. Each licensee is addressed as \"you\". \"Licensees\" and\n" +
|
|
||||||
"\"recipients\" may be individuals or organizations.\n" +
|
|
||||||
"\n" +
|
|
||||||
"To \"modify\" a work means to copy from or adapt all or part of the work\n" +
|
|
||||||
"in a fashion requiring copyright permission, other than the making of an\n" +
|
|
||||||
"exact copy. The resulting work is called a \"modified version\" of the\n" +
|
|
||||||
"earlier work or a work \"based on\" the earlier work.\n" +
|
|
||||||
"\n" +
|
|
||||||
"A \"covered work\" means either the unmodified Program or a work based\n" +
|
|
||||||
"on the Program.\n" +
|
|
||||||
"\n" +
|
|
||||||
"To \"propagate\" a work means to do anything with it that, without\n" +
|
|
||||||
"permission, would make you directly or secondarily liable for\n" +
|
|
||||||
"infringement under applicable copyright law, except executing it on a\n" +
|
|
||||||
"computer or modifying a private copy. Propagation includes copying,\n" +
|
|
||||||
"distribution (with or without modification), making available to the\n" +
|
|
||||||
"public, and in some countries other activities as well.\n" +
|
|
||||||
"\n" +
|
|
||||||
"To \"convey\" a work means any kind of propagation that enables other\n" +
|
|
||||||
"parties to make or receive copies. Mere interaction with a user through\n" +
|
|
||||||
"a computer network, with no transfer of a copy, is not conveying.\n" +
|
|
||||||
"\n" +
|
|
||||||
"An interactive user interface displays \"Appropriate Legal Notices\"\n" +
|
|
||||||
"to the extent that it includes a convenient and prominently visible\n" +
|
|
||||||
"feature that (1) displays an appropriate copyright notice, and (2)\n" +
|
|
||||||
"tells the user that there is no warranty for the work (except to the\n" +
|
|
||||||
"extent that warranties are provided), that licensees may convey the\n" +
|
|
||||||
"work under this License, and how to view a copy of this License. If\n" +
|
|
||||||
"the interface presents a list of user commands or options, such as a\n" +
|
|
||||||
"menu, a prominent item in the list meets this criterion.\n" +
|
|
||||||
"\n" +
|
|
||||||
"1. Source Code.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The \"source code\" for a work means the preferred form of the work\n" +
|
|
||||||
"for making modifications to it. \"Object code\" means any non-source\n" +
|
|
||||||
"form of a work.\n" +
|
|
||||||
"\n" +
|
|
||||||
"A \"Standard Interface\" means an interface that either is an official\n" +
|
|
||||||
"standard defined by a recognized standards body, or, in the case of\n" +
|
|
||||||
"interfaces specified for a particular programming language, one that\n" +
|
|
||||||
"is widely used among developers working in that language.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The \"System Libraries\" of an executable work include anything, other\n" +
|
|
||||||
"than the work as a whole, that (a) is included in the normal form of\n" +
|
|
||||||
"packaging a Major Component, but which is not part of that Major\n" +
|
|
||||||
"Component, and (b) serves only to enable use of the work with that\n" +
|
|
||||||
"Major Component, or to implement a Standard Interface for which an\n" +
|
|
||||||
"implementation is available to the public in source code form. A\n" +
|
|
||||||
"\"Major Component\", in this context, means a major essential component\n" +
|
|
||||||
"(kernel, window system, and so on) of the specific operating system\n" +
|
|
||||||
"(if any) on which the executable work runs, or a compiler used to\n" +
|
|
||||||
"produce the work, or an object code interpreter used to run it.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The \"Corresponding Source\" for a work in object code form means all\n" +
|
|
||||||
"the source code needed to generate, install, and (for an executable\n" +
|
|
||||||
"work) run the object code and to modify the work, including scripts to\n" +
|
|
||||||
"control those activities. However, it does not include the work's\n" +
|
|
||||||
"System Libraries, or general-purpose tools or generally available free\n" +
|
|
||||||
"programs which are used unmodified in performing those activities but\n" +
|
|
||||||
"which are not part of the work. For example, Corresponding Source\n" +
|
|
||||||
"includes interface definition files associated with source files for\n" +
|
|
||||||
"the work, and the source code for shared libraries and dynamically\n" +
|
|
||||||
"linked subprograms that the work is specifically designed to require,\n" +
|
|
||||||
"such as by intimate data communication or control flow between those\n" +
|
|
||||||
"subprograms and other parts of the work.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The Corresponding Source need not include anything that users\n" +
|
|
||||||
"can regenerate automatically from other parts of the Corresponding\n" +
|
|
||||||
"Source.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The Corresponding Source for a work in source code form is that\n" +
|
|
||||||
"same work.\n" +
|
|
||||||
"\n" +
|
|
||||||
"2. Basic Permissions.\n" +
|
|
||||||
"\n" +
|
|
||||||
"All rights granted under this License are granted for the term of\n" +
|
|
||||||
"copyright on the Program, and are irrevocable provided the stated\n" +
|
|
||||||
"conditions are met. This License explicitly affirms your unlimited\n" +
|
|
||||||
"permission to run the unmodified Program. The output from running a\n" +
|
|
||||||
"covered work is covered by this License only if the output, given its\n" +
|
|
||||||
"content, constitutes a covered work. This License acknowledges your\n" +
|
|
||||||
"rights of fair use or other equivalent, as provided by copyright law.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You may make, run and propagate covered works that you do not\n" +
|
|
||||||
"convey, without conditions so long as your license otherwise remains\n" +
|
|
||||||
"in force. You may convey covered works to others for the sole purpose\n" +
|
|
||||||
"of having them make modifications exclusively for you, or provide you\n" +
|
|
||||||
"with facilities for running those works, provided that you comply with\n" +
|
|
||||||
"the terms of this License in conveying all material for which you do\n" +
|
|
||||||
"not control copyright. Those thus making or running the covered works\n" +
|
|
||||||
"for you must do so exclusively on your behalf, under your direction\n" +
|
|
||||||
"and control, on terms that prohibit them from making any copies of\n" +
|
|
||||||
"your copyrighted material outside their relationship with you.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Conveying under any other circumstances is permitted solely under\n" +
|
|
||||||
"the conditions stated below. Sublicensing is not allowed; section 10\n" +
|
|
||||||
"makes it unnecessary.\n" +
|
|
||||||
"\n" +
|
|
||||||
"3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n" +
|
|
||||||
"\n" +
|
|
||||||
"No covered work shall be deemed part of an effective technological\n" +
|
|
||||||
"measure under any applicable law fulfilling obligations under article\n" +
|
|
||||||
"11 of the WIPO copyright treaty adopted on 20 December 1996, or\n" +
|
|
||||||
"similar laws prohibiting or restricting circumvention of such\n" +
|
|
||||||
"measures.\n" +
|
|
||||||
"\n" +
|
|
||||||
"When you convey a covered work, you waive any legal power to forbid\n" +
|
|
||||||
"circumvention of technological measures to the extent such circumvention\n" +
|
|
||||||
"is effected by exercising rights under this License with respect to\n" +
|
|
||||||
"the covered work, and you disclaim any intention to limit operation or\n" +
|
|
||||||
"modification of the work as a means of enforcing, against the work's\n" +
|
|
||||||
"users, your or third parties' legal rights to forbid circumvention of\n" +
|
|
||||||
"technological measures.\n" +
|
|
||||||
"\n" +
|
|
||||||
"4. Conveying Verbatim Copies.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You may convey verbatim copies of the Program's source code as you\n" +
|
|
||||||
"receive it, in any medium, provided that you conspicuously and\n" +
|
|
||||||
"appropriately publish on each copy an appropriate copyright notice;\n" +
|
|
||||||
"keep intact all notices stating that this License and any\n" +
|
|
||||||
"non-permissive terms added in accord with section 7 apply to the code;\n" +
|
|
||||||
"keep intact all notices of the absence of any warranty; and give all\n" +
|
|
||||||
"recipients a copy of this License along with the Program.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You may charge any price or no price for each copy that you convey,\n" +
|
|
||||||
"and you may offer support or warranty protection for a fee.\n" +
|
|
||||||
"\n" +
|
|
||||||
"5. Conveying Modified Source Versions.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You may convey a work based on the Program, or the modifications to\n" +
|
|
||||||
"produce it from the Program, in the form of source code under the\n" +
|
|
||||||
"terms of section 4, provided that you also meet all of these conditions:\n" +
|
|
||||||
"\n" +
|
|
||||||
"a) The work must carry prominent notices stating that you modified\n" +
|
|
||||||
"it, and giving a relevant date.\n" +
|
|
||||||
"\n" +
|
|
||||||
"b) The work must carry prominent notices stating that it is\n" +
|
|
||||||
"released under this License and any conditions added under section\n" +
|
|
||||||
"7. This requirement modifies the requirement in section 4 to\n" +
|
|
||||||
"\"keep intact all notices\".\n" +
|
|
||||||
"\n" +
|
|
||||||
"c) You must license the entire work, as a whole, under this\n" +
|
|
||||||
"License to anyone who comes into possession of a copy. This\n" +
|
|
||||||
"License will therefore apply, along with any applicable section 7\n" +
|
|
||||||
"additional terms, to the whole of the work, and all its parts,\n" +
|
|
||||||
"regardless of how they are packaged. This License gives no\n" +
|
|
||||||
"permission to license the work in any other way, but it does not\n" +
|
|
||||||
"invalidate such permission if you have separately received it.\n" +
|
|
||||||
"\n" +
|
|
||||||
"d) If the work has interactive user interfaces, each must display\n" +
|
|
||||||
"Appropriate Legal Notices; however, if the Program has interactive\n" +
|
|
||||||
"interfaces that do not display Appropriate Legal Notices, your\n" +
|
|
||||||
"work need not make them do so.\n" +
|
|
||||||
"\n" +
|
|
||||||
"A compilation of a covered work with other separate and independent\n" +
|
|
||||||
"works, which are not by their nature extensions of the covered work,\n" +
|
|
||||||
"and which are not combined with it such as to form a larger program,\n" +
|
|
||||||
"in or on a volume of a storage or distribution medium, is called an\n" +
|
|
||||||
"\"aggregate\" if the compilation and its resulting copyright are not\n" +
|
|
||||||
"used to limit the access or legal rights of the compilation's users\n" +
|
|
||||||
"beyond what the individual works permit. Inclusion of a covered work\n" +
|
|
||||||
"in an aggregate does not cause this License to apply to the other\n" +
|
|
||||||
"parts of the aggregate.\n" +
|
|
||||||
"\n" +
|
|
||||||
"6. Conveying Non-Source Forms.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You may convey a covered work in object code form under the terms\n" +
|
|
||||||
"of sections 4 and 5, provided that you also convey the\n" +
|
|
||||||
"machine-readable Corresponding Source under the terms of this License,\n" +
|
|
||||||
"in one of these ways:\n" +
|
|
||||||
"\n" +
|
|
||||||
"a) Convey the object code in, or embodied in, a physical product\n" +
|
|
||||||
"(including a physical distribution medium), accompanied by the\n" +
|
|
||||||
"Corresponding Source fixed on a durable physical medium\n" +
|
|
||||||
"customarily used for software interchange.\n" +
|
|
||||||
"\n" +
|
|
||||||
"b) Convey the object code in, or embodied in, a physical product\n" +
|
|
||||||
"(including a physical distribution medium), accompanied by a\n" +
|
|
||||||
"written offer, valid for at least three years and valid for as\n" +
|
|
||||||
"long as you offer spare parts or customer support for that product\n" +
|
|
||||||
"model, to give anyone who possesses the object code either (1) a\n" +
|
|
||||||
"copy of the Corresponding Source for all the software in the\n" +
|
|
||||||
"product that is covered by this License, on a durable physical\n" +
|
|
||||||
"medium customarily used for software interchange, for a price no\n" +
|
|
||||||
"more than your reasonable cost of physically performing this\n" +
|
|
||||||
"conveying of source, or (2) access to copy the\n" +
|
|
||||||
"Corresponding Source from a network server at no charge.\n" +
|
|
||||||
"\n" +
|
|
||||||
"c) Convey individual copies of the object code with a copy of the\n" +
|
|
||||||
"written offer to provide the Corresponding Source. This\n" +
|
|
||||||
"alternative is allowed only occasionally and noncommercially, and\n" +
|
|
||||||
"only if you received the object code with such an offer, in accord\n" +
|
|
||||||
"with subsection 6b.\n" +
|
|
||||||
"\n" +
|
|
||||||
"d) Convey the object code by offering access from a designated\n" +
|
|
||||||
"place (gratis or for a charge), and offer equivalent access to the\n" +
|
|
||||||
"Corresponding Source in the same way through the same place at no\n" +
|
|
||||||
"further charge. You need not require recipients to copy the\n" +
|
|
||||||
"Corresponding Source along with the object code. If the place to\n" +
|
|
||||||
"copy the object code is a network server, the Corresponding Source\n" +
|
|
||||||
"may be on a different server (operated by you or a third party)\n" +
|
|
||||||
"that supports equivalent copying facilities, provided you maintain\n" +
|
|
||||||
"clear directions next to the object code saying where to find the\n" +
|
|
||||||
"Corresponding Source. Regardless of what server hosts the\n" +
|
|
||||||
"Corresponding Source, you remain obligated to ensure that it is\n" +
|
|
||||||
"available for as long as needed to satisfy these requirements.\n" +
|
|
||||||
"\n" +
|
|
||||||
"e) Convey the object code using peer-to-peer transmission, provided\n" +
|
|
||||||
"you inform other peers where the object code and Corresponding\n" +
|
|
||||||
"Source of the work are being offered to the general public at no\n" +
|
|
||||||
"charge under subsection 6d.\n" +
|
|
||||||
"\n" +
|
|
||||||
"A separable portion of the object code, whose source code is excluded\n" +
|
|
||||||
"from the Corresponding Source as a System Library, need not be\n" +
|
|
||||||
"included in conveying the object code work.\n" +
|
|
||||||
"\n" +
|
|
||||||
"A \"User Product\" is either (1) a \"consumer product\", which means any\n" +
|
|
||||||
"tangible personal property which is normally used for personal, family,\n" +
|
|
||||||
"or household purposes, or (2) anything designed or sold for incorporation\n" +
|
|
||||||
"into a dwelling. In determining whether a product is a consumer product,\n" +
|
|
||||||
"doubtful cases shall be resolved in favor of coverage. For a particular\n" +
|
|
||||||
"product received by a particular user, \"normally used\" refers to a\n" +
|
|
||||||
"typical or common use of that class of product, regardless of the status\n" +
|
|
||||||
"of the particular user or of the way in which the particular user\n" +
|
|
||||||
"actually uses, or expects or is expected to use, the product. A product\n" +
|
|
||||||
"is a consumer product regardless of whether the product has substantial\n" +
|
|
||||||
"commercial, industrial or non-consumer uses, unless such uses represent\n" +
|
|
||||||
"the only significant mode of use of the product.\n" +
|
|
||||||
"\n" +
|
|
||||||
"\"Installation Information\" for a User Product means any methods,\n" +
|
|
||||||
"procedures, authorization keys, or other information required to install\n" +
|
|
||||||
"and execute modified versions of a covered work in that User Product from\n" +
|
|
||||||
"a modified version of its Corresponding Source. The information must\n" +
|
|
||||||
"suffice to ensure that the continued functioning of the modified object\n" +
|
|
||||||
"code is in no case prevented or interfered with solely because\n" +
|
|
||||||
"modification has been made.\n" +
|
|
||||||
"\n" +
|
|
||||||
"If you convey an object code work under this section in, or with, or\n" +
|
|
||||||
"specifically for use in, a User Product, and the conveying occurs as\n" +
|
|
||||||
"part of a transaction in which the right of possession and use of the\n" +
|
|
||||||
"User Product is transferred to the recipient in perpetuity or for a\n" +
|
|
||||||
"fixed term (regardless of how the transaction is characterized), the\n" +
|
|
||||||
"Corresponding Source conveyed under this section must be accompanied\n" +
|
|
||||||
"by the Installation Information. But this requirement does not apply\n" +
|
|
||||||
"if neither you nor any third party retains the ability to install\n" +
|
|
||||||
"modified object code on the User Product (for example, the work has\n" +
|
|
||||||
"been installed in ROM).\n" +
|
|
||||||
"\n" +
|
|
||||||
"The requirement to provide Installation Information does not include a\n" +
|
|
||||||
"requirement to continue to provide support service, warranty, or updates\n" +
|
|
||||||
"for a work that has been modified or installed by the recipient, or for\n" +
|
|
||||||
"the User Product in which it has been modified or installed. Access to a\n" +
|
|
||||||
"network may be denied when the modification itself materially and\n" +
|
|
||||||
"adversely affects the operation of the network or violates the rules and\n" +
|
|
||||||
"protocols for communication across the network.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Corresponding Source conveyed, and Installation Information provided,\n" +
|
|
||||||
"in accord with this section must be in a format that is publicly\n" +
|
|
||||||
"documented (and with an implementation available to the public in\n" +
|
|
||||||
"source code form), and must require no special password or key for\n" +
|
|
||||||
"unpacking, reading or copying.\n" +
|
|
||||||
"\n" +
|
|
||||||
"7. Additional Terms.\n" +
|
|
||||||
"\n" +
|
|
||||||
"\"Additional permissions\" are terms that supplement the terms of this\n" +
|
|
||||||
"License by making exceptions from one or more of its conditions.\n" +
|
|
||||||
"Additional permissions that are applicable to the entire Program shall\n" +
|
|
||||||
"be treated as though they were included in this License, to the extent\n" +
|
|
||||||
"that they are valid under applicable law. If additional permissions\n" +
|
|
||||||
"apply only to part of the Program, that part may be used separately\n" +
|
|
||||||
"under those permissions, but the entire Program remains governed by\n" +
|
|
||||||
"this License without regard to the additional permissions.\n" +
|
|
||||||
"\n" +
|
|
||||||
"When you convey a copy of a covered work, you may at your option\n" +
|
|
||||||
"remove any additional permissions from that copy, or from any part of\n" +
|
|
||||||
"it. (Additional permissions may be written to require their own\n" +
|
|
||||||
"removal in certain cases when you modify the work.) You may place\n" +
|
|
||||||
"additional permissions on material, added by you to a covered work,\n" +
|
|
||||||
"for which you have or can give appropriate copyright permission.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Notwithstanding any other provision of this License, for material you\n" +
|
|
||||||
"add to a covered work, you may (if authorized by the copyright holders of\n" +
|
|
||||||
"that material) supplement the terms of this License with terms:\n" +
|
|
||||||
"\n" +
|
|
||||||
"a) Disclaiming warranty or limiting liability differently from the\n" +
|
|
||||||
"terms of sections 15 and 16 of this License; or\n" +
|
|
||||||
"\n" +
|
|
||||||
"b) Requiring preservation of specified reasonable legal notices or\n" +
|
|
||||||
"author attributions in that material or in the Appropriate Legal\n" +
|
|
||||||
"Notices displayed by works containing it; or\n" +
|
|
||||||
"\n" +
|
|
||||||
"c) Prohibiting misrepresentation of the origin of that material, or\n" +
|
|
||||||
"requiring that modified versions of such material be marked in\n" +
|
|
||||||
"reasonable ways as different from the original version; or\n" +
|
|
||||||
"\n" +
|
|
||||||
"d) Limiting the use for publicity purposes of names of licensors or\n" +
|
|
||||||
"authors of the material; or\n" +
|
|
||||||
"\n" +
|
|
||||||
"e) Declining to grant rights under trademark law for use of some\n" +
|
|
||||||
"trade names, trademarks, or service marks; or\n" +
|
|
||||||
"\n" +
|
|
||||||
"f) Requiring indemnification of licensors and authors of that\n" +
|
|
||||||
"material by anyone who conveys the material (or modified versions of\n" +
|
|
||||||
"it) with contractual assumptions of liability to the recipient, for\n" +
|
|
||||||
"any liability that these contractual assumptions directly impose on\n" +
|
|
||||||
"those licensors and authors.\n" +
|
|
||||||
"\n" +
|
|
||||||
"All other non-permissive additional terms are considered \"further\n" +
|
|
||||||
"restrictions\" within the meaning of section 10. If the Program as you\n" +
|
|
||||||
"received it, or any part of it, contains a notice stating that it is\n" +
|
|
||||||
"governed by this License along with a term that is a further\n" +
|
|
||||||
"restriction, you may remove that term. If a license document contains\n" +
|
|
||||||
"a further restriction but permits relicensing or conveying under this\n" +
|
|
||||||
"License, you may add to a covered work material governed by the terms\n" +
|
|
||||||
"of that license document, provided that the further restriction does\n" +
|
|
||||||
"not survive such relicensing or conveying.\n" +
|
|
||||||
"\n" +
|
|
||||||
"If you add terms to a covered work in accord with this section, you\n" +
|
|
||||||
"must place, in the relevant source files, a statement of the\n" +
|
|
||||||
"additional terms that apply to those files, or a notice indicating\n" +
|
|
||||||
"where to find the applicable terms.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Additional terms, permissive or non-permissive, may be stated in the\n" +
|
|
||||||
"form of a separately written license, or stated as exceptions;\n" +
|
|
||||||
"the above requirements apply either way.\n" +
|
|
||||||
"\n" +
|
|
||||||
"8. Termination.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You may not propagate or modify a covered work except as expressly\n" +
|
|
||||||
"provided under this License. Any attempt otherwise to propagate or\n" +
|
|
||||||
"modify it is void, and will automatically terminate your rights under\n" +
|
|
||||||
"this License (including any patent licenses granted under the third\n" +
|
|
||||||
"paragraph of section 11).\n" +
|
|
||||||
"\n" +
|
|
||||||
"However, if you cease all violation of this License, then your\n" +
|
|
||||||
"license from a particular copyright holder is reinstated (a)\n" +
|
|
||||||
"provisionally, unless and until the copyright holder explicitly and\n" +
|
|
||||||
"finally terminates your license, and (b) permanently, if the copyright\n" +
|
|
||||||
"holder fails to notify you of the violation by some reasonable means\n" +
|
|
||||||
"prior to 60 days after the cessation.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Moreover, your license from a particular copyright holder is\n" +
|
|
||||||
"reinstated permanently if the copyright holder notifies you of the\n" +
|
|
||||||
"violation by some reasonable means, this is the first time you have\n" +
|
|
||||||
"received notice of violation of this License (for any work) from that\n" +
|
|
||||||
"copyright holder, and you cure the violation prior to 30 days after\n" +
|
|
||||||
"your receipt of the notice.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Termination of your rights under this section does not terminate the\n" +
|
|
||||||
"licenses of parties who have received copies or rights from you under\n" +
|
|
||||||
"this License. If your rights have been terminated and not permanently\n" +
|
|
||||||
"reinstated, you do not qualify to receive new licenses for the same\n" +
|
|
||||||
"material under section 10.\n" +
|
|
||||||
"\n" +
|
|
||||||
"9. Acceptance Not Required for Having Copies.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You are not required to accept this License in order to receive or\n" +
|
|
||||||
"run a copy of the Program. Ancillary propagation of a covered work\n" +
|
|
||||||
"occurring solely as a consequence of using peer-to-peer transmission\n" +
|
|
||||||
"to receive a copy likewise does not require acceptance. However,\n" +
|
|
||||||
"nothing other than this License grants you permission to propagate or\n" +
|
|
||||||
"modify any covered work. These actions infringe copyright if you do\n" +
|
|
||||||
"not accept this License. Therefore, by modifying or propagating a\n" +
|
|
||||||
"covered work, you indicate your acceptance of this License to do so.\n" +
|
|
||||||
"\n" +
|
|
||||||
"10. Automatic Licensing of Downstream Recipients.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Each time you convey a covered work, the recipient automatically\n" +
|
|
||||||
"receives a license from the original licensors, to run, modify and\n" +
|
|
||||||
"propagate that work, subject to this License. You are not responsible\n" +
|
|
||||||
"for enforcing compliance by third parties with this License.\n" +
|
|
||||||
"\n" +
|
|
||||||
"An \"entity transaction\" is a transaction transferring control of an\n" +
|
|
||||||
"organization, or substantially all assets of one, or subdividing an\n" +
|
|
||||||
"organization, or merging organizations. If propagation of a covered\n" +
|
|
||||||
"work results from an entity transaction, each party to that\n" +
|
|
||||||
"transaction who receives a copy of the work also receives whatever\n" +
|
|
||||||
"licenses to the work the party's predecessor in interest had or could\n" +
|
|
||||||
"give under the previous paragraph, plus a right to possession of the\n" +
|
|
||||||
"Corresponding Source of the work from the predecessor in interest, if\n" +
|
|
||||||
"the predecessor has it or can get it with reasonable efforts.\n" +
|
|
||||||
"\n" +
|
|
||||||
"You may not impose any further restrictions on the exercise of the\n" +
|
|
||||||
"rights granted or affirmed under this License. For example, you may\n" +
|
|
||||||
"not impose a license fee, royalty, or other charge for exercise of\n" +
|
|
||||||
"rights granted under this License, and you may not initiate litigation\n" +
|
|
||||||
"(including a cross-claim or counterclaim in a lawsuit) alleging that\n" +
|
|
||||||
"any patent claim is infringed by making, using, selling, offering for\n" +
|
|
||||||
"sale, or importing the Program or any portion of it.\n" +
|
|
||||||
"\n" +
|
|
||||||
"11. Patents.\n" +
|
|
||||||
"\n" +
|
|
||||||
"A \"contributor\" is a copyright holder who authorizes use under this\n" +
|
|
||||||
"License of the Program or a work on which the Program is based. The\n" +
|
|
||||||
"work thus licensed is called the contributor's \"contributor version\".\n" +
|
|
||||||
"\n" +
|
|
||||||
"A contributor's \"essential patent claims\" are all patent claims\n" +
|
|
||||||
"owned or controlled by the contributor, whether already acquired or\n" +
|
|
||||||
"hereafter acquired, that would be infringed by some manner, permitted\n" +
|
|
||||||
"by this License, of making, using, or selling its contributor version,\n" +
|
|
||||||
"but do not include claims that would be infringed only as a\n" +
|
|
||||||
"consequence of further modification of the contributor version. For\n" +
|
|
||||||
"purposes of this definition, \"control\" includes the right to grant\n" +
|
|
||||||
"patent sublicenses in a manner consistent with the requirements of\n" +
|
|
||||||
"this License.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Each contributor grants you a non-exclusive, worldwide, royalty-free\n" +
|
|
||||||
"patent license under the contributor's essential patent claims, to\n" +
|
|
||||||
"make, use, sell, offer for sale, import and otherwise run, modify and\n" +
|
|
||||||
"propagate the contents of its contributor version.\n" +
|
|
||||||
"\n" +
|
|
||||||
"In the following three paragraphs, a \"patent license\" is any express\n" +
|
|
||||||
"agreement or commitment, however denominated, not to enforce a patent\n" +
|
|
||||||
"(such as an express permission to practice a patent or covenant not to\n" +
|
|
||||||
"sue for patent infringement). To \"grant\" such a patent license to a\n" +
|
|
||||||
"party means to make such an agreement or commitment not to enforce a\n" +
|
|
||||||
"patent against the party.\n" +
|
|
||||||
"\n" +
|
|
||||||
"If you convey a covered work, knowingly relying on a patent license,\n" +
|
|
||||||
"and the Corresponding Source of the work is not available for anyone\n" +
|
|
||||||
"to copy, free of charge and under the terms of this License, through a\n" +
|
|
||||||
"publicly available network server or other readily accessible means,\n" +
|
|
||||||
"then you must either (1) cause the Corresponding Source to be so\n" +
|
|
||||||
"available, or (2) arrange to deprive yourself of the benefit of the\n" +
|
|
||||||
"patent license for this particular work, or (3) arrange, in a manner\n" +
|
|
||||||
"consistent with the requirements of this License, to extend the patent\n" +
|
|
||||||
"license to downstream recipients. \"Knowingly relying\" means you have\n" +
|
|
||||||
"actual knowledge that, but for the patent license, your conveying the\n" +
|
|
||||||
"covered work in a country, or your recipient's use of the covered work\n" +
|
|
||||||
"in a country, would infringe one or more identifiable patents in that\n" +
|
|
||||||
"country that you have reason to believe are valid.\n" +
|
|
||||||
"\n" +
|
|
||||||
"If, pursuant to or in connection with a single transaction or\n" +
|
|
||||||
"arrangement, you convey, or propagate by procuring conveyance of, a\n" +
|
|
||||||
"covered work, and grant a patent license to some of the parties\n" +
|
|
||||||
"receiving the covered work authorizing them to use, propagate, modify\n" +
|
|
||||||
"or convey a specific copy of the covered work, then the patent license\n" +
|
|
||||||
"you grant is automatically extended to all recipients of the covered\n" +
|
|
||||||
"work and works based on it.\n" +
|
|
||||||
"\n" +
|
|
||||||
"A patent license is \"discriminatory\" if it does not include within\n" +
|
|
||||||
"the scope of its coverage, prohibits the exercise of, or is\n" +
|
|
||||||
"conditioned on the non-exercise of one or more of the rights that are\n" +
|
|
||||||
"specifically granted under this License. You may not convey a covered\n" +
|
|
||||||
"work if you are a party to an arrangement with a third party that is\n" +
|
|
||||||
"in the business of distributing software, under which you make payment\n" +
|
|
||||||
"to the third party based on the extent of your activity of conveying\n" +
|
|
||||||
"the work, and under which the third party grants, to any of the\n" +
|
|
||||||
"parties who would receive the covered work from you, a discriminatory\n" +
|
|
||||||
"patent license (a) in connection with copies of the covered work\n" +
|
|
||||||
"conveyed by you (or copies made from those copies), or (b) primarily\n" +
|
|
||||||
"for and in connection with specific products or compilations that\n" +
|
|
||||||
"contain the covered work, unless you entered into that arrangement,\n" +
|
|
||||||
"or that patent license was granted, prior to 28 March 2007.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Nothing in this License shall be construed as excluding or limiting\n" +
|
|
||||||
"any implied license or other defenses to infringement that may\n" +
|
|
||||||
"otherwise be available to you under applicable patent law.\n" +
|
|
||||||
"\n" +
|
|
||||||
"12. No Surrender of Others' Freedom.\n" +
|
|
||||||
"\n" +
|
|
||||||
"If conditions are imposed on you (whether by court order, agreement or\n" +
|
|
||||||
"otherwise) that contradict the conditions of this License, they do not\n" +
|
|
||||||
"excuse you from the conditions of this License. If you cannot convey a\n" +
|
|
||||||
"covered work so as to satisfy simultaneously your obligations under this\n" +
|
|
||||||
"License and any other pertinent obligations, then as a consequence you may\n" +
|
|
||||||
"not convey it at all. For example, if you agree to terms that obligate you\n" +
|
|
||||||
"to collect a royalty for further conveying from those to whom you convey\n" +
|
|
||||||
"the Program, the only way you could satisfy both those terms and this\n" +
|
|
||||||
"License would be to refrain entirely from conveying the Program.\n" +
|
|
||||||
"\n" +
|
|
||||||
"13. Use with the GNU Affero General Public License.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Notwithstanding any other provision of this License, you have\n" +
|
|
||||||
"permission to link or combine any covered work with a work licensed\n" +
|
|
||||||
"under version 3 of the GNU Affero General Public License into a single\n" +
|
|
||||||
"combined work, and to convey the resulting work. The terms of this\n" +
|
|
||||||
"License will continue to apply to the part which is the covered work,\n" +
|
|
||||||
"but the special requirements of the GNU Affero General Public License,\n" +
|
|
||||||
"section 13, concerning interaction through a network will apply to the\n" +
|
|
||||||
"combination as such.\n" +
|
|
||||||
"\n" +
|
|
||||||
"14. Revised Versions of this License.\n" +
|
|
||||||
"\n" +
|
|
||||||
"The Free Software Foundation may publish revised and/or new versions of\n" +
|
|
||||||
"the GNU General Public License from time to time. Such new versions will\n" +
|
|
||||||
"be similar in spirit to the present version, but may differ in detail to\n" +
|
|
||||||
"address new problems or concerns.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Each version is given a distinguishing version number. If the\n" +
|
|
||||||
"Program specifies that a certain numbered version of the GNU General\n" +
|
|
||||||
"Public License \"or any later version\" applies to it, you have the\n" +
|
|
||||||
"option of following the terms and conditions either of that numbered\n" +
|
|
||||||
"version or of any later version published by the Free Software\n" +
|
|
||||||
"Foundation. If the Program does not specify a version number of the\n" +
|
|
||||||
"GNU General Public License, you may choose any version ever published\n" +
|
|
||||||
"by the Free Software Foundation.\n" +
|
|
||||||
"\n" +
|
|
||||||
"If the Program specifies that a proxy can decide which future\n" +
|
|
||||||
"versions of the GNU General Public License can be used, that proxy's\n" +
|
|
||||||
"public statement of acceptance of a version permanently authorizes you\n" +
|
|
||||||
"to choose that version for the Program.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Later license versions may give you additional or different\n" +
|
|
||||||
"permissions. However, no additional obligations are imposed on any\n" +
|
|
||||||
"author or copyright holder as a result of your choosing to follow a\n" +
|
|
||||||
"later version.\n" +
|
|
||||||
"\n" +
|
|
||||||
"15. Disclaimer of Warranty.\n" +
|
|
||||||
"\n" +
|
|
||||||
"THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\n" +
|
|
||||||
"APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\n" +
|
|
||||||
"HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\n" +
|
|
||||||
"OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\n" +
|
|
||||||
"THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n" +
|
|
||||||
"PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\n" +
|
|
||||||
"IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\n" +
|
|
||||||
"ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n" +
|
|
||||||
"\n" +
|
|
||||||
"16. Limitation of Liability.\n" +
|
|
||||||
"\n" +
|
|
||||||
"IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n" +
|
|
||||||
"WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\n" +
|
|
||||||
"THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\n" +
|
|
||||||
"GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\n" +
|
|
||||||
"USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\n" +
|
|
||||||
"DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\n" +
|
|
||||||
"PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\n" +
|
|
||||||
"EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\n" +
|
|
||||||
"SUCH DAMAGES.\n" +
|
|
||||||
"\n" +
|
|
||||||
"17. Interpretation of Sections 15 and 16.\n" +
|
|
||||||
"\n" +
|
|
||||||
"If the disclaimer of warranty and limitation of liability provided\n" +
|
|
||||||
"above cannot be given local legal effect according to their terms,\n" +
|
|
||||||
"reviewing courts shall apply local law that most closely approximates\n" +
|
|
||||||
"an absolute waiver of all civil liability in connection with the\n" +
|
|
||||||
"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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,64 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
|
|
||||||
|
|
||||||
class CommandLineDownloadProgress implements DownloadProgressInterface {
|
|
||||||
private int mediaCount = 0;
|
|
||||||
private int i = 0;
|
|
||||||
|
|
||||||
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;
|
|
||||||
System.out.println("Checking and downloading media.");
|
|
||||||
System.out.println("Legend:");
|
|
||||||
System.out.println("'V' - Video 'P' - Photo 'D' - Document");
|
|
||||||
System.out.println("'S' - Sticker 'A' - Audio 'G' - Geolocation");
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
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); }
|
|
||||||
}
|
|
@ -1,92 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.CommandLineController;
|
|
||||||
import de.fabianonline.telegram_backup.Utils;
|
|
||||||
import de.fabianonline.telegram_backup.Version;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import ch.qos.logback.classic.Logger;
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
|
||||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
||||||
import ch.qos.logback.core.ConsoleAppender;
|
|
||||||
import ch.qos.logback.classic.Level;
|
|
||||||
|
|
||||||
public class CommandLineRunner {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
CommandLineOptions.parseOptions(args);
|
|
||||||
|
|
||||||
setupLogging();
|
|
||||||
checkVersion();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (true || CommandLineOptions.cmd_console) {
|
|
||||||
// Always use the console for now.
|
|
||||||
new CommandLineController();
|
|
||||||
} else {
|
|
||||||
new GUIController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setupLogging() {
|
|
||||||
Logger logger = (Logger)LoggerFactory.getLogger(CommandLineRunner.class);
|
|
||||||
Logger rootLogger = (Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
|
||||||
LoggerContext rootContext = rootLogger.getLoggerContext();
|
|
||||||
rootContext.reset();
|
|
||||||
|
|
||||||
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
|
|
||||||
encoder.setContext(rootContext);
|
|
||||||
encoder.setPattern("%d{HH:mm:ss.SSS} %-5level %-35.-35(%logger{0}.%method): %message%n");
|
|
||||||
encoder.start();
|
|
||||||
|
|
||||||
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
|
|
||||||
appender.setContext(rootContext);
|
|
||||||
appender.setEncoder(encoder);
|
|
||||||
appender.start();
|
|
||||||
|
|
||||||
rootLogger.addAppender(appender);
|
|
||||||
rootLogger.setLevel(Level.OFF);
|
|
||||||
|
|
||||||
if (CommandLineOptions.cmd_trace) {
|
|
||||||
((Logger)LoggerFactory.getLogger("de.fabianonline.telegram_backup")).setLevel(Level.TRACE);
|
|
||||||
} else if (CommandLineOptions.cmd_debug) {
|
|
||||||
((Logger)LoggerFactory.getLogger("de.fabianonline.telegram_backup")).setLevel(Level.DEBUG);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CommandLineOptions.cmd_trace_telegram) {
|
|
||||||
((Logger)LoggerFactory.getLogger("com.github.badoualy")).setLevel(Level.TRACE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean checkVersion() {
|
|
||||||
Version v = Utils.getNewestVersion();
|
|
||||||
if (v!=null && v.isNewer) {
|
|
||||||
System.out.println("A newer version is vailable!");
|
|
||||||
System.out.println("You are using: " + Config.APP_APPVER);
|
|
||||||
System.out.println("Available: " + v.version);
|
|
||||||
System.out.println("Get it here: " + v.url);
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Changes in this version:");
|
|
||||||
System.out.println(v.body);
|
|
||||||
System.out.println();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
public class Config {
|
|
||||||
public static final int APP_ID = 32860;
|
|
||||||
public static final String APP_HASH = "16e4ff955cd0adfc058f95ca564f562d";
|
|
||||||
public static final String APP_MODEL = "Desktop";
|
|
||||||
public static final String APP_SYSVER = "1.0";
|
|
||||||
public static final String APP_APPVER;
|
|
||||||
public static final String APP_LANG = "en";
|
|
||||||
|
|
||||||
public static String FILE_BASE = System.getProperty("user.home") + File.separatorChar + ".telegram_backup";
|
|
||||||
public static final String FILE_NAME_AUTH_KEY = "auth.dat";
|
|
||||||
public static final String FILE_NAME_DC = "dc.dat";
|
|
||||||
public static final String FILE_NAME_DB = "database.sqlite";
|
|
||||||
public static final String FILE_NAME_DB_BACKUP = "database.version_%d.backup.sqlite";
|
|
||||||
public static final String FILE_FILES_BASE = "files";
|
|
||||||
public static final String FILE_STICKER_BASE = "stickers";
|
|
||||||
|
|
||||||
public static int DELAY_AFTER_GET_MESSAGES = 400;
|
|
||||||
public static int DELAY_AFTER_GET_FILE = 100;
|
|
||||||
public static int GET_MESSAGES_BATCH_SIZE = 200;
|
|
||||||
|
|
||||||
public static int RENAMING_MAX_TRIES = 5;
|
|
||||||
public static int RENAMING_DELAY = 1000;
|
|
||||||
|
|
||||||
public static final String SECRET_GMAPS = "AIzaSyBEtUDhCQKEH6i2Mn1GAiQ9M_tLN0vxHIs";
|
|
||||||
|
|
||||||
static {
|
|
||||||
Properties p = new Properties();
|
|
||||||
try {
|
|
||||||
p.load(Config.class.getResourceAsStream("/build.properties"));
|
|
||||||
APP_APPVER = p.getProperty("version");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,715 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLVector;
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.ResultSetMetaData;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.Types;
|
|
||||||
import java.sql.Time;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory;
|
|
||||||
|
|
||||||
public class Database {
|
|
||||||
private Connection conn;
|
|
||||||
private Statement stmt;
|
|
||||||
public UserManager user_manager;
|
|
||||||
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;
|
|
||||||
System.out.println("Opening database...");
|
|
||||||
try {
|
|
||||||
Class.forName("org.sqlite.JDBC");
|
|
||||||
} 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);
|
|
||||||
try {
|
|
||||||
String src = user_manager.getFileBase() + Config.FILE_NAME_DB;
|
|
||||||
String dst = user_manager.getFileBase() + filename;
|
|
||||||
logger.debug("Copying {} to {}", src, dst);
|
|
||||||
Files.copy(
|
|
||||||
new File(src).toPath(),
|
|
||||||
new File(dst).toPath());
|
|
||||||
} catch (FileAlreadyExistsException e) {
|
|
||||||
logger.warn("Backup already exists:", e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Could not create backup.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTopMessageID() {
|
|
||||||
try {
|
|
||||||
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 "+
|
|
||||||
"(time, start_id, end_id, count_missing) "+
|
|
||||||
"VALUES "+
|
|
||||||
"(DateTime('now'), ?, ?, ? )");
|
|
||||||
ps.setInt(1, start_id);
|
|
||||||
ps.setInt(2, end_id);
|
|
||||||
ps.setInt(3, count);
|
|
||||||
ps.execute();
|
|
||||||
} catch (SQLException e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int queryInt(String query) {
|
|
||||||
try {
|
|
||||||
ResultSet rs = stmt.executeQuery(query);
|
|
||||||
rs.next();
|
|
||||||
return rs.getInt(1);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
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<Integer> getMissingIDs() {
|
|
||||||
try {
|
|
||||||
LinkedList<Integer> missing = new LinkedList<Integer>();
|
|
||||||
int max = getTopMessageID();
|
|
||||||
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++) {
|
|
||||||
if (i==id) {
|
|
||||||
rs.next();
|
|
||||||
if (rs.isClosed()) {
|
|
||||||
id = Integer.MAX_VALUE;
|
|
||||||
} else {
|
|
||||||
id=rs.getInt(1);
|
|
||||||
}
|
|
||||||
} else if (i<id) {
|
|
||||||
missing.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return missing;
|
|
||||||
} catch(SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Could not get list of ids.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void saveMessages(TLVector<TLAbsMessage> all, Integer api_layer) {
|
|
||||||
try {
|
|
||||||
//"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " +
|
|
||||||
//"VALUES " +
|
|
||||||
//"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
String columns =
|
|
||||||
"(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
|
|
||||||
PreparedStatement ps = conn.prepareStatement("INSERT OR REPLACE INTO messages " + columns);
|
|
||||||
PreparedStatement ps_insert_or_ignore = conn.prepareStatement("INSERT OR IGNORE INTO messages " + columns);
|
|
||||||
|
|
||||||
for (TLAbsMessage abs : all) {
|
|
||||||
if (abs instanceof TLMessage) {
|
|
||||||
TLMessage msg = (TLMessage) abs;
|
|
||||||
ps.setInt(1, msg.getId());
|
|
||||||
ps.setString(2, "message");
|
|
||||||
TLAbsPeer peer = msg.getToId();
|
|
||||||
if (peer instanceof TLPeerChat) {
|
|
||||||
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.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());
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
ps.setNull(6, Types.INTEGER);
|
|
||||||
}
|
|
||||||
|
|
||||||
String text = msg.getMessage();
|
|
||||||
if ((text==null || text.equals("")) && msg.getMedia()!=null) {
|
|
||||||
if (msg.getMedia() instanceof TLMessageMediaDocument) {
|
|
||||||
text = ((TLMessageMediaDocument)msg.getMedia()).getCaption();
|
|
||||||
} else if (msg.getMedia() instanceof TLMessageMediaPhoto) {
|
|
||||||
text = ((TLMessageMediaPhoto)msg.getMedia()).getCaption();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ps.setString(7, text);
|
|
||||||
ps.setString(8, ""+msg.getDate());
|
|
||||||
AbstractMediaFileManager f = FileManagerFactory.getFileManager(msg, user_manager, client);
|
|
||||||
if (f==null) {
|
|
||||||
ps.setNull(9, Types.BOOLEAN);
|
|
||||||
ps.setNull(10, Types.VARCHAR);
|
|
||||||
ps.setNull(11, Types.VARCHAR);
|
|
||||||
ps.setNull(12, Types.INTEGER);
|
|
||||||
} else {
|
|
||||||
ps.setBoolean(9, true);
|
|
||||||
ps.setString(10, f.getName());
|
|
||||||
ps.setString(11, f.getTargetFilename());
|
|
||||||
ps.setInt(12, f.getSize());
|
|
||||||
}
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
msg.serializeBody(stream);
|
|
||||||
ps.setBytes(13, stream.toByteArray());
|
|
||||||
ps.setInt(14, api_layer);
|
|
||||||
ps.addBatch();
|
|
||||||
} else if (abs instanceof TLMessageService) {
|
|
||||||
ps_insert_or_ignore.setInt(1, abs.getId());
|
|
||||||
ps_insert_or_ignore.setString(2, "service_message");
|
|
||||||
ps_insert_or_ignore.setNull(3, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(4, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(5, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(6, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(7, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(8, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(9, Types.BOOLEAN);
|
|
||||||
ps_insert_or_ignore.setNull(10, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(11, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(12, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(13, Types.BLOB);
|
|
||||||
ps_insert_or_ignore.setInt(14, api_layer);
|
|
||||||
ps_insert_or_ignore.addBatch();
|
|
||||||
} else if (abs instanceof TLMessageEmpty) {
|
|
||||||
ps_insert_or_ignore.setInt(1, abs.getId());
|
|
||||||
ps_insert_or_ignore.setString(2, "empty_message");
|
|
||||||
ps_insert_or_ignore.setNull(3, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(4, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(5, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(6, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(7, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(8, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(9, Types.BOOLEAN);
|
|
||||||
ps_insert_or_ignore.setNull(10, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(11, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(12, Types.INTEGER);
|
|
||||||
ps_insert_or_ignore.setNull(13, Types.BLOB);
|
|
||||||
ps_insert_or_ignore.setInt(14, api_layer);
|
|
||||||
ps_insert_or_ignore.addBatch();
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unexpected Message type: " + abs.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn.setAutoCommit(false);
|
|
||||||
ps.executeBatch();
|
|
||||||
ps.clearBatch();
|
|
||||||
ps_insert_or_ignore.executeBatch();
|
|
||||||
ps_insert_or_ignore.clearBatch();
|
|
||||||
conn.commit();
|
|
||||||
conn.setAutoCommit(true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Exception shown above happened.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void saveChats(TLVector<TLAbsChat> all) {
|
|
||||||
try {
|
|
||||||
PreparedStatement ps_insert_or_replace = conn.prepareStatement(
|
|
||||||
"INSERT OR REPLACE INTO chats " +
|
|
||||||
"(id, name, type) "+
|
|
||||||
"VALUES " +
|
|
||||||
"(?, ?, ?)");
|
|
||||||
PreparedStatement ps_insert_or_ignore = conn.prepareStatement(
|
|
||||||
"INSERT OR IGNORE INTO chats " +
|
|
||||||
"(id, name, type) "+
|
|
||||||
"VALUES " +
|
|
||||||
"(?, ?, ?)");
|
|
||||||
|
|
||||||
for(TLAbsChat abs : all) {
|
|
||||||
ps_insert_or_replace.setInt(1, abs.getId());
|
|
||||||
ps_insert_or_ignore.setInt(1, abs.getId());
|
|
||||||
if (abs instanceof TLChatEmpty) {
|
|
||||||
ps_insert_or_ignore.setNull(2, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setString(3, "empty_chat");
|
|
||||||
ps_insert_or_ignore.addBatch();
|
|
||||||
} else if (abs instanceof TLChatForbidden) {
|
|
||||||
ps_insert_or_replace.setString(2, ((TLChatForbidden)abs).getTitle());
|
|
||||||
ps_insert_or_replace.setString(3, "chat");
|
|
||||||
ps_insert_or_replace.addBatch();
|
|
||||||
} else if (abs instanceof TLChannelForbidden) {
|
|
||||||
ps_insert_or_replace.setString(2, ((TLChannelForbidden)abs).getTitle());
|
|
||||||
ps_insert_or_replace.setString(3, "channel");
|
|
||||||
ps_insert_or_replace.addBatch();
|
|
||||||
} else if (abs instanceof TLChat) {
|
|
||||||
ps_insert_or_replace.setString(2, ((TLChat) abs).getTitle());
|
|
||||||
ps_insert_or_replace.setString(3, "chat");
|
|
||||||
ps_insert_or_replace.addBatch();
|
|
||||||
} else if (abs instanceof TLChannel) {
|
|
||||||
ps_insert_or_replace.setString(2, ((TLChannel)abs).getTitle());
|
|
||||||
ps_insert_or_replace.setString(3, "channel");
|
|
||||||
ps_insert_or_replace.addBatch();
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unexpected " + abs.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn.setAutoCommit(false);
|
|
||||||
ps_insert_or_ignore.executeBatch();
|
|
||||||
ps_insert_or_ignore.clearBatch();
|
|
||||||
ps_insert_or_replace.executeBatch();
|
|
||||||
ps_insert_or_replace.clearBatch();
|
|
||||||
conn.commit();
|
|
||||||
conn.setAutoCommit(true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Exception shown above happened.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void saveUsers(TLVector<TLAbsUser> all) {
|
|
||||||
try {
|
|
||||||
PreparedStatement ps_insert_or_replace = conn.prepareStatement(
|
|
||||||
"INSERT OR REPLACE INTO users " +
|
|
||||||
"(id, first_name, last_name, username, type, phone) " +
|
|
||||||
"VALUES " +
|
|
||||||
"(?, ?, ?, ?, ?, ?)");
|
|
||||||
PreparedStatement ps_insert_or_ignore = conn.prepareStatement(
|
|
||||||
"INSERT OR IGNORE INTO users " +
|
|
||||||
"(id, first_name, last_name, username, type, phone) " +
|
|
||||||
"VALUES " +
|
|
||||||
"(?, ?, ?, ?, ?, ?)");
|
|
||||||
for (TLAbsUser abs : all) {
|
|
||||||
if (abs instanceof TLUser) {
|
|
||||||
TLUser user = (TLUser)abs;
|
|
||||||
ps_insert_or_replace.setInt(1, user.getId());
|
|
||||||
ps_insert_or_replace.setString(2, user.getFirstName());
|
|
||||||
ps_insert_or_replace.setString(3, user.getLastName());
|
|
||||||
ps_insert_or_replace.setString(4, user.getUsername());
|
|
||||||
ps_insert_or_replace.setString(5, "user");
|
|
||||||
ps_insert_or_replace.setString(6, user.getPhone());
|
|
||||||
ps_insert_or_replace.addBatch();
|
|
||||||
} else if (abs instanceof TLUserEmpty) {
|
|
||||||
ps_insert_or_ignore.setInt(1, abs.getId());
|
|
||||||
ps_insert_or_ignore.setNull(2, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(3, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setNull(4, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.setString(5, "empty_user");
|
|
||||||
ps_insert_or_ignore.setNull(6, Types.VARCHAR);
|
|
||||||
ps_insert_or_ignore.addBatch();
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unexpected " + abs.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn.setAutoCommit(false);
|
|
||||||
ps_insert_or_ignore.executeBatch();
|
|
||||||
ps_insert_or_ignore.clearBatch();
|
|
||||||
ps_insert_or_replace.executeBatch();
|
|
||||||
ps_insert_or_replace.clearBatch();
|
|
||||||
conn.commit();
|
|
||||||
conn.setAutoCommit(true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Exception shown above happened.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinkedList<TLMessage> getMessagesWithMedia() {
|
|
||||||
try {
|
|
||||||
LinkedList<TLMessage> list = new LinkedList<TLMessage>();
|
|
||||||
ResultSet rs = stmt.executeQuery("SELECT data FROM messages WHERE has_media=1");
|
|
||||||
while (rs.next()) {
|
|
||||||
list.add(bytesToTLMessage(rs.getBytes(1)));
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
return list;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
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());
|
|
||||||
rs.next();
|
|
||||||
return rs.getInt(1);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinkedList<Integer> getIdsFromQuery(String query) {
|
|
||||||
try {
|
|
||||||
LinkedList<Integer> list = new LinkedList<Integer>();
|
|
||||||
ResultSet rs = stmt.executeQuery(query);
|
|
||||||
while(rs.next()) { list.add(rs.getInt(1)); }
|
|
||||||
rs.close();
|
|
||||||
return list;
|
|
||||||
} catch (SQLException e) { throw new RuntimeException(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, Integer> getMessageTypesWithCount() {
|
|
||||||
return getMessageTypesWithCount(new GlobalChat());
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, Integer> getMessageTypesWithCount(AbstractChat c) {
|
|
||||||
HashMap<String, Integer> map = new HashMap<String, Integer>();
|
|
||||||
try {
|
|
||||||
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<String, Integer> getMessageMediaTypesWithCount() {
|
|
||||||
return getMessageMediaTypesWithCount(new GlobalChat());
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, Integer> getMessageMediaTypesWithCount(AbstractChat c) {
|
|
||||||
HashMap<String, Integer> map = new HashMap<String, Integer>();
|
|
||||||
try {
|
|
||||||
int count = 0;
|
|
||||||
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) {
|
|
||||||
s="null";
|
|
||||||
} else {
|
|
||||||
count += rs.getInt(2);
|
|
||||||
}
|
|
||||||
map.put("count.messages.media_type." + s, rs.getInt(2));
|
|
||||||
}
|
|
||||||
map.put("count.messages.media_type.any", count);
|
|
||||||
return map;
|
|
||||||
} catch (Exception e) { throw new RuntimeException(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, Integer> getMessageApiLayerWithCount() {
|
|
||||||
HashMap<String, Integer> map = new HashMap<String, Integer>();
|
|
||||||
try {
|
|
||||||
ResultSet rs = stmt.executeQuery("SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer");
|
|
||||||
while(rs.next()) {
|
|
||||||
Integer layer = rs.getInt(2);
|
|
||||||
if (layer==null) layer=0;
|
|
||||||
map.put("count.messages.api_layer." + layer, rs.getInt(1));
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
return map;
|
|
||||||
} catch (Exception e) { throw new RuntimeException(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, Object> getMessageAuthorsWithCount() {
|
|
||||||
return getMessageAuthorsWithCount(new GlobalChat());
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<String, Object> getMessageAuthorsWithCount(AbstractChat c) {
|
|
||||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
|
||||||
HashMap<User, Integer> user_map = new HashMap<User, Integer>();
|
|
||||||
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 " +
|
|
||||||
"LEFT JOIN users ON users.id=messages.sender_id " +
|
|
||||||
"WHERE " + c.getQuery() + " GROUP BY sender_id");
|
|
||||||
while (rs.next()) {
|
|
||||||
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 {
|
|
||||||
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(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 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);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) { throw new RuntimeException(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEncoding() {
|
|
||||||
try {
|
|
||||||
ResultSet rs = stmt.executeQuery("PRAGMA encoding");
|
|
||||||
rs.next();
|
|
||||||
return rs.getString(1);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
logger.debug("SQLException: {}", e);
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public LinkedList<Chat> getListOfChatsForExport() {
|
|
||||||
LinkedList<Chat> list = new LinkedList<Chat>();
|
|
||||||
try {
|
|
||||||
ResultSet rs = stmt.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c "+
|
|
||||||
"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)));
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
return list;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Exception above!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public LinkedList<Dialog> getListOfDialogsForExport() {
|
|
||||||
LinkedList<Dialog> list = new LinkedList<Dialog>();
|
|
||||||
try {
|
|
||||||
ResultSet rs = stmt.executeQuery(
|
|
||||||
"SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " +
|
|
||||||
"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)));
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
return list;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Exception above!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinkedList<HashMap<String, Object>> getMessagesForExport(AbstractChat c) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
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 " +
|
|
||||||
"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<HashMap<String, Object>> list = new LinkedList<HashMap<String, Object>>();
|
|
||||||
|
|
||||||
Integer count=0;
|
|
||||||
String old_date = null;
|
|
||||||
Integer old_user = null;
|
|
||||||
while (rs.next()) {
|
|
||||||
HashMap<String, Object> h = new HashMap<String, Object>(columns);
|
|
||||||
for (int i=1; i<=columns; i++) {
|
|
||||||
h.put(meta.getColumnName(i), rs.getObject(i));
|
|
||||||
}
|
|
||||||
// Additional values to make up for Mustache's inability to format dates
|
|
||||||
Date d = rs.getTime("time");
|
|
||||||
String date = format_date.format(d);
|
|
||||||
h.put("formatted_time", format_time.format(d));
|
|
||||||
h.put("formatted_date", date);
|
|
||||||
if (rs.getString("media_type")!=null) {
|
|
||||||
h.put("media_" + rs.getString("media_type"), true);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
} catch(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Exception above!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TLMessage bytesToTLMessage(byte[] b) {
|
|
||||||
try {
|
|
||||||
if (b==null) return null;
|
|
||||||
ByteArrayInputStream stream = new ByteArrayInputStream(b);
|
|
||||||
TLMessage msg = new TLMessage();
|
|
||||||
msg.deserializeBody(stream, TLApiContext.getInstance());
|
|
||||||
return msg;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
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;
|
|
||||||
this.last_name = last_name;
|
|
||||||
this.username = username;
|
|
||||||
this.count = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "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();
|
|
||||||
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"; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,349 +0,0 @@
|
|||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.sql.Types;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
|
|
||||||
|
|
||||||
public class DatabaseUpdates {
|
|
||||||
protected Connection conn;
|
|
||||||
protected Database db;
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseUpdates.class);
|
|
||||||
private static LinkedList<DatabaseUpdate> updates = new LinkedList<DatabaseUpdate>();
|
|
||||||
|
|
||||||
public DatabaseUpdates(Connection conn, Database db) {
|
|
||||||
this.conn = conn;
|
|
||||||
this.db = db;
|
|
||||||
logger.debug("Registering Database Updates...");
|
|
||||||
register(new DB_Update_1(conn, db));
|
|
||||||
register(new DB_Update_2(conn, db));
|
|
||||||
register(new DB_Update_3(conn, db));
|
|
||||||
register(new DB_Update_4(conn, db));
|
|
||||||
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();
|
|
||||||
ResultSet rs;
|
|
||||||
logger.debug("DatabaseUpdate.doUpdates running");
|
|
||||||
|
|
||||||
logger.debug("Getting current database version");
|
|
||||||
int version;
|
|
||||||
logger.debug("Checking if table database_versions exists");
|
|
||||||
rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'");
|
|
||||||
rs.next();
|
|
||||||
if (rs.getInt(1)==0) {
|
|
||||||
logger.debug("Table does not exist");
|
|
||||||
version = 0;
|
|
||||||
} else {
|
|
||||||
logger.debug("Table exists. Checking max version");
|
|
||||||
rs.close();
|
|
||||||
rs = stmt.executeQuery("SELECT MAX(version) FROM database_versions");
|
|
||||||
rs.next();
|
|
||||||
version = rs.getInt(1);
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
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;
|
|
||||||
for (int i=version+1; i<=getMaxPossibleVersion(); i++) {
|
|
||||||
if (getUpdateToVersion(i).needsBackup()) {
|
|
||||||
logger.debug("Update to version {} needs a backup", i);
|
|
||||||
backup=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (backup) {
|
|
||||||
if (version > 0) {
|
|
||||||
logger.debug("Performing backup");
|
|
||||||
db.backupDatabase(version);
|
|
||||||
} else {
|
|
||||||
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++) {
|
|
||||||
getUpdateToVersion(i).doUpdate();
|
|
||||||
}
|
|
||||||
} catch (SQLException e) { throw new RuntimeException(e); }
|
|
||||||
} 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) {
|
|
||||||
throw new RuntimeException("Tried to register DB update to version " + d.getVersion() + ", but would need update to version " + (updates.size()+1));
|
|
||||||
}
|
|
||||||
updates.add(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DatabaseUpdate {
|
|
||||||
protected Connection conn;
|
|
||||||
protected Statement stmt;
|
|
||||||
protected Database db;
|
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(DatabaseUpdate.class);
|
|
||||||
public DatabaseUpdate(Connection conn, Database db) {
|
|
||||||
this.conn = conn;
|
|
||||||
try {
|
|
||||||
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());
|
|
||||||
System.out.println(" Updating to version " + getVersion() + "...");
|
|
||||||
_doUpdate();
|
|
||||||
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, "
|
|
||||||
+ "dialog_id INTEGER, "
|
|
||||||
+ "to_id INTEGER, "
|
|
||||||
+ "from_id INTEGER, "
|
|
||||||
+ "from_type TEXT, "
|
|
||||||
+ "text TEXT, "
|
|
||||||
+ "time TEXT, "
|
|
||||||
+ "has_media BOOLEAN, "
|
|
||||||
+ "sticker TEXT, "
|
|
||||||
+ "data BLOB,"
|
|
||||||
+ "type TEXT)");
|
|
||||||
stmt.executeUpdate("CREATE TABLE dialogs ("
|
|
||||||
+ "id INTEGER PRIMARY KEY ASC, "
|
|
||||||
+ "name TEXT, "
|
|
||||||
+ "type TEXT)");
|
|
||||||
stmt.executeUpdate("CREATE TABLE people ("
|
|
||||||
+ "id INTEGER PRIMARY KEY ASC, "
|
|
||||||
+ "first_name TEXT, "
|
|
||||||
+ "last_name TEXT, "
|
|
||||||
+ "username TEXT, "
|
|
||||||
+ "type TEXT)");
|
|
||||||
stmt.executeUpdate("CREATE TABLE database_versions ("
|
|
||||||
+ "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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
stmt.executeUpdate("DROP TABLE messages");
|
|
||||||
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO 'messages'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DB_Update_5 extends DatabaseUpdate {
|
|
||||||
public int getVersion() { return 5; }
|
|
||||||
public DB_Update_5(Connection conn, Database db) { super(conn, db); }
|
|
||||||
|
|
||||||
protected void _doUpdate() throws SQLException {
|
|
||||||
stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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" +
|
|
||||||
" id INTEGER PRIMARY KEY ASC,\n" +
|
|
||||||
" message_type TEXT,\n" +
|
|
||||||
" dialog_id INTEGER,\n" +
|
|
||||||
" chat_id INTEGER,\n" +
|
|
||||||
" sender_id INTEGER,\n" +
|
|
||||||
" fwd_from_id INTEGER,\n" +
|
|
||||||
" text TEXT,\n" +
|
|
||||||
" time INTEGER,\n" +
|
|
||||||
" has_media BOOLEAN,\n" +
|
|
||||||
" media_type TEXT,\n" +
|
|
||||||
" media_file TEXT,\n" +
|
|
||||||
" media_size INTEGER,\n" +
|
|
||||||
" media_json TEXT,\n" +
|
|
||||||
" markup_json TEXT,\n" +
|
|
||||||
" data BLOB)");
|
|
||||||
LinkedHashMap<String, String> mappings = new LinkedHashMap<String, String>();
|
|
||||||
mappings.put("id", "id");
|
|
||||||
mappings.put("message_type", "type");
|
|
||||||
mappings.put("dialog_id", "CASE from_type WHEN 'user' THEN dialog_id ELSE NULL END");
|
|
||||||
mappings.put("chat_id", "CASE from_type WHEN 'chat' THEN dialog_id ELSE NULL END");
|
|
||||||
mappings.put("sender_id", "from_id");
|
|
||||||
mappings.put("text", "text");
|
|
||||||
mappings.put("time", "time");
|
|
||||||
mappings.put("has_media", "has_media");
|
|
||||||
mappings.put("data", "data");
|
|
||||||
StringBuilder query = new StringBuilder("INSERT INTO messages_new\n(");
|
|
||||||
boolean first;
|
|
||||||
first = true;
|
|
||||||
for(String s : mappings.keySet()) {
|
|
||||||
if (!first) query.append(", ");
|
|
||||||
query.append(s);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
query.append(")\nSELECT \n");
|
|
||||||
first = true;
|
|
||||||
for (String s : mappings.values()) {
|
|
||||||
if (!first) query.append(", ");
|
|
||||||
query.append(s);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
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=?");
|
|
||||||
while (rs.next()) {
|
|
||||||
ps.setInt(5, rs.getInt(1));
|
|
||||||
TLMessage msg = db.bytesToTLMessage(rs.getBytes(2));
|
|
||||||
if (msg==null || msg.getFwdFrom()==null) {
|
|
||||||
ps.setNull(1, Types.INTEGER);
|
|
||||||
} else {
|
|
||||||
ps.setInt(1, msg.getFwdFrom().getFromId());
|
|
||||||
}
|
|
||||||
AbstractMediaFileManager f = FileManagerFactory.getFileManager(msg, db.user_manager, db.client);
|
|
||||||
if (f==null) {
|
|
||||||
ps.setNull(2, Types.VARCHAR);
|
|
||||||
ps.setNull(3, Types.VARCHAR);
|
|
||||||
ps.setNull(4, Types.INTEGER);
|
|
||||||
} else {
|
|
||||||
ps.setString(2, f.getName());
|
|
||||||
ps.setString(3, f.getTargetFilename());
|
|
||||||
ps.setInt(4, f.getSize());
|
|
||||||
}
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
conn.setAutoCommit(false);
|
|
||||||
ps.executeBatch();
|
|
||||||
conn.commit();
|
|
||||||
conn.setAutoCommit(true);
|
|
||||||
stmt.executeUpdate("DROP TABLE messages");
|
|
||||||
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO messages");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,471 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.api.Kotlogram;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public class DownloadManager {
|
|
||||||
UserManager user;
|
|
||||||
TelegramClient client;
|
|
||||||
Database db;
|
|
||||||
DownloadProgressInterface prog = null;
|
|
||||||
static TelegramClient download_client;
|
|
||||||
static boolean last_download_succeeded = true;
|
|
||||||
static final Logger logger = LoggerFactory.getLogger(DownloadManager.class);
|
|
||||||
boolean has_seen_flood_wait_message = false;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
completed = true;
|
|
||||||
try {
|
|
||||||
_downloadMessages(limit);
|
|
||||||
} catch (RpcErrorException e) {
|
|
||||||
if (e.getCode()==420) { // FLOOD_WAIT
|
|
||||||
completed = false;
|
|
||||||
Utils.obeyFloodWaitException(e);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
completed = false;
|
|
||||||
System.out.println("");
|
|
||||||
System.out.println("Telegram took too long to respond to our request.");
|
|
||||||
System.out.println("I'm going to wait a minute and then try again.");
|
|
||||||
try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {}
|
|
||||||
System.out.println("");
|
|
||||||
}
|
|
||||||
} while (!completed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void _downloadMessages(Integer limit) throws RpcErrorException, IOException, TimeoutException {
|
|
||||||
logger.info("This is _downloadMessages with limit {}", limit);
|
|
||||||
int dialog_limit = 100;
|
|
||||||
logger.info("Downloading the last {} dialogs", dialog_limit);
|
|
||||||
System.out.println("Downloading most recent dialogs... ");
|
|
||||||
int max_message_id = 0;
|
|
||||||
TLAbsDialogs dialogs = client.messagesGetDialogs(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
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());
|
|
||||||
max_message_id = d.getTopMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
System.out.println("Top message ID is " + max_message_id);
|
|
||||||
int max_database_id = db.getTopMessageID();
|
|
||||||
System.out.println("Top message ID in database is " + max_database_id);
|
|
||||||
if (limit != null) {
|
|
||||||
System.out.println("Limit is set to " + limit);
|
|
||||||
max_database_id = Math.max(max_database_id, max_message_id-limit);
|
|
||||||
System.out.println("New top message id 'in database' is " + max_database_id);
|
|
||||||
}
|
|
||||||
if (max_message_id - max_database_id > 1000000) {
|
|
||||||
System.out.println("Would have to load more than 1 million messages which is not supported by telegram. Capping the list.");
|
|
||||||
logger.debug("max_message_id={}, max_database_id={}, difference={}", max_message_id, max_database_id, max_message_id - max_database_id);
|
|
||||||
max_database_id = Math.max(0, max_message_id - 1000000);
|
|
||||||
logger.debug("new max_database_id: {}", max_database_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max_database_id == max_message_id) {
|
|
||||||
System.out.println("No new messages to download.");
|
|
||||||
} else if (max_database_id > max_message_id) {
|
|
||||||
throw new RuntimeException("max_database_id is bigger then max_message_id. This shouldn't happen. But the telegram api nonetheless does that sometimes. Just ignore this error, wait a few seconds and then try again.");
|
|
||||||
} else {
|
|
||||||
int start_id = max_database_id + 1;
|
|
||||||
int end_id = max_message_id;
|
|
||||||
|
|
||||||
List<Integer> ids = makeIdList(start_id, end_id);
|
|
||||||
downloadMessages(ids, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Searching for missing messages in the db");
|
|
||||||
int count_missing = 0;
|
|
||||||
System.out.println("Checking message database for completeness...");
|
|
||||||
int db_count = db.getMessageCount();
|
|
||||||
int db_max = db.getTopMessageID();
|
|
||||||
logger.debug("db_count: {}", db_count);
|
|
||||||
logger.debug("db_max: {}", 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 {
|
|
||||||
LinkedList<Integer> all_missing_ids = db.getMissingIDs();
|
|
||||||
LinkedList<Integer> downloadable_missing_ids = new LinkedList<Integer>();
|
|
||||||
for (Integer id : all_missing_ids) {
|
|
||||||
if (id > max_message_id - 1000000) downloadable_missing_ids.add(id);
|
|
||||||
}
|
|
||||||
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, 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 || 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<Integer, Long> channel_access_hashes = new HashMap<Integer, Long>();
|
|
||||||
HashMap<Integer, String> channel_names = new HashMap<Integer, String>();
|
|
||||||
LinkedList<Integer> channels = new LinkedList<Integer>();
|
|
||||||
LinkedList<Integer> supergroups = new LinkedList<Integer>();
|
|
||||||
|
|
||||||
// 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());
|
|
||||||
if (ch.getMegagroup()) {
|
|
||||||
supergroups.add(c.getId());
|
|
||||||
} else {
|
|
||||||
channels.add(c.getId());
|
|
||||||
}
|
|
||||||
// Channel: TLChannel
|
|
||||||
// Supergroup: getMegagroup()==true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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<Integer> ids = 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, "channel " + channel_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadMessages(List<Integer> ids, TLInputChannel channel, String source_string) throws RpcErrorException, IOException {
|
|
||||||
prog.onMessageDownloadStart(ids.size(), source_string);
|
|
||||||
|
|
||||||
logger.debug("Entering download loop");
|
|
||||||
while (ids.size()>0) {
|
|
||||||
logger.trace("Loop");
|
|
||||||
TLIntVector vector = new TLIntVector();
|
|
||||||
int download_count = Config.GET_MESSAGES_BATCH_SIZE;
|
|
||||||
logger.trace("download_count: {}", download_count);
|
|
||||||
for (int i=0; i<download_count; i++) {
|
|
||||||
if (ids.size()==0) break;
|
|
||||||
vector.add(ids.remove(0));
|
|
||||||
}
|
|
||||||
logger.trace("vector.size(): {}", vector.size());
|
|
||||||
logger.trace("ids.size(): {}", ids.size());
|
|
||||||
|
|
||||||
TLAbsMessages response;
|
|
||||||
int tries = 0;
|
|
||||||
while(true) {
|
|
||||||
logger.trace("Trying getMessages(), tries={}", tries);
|
|
||||||
if (tries>=5) {
|
|
||||||
CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.");
|
|
||||||
}
|
|
||||||
tries++;
|
|
||||||
try {
|
|
||||||
if (channel == null) {
|
|
||||||
response = client.messagesGetMessages(vector);
|
|
||||||
} else {
|
|
||||||
response = client.channelsGetMessages(channel, vector);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} catch (RpcErrorException e) {
|
|
||||||
if (e.getCode()==420) { // FLOOD_WAIT
|
|
||||||
Utils.obeyFloodWaitException(e, has_seen_flood_wait_message);
|
|
||||||
has_seen_flood_wait_message = true;
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.trace("response.getMessages().size(): {}", response.getMessages().size());
|
|
||||||
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());
|
|
||||||
db.saveUsers(response.getUsers());
|
|
||||||
logger.trace("Sleeping");
|
|
||||||
try {
|
|
||||||
TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES);
|
|
||||||
} catch (InterruptedException e) {}
|
|
||||||
}
|
|
||||||
logger.debug("Finished.");
|
|
||||||
|
|
||||||
prog.onMessageDownloadFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void downloadMedia() throws RpcErrorException, IOException {
|
|
||||||
download_client = client.getDownloaderClient();
|
|
||||||
boolean completed = true;
|
|
||||||
do {
|
|
||||||
completed = true;
|
|
||||||
try {
|
|
||||||
_downloadMedia();
|
|
||||||
} catch (RpcErrorException e) {
|
|
||||||
if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
|
|
||||||
completed = false;
|
|
||||||
Utils.obeyFloodWaitException(e);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} /*catch (TimeoutException e) {
|
|
||||||
completed = false;
|
|
||||||
System.out.println("");
|
|
||||||
System.out.println("Telegram took too long to respond to our request.");
|
|
||||||
System.out.println("I'm going to wait a minute and then try again.");
|
|
||||||
logger.warn("TimeoutException caught", e);
|
|
||||||
try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {}
|
|
||||||
System.out.println("");
|
|
||||||
}*/
|
|
||||||
} 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");
|
|
||||||
LinkedList<Integer> ids = db.getIdsFromQuery("SELECT id FROM messages WHERE has_media=1 AND api_layer<" + Kotlogram.API_LAYER);
|
|
||||||
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, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedList<TLMessage> messages = this.db.getMessagesWithMedia();
|
|
||||||
logger.debug("Database returned {} messages with media", messages.size());
|
|
||||||
prog.onMediaDownloadStart(messages.size());
|
|
||||||
for (TLMessage msg : messages) {
|
|
||||||
AbstractMediaFileManager m = FileManagerFactory.getFileManager(msg, user, client);
|
|
||||||
logger.trace("message {}, {}, {}, {}, {}",
|
|
||||||
msg.getId(),
|
|
||||||
msg.getMedia().getClass().getSimpleName().replace("TLMessageMedia", "…"),
|
|
||||||
m.getClass().getSimpleName(),
|
|
||||||
m.isEmpty() ? "empty" : "non-empty",
|
|
||||||
m.isDownloaded() ? "downloaded" : "not downloaded");
|
|
||||||
if (m.isEmpty()) {
|
|
||||||
prog.onMediaDownloadedEmpty();
|
|
||||||
} else if (m.isDownloaded()) {
|
|
||||||
prog.onMediaAlreadyPresent(m);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
m.download();
|
|
||||||
prog.onMediaDownloaded(m);
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
// do nothing - skip this file
|
|
||||||
prog.onMediaSkipped();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prog.onMediaDownloadFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Integer> makeIdList(int start, int end) {
|
|
||||||
LinkedList<Integer> a = new LinkedList<Integer>();
|
|
||||||
for (int i=start; i<=end; i++) a.add(i);
|
|
||||||
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");
|
|
||||||
offset = (int)new File(temp_filename).length();
|
|
||||||
if (offset >= size) {
|
|
||||||
logger.warn("Temporary file size is >= the target size. Assuming corrupt file & deleting it");
|
|
||||||
new File(temp_filename).delete();
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.trace("offset before the loop is {}", offset);
|
|
||||||
fos = new FileOutputStream(temp_filename, true);
|
|
||||||
TLFile response = null;
|
|
||||||
boolean try_again;
|
|
||||||
do {
|
|
||||||
try_again = false;
|
|
||||||
int block_size = size;
|
|
||||||
logger.trace("offset: {} block_size: {} size: {}", offset, block_size, size);
|
|
||||||
TLRequestUploadGetFile req = new TLRequestUploadGetFile(loc, offset, block_size);
|
|
||||||
try {
|
|
||||||
if (dcID==null) {
|
|
||||||
response = (TLFile) download_client.executeRpcQuery(req);
|
|
||||||
} else {
|
|
||||||
response = (TLFile) download_client.executeRpcQuery(req, dcID);
|
|
||||||
}
|
|
||||||
} catch (RpcErrorException e) {
|
|
||||||
if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
|
|
||||||
try_again = true;
|
|
||||||
Utils.obeyFloodWaitException(e);
|
|
||||||
} else if (e.getCode()==400) {
|
|
||||||
//Somehow this file is broken. No idea why. Let's skip it for now
|
|
||||||
try_again = true;
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
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) {}
|
|
||||||
} while(offset < size && (response.getBytes().getData().length>0 || try_again));
|
|
||||||
fos.close();
|
|
||||||
if (offset < size) {
|
|
||||||
System.out.println("Requested file " + target + " with " + size + " bytes, but got only " + offset + " bytes.");
|
|
||||||
new File(temp_filename).delete();
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
logger.trace("Renaming {} to {}", temp_filename, target);
|
|
||||||
int rename_tries = 0;
|
|
||||||
IOException last_exception = null;
|
|
||||||
while (rename_tries <= Config.RENAMING_MAX_TRIES) {
|
|
||||||
rename_tries++;
|
|
||||||
try {
|
|
||||||
Files.move(new File(temp_filename).toPath(), new File(target).toPath(), StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
last_exception = null;
|
|
||||||
break;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Exception during move. rename_tries: {}. Exception: {}", rename_tries, e);
|
|
||||||
last_exception = e;
|
|
||||||
try { TimeUnit.MILLISECONDS.sleep(Config.RENAMING_DELAY); } catch (InterruptedException e2) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (last_exception != null) {
|
|
||||||
throw last_exception;
|
|
||||||
}
|
|
||||||
last_download_succeeded = true;
|
|
||||||
return true;
|
|
||||||
} catch (java.io.IOException ex) {
|
|
||||||
if (fos!=null) fos.close();
|
|
||||||
System.out.println("IOException happened while downloading " + target);
|
|
||||||
throw ex;
|
|
||||||
} catch (RpcErrorException ex) {
|
|
||||||
if (fos!=null) fos.close();
|
|
||||||
if (ex.getCode()==500) {
|
|
||||||
if (!last_download_succeeded) {
|
|
||||||
System.out.println("Got an Internal Server Error from Telegram. Since the file downloaded before also happened to get this error, we will stop downloading now. Please try again later.");
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
last_download_succeeded = false;
|
|
||||||
System.out.println("Got an Internal Server Error from Telegram. Skipping this file for now. Next run of telegram_backup will continue to download this file.");
|
|
||||||
logger.warn(ex.toString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
System.out.println("RpcErrorException happened while downloading " + target);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean downloadExternalFile(String target, String url) throws IOException {
|
|
||||||
FileUtils.copyURLToFile(new URL(url), new File(target), 5000, 5000);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,101 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.event.ListSelectionEvent;
|
|
||||||
import javax.swing.event.ListSelectionListener;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
public class GUIController {
|
|
||||||
public GUIController() {
|
|
||||||
showAccountChooserDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showAccountChooserDialog() {
|
|
||||||
final JDialog accountChooser = new JDialog();
|
|
||||||
accountChooser.setTitle("Choose account");
|
|
||||||
accountChooser.setSize(400, 200);
|
|
||||||
JPanel vert = new JPanel();
|
|
||||||
vert.setLayout(new BorderLayout());
|
|
||||||
vert.add(new JLabel("Please select the account to use or create a new one."), BorderLayout.NORTH);
|
|
||||||
Vector<String> accounts = Utils.getAccounts();
|
|
||||||
JList<String> list = new JList<String>(accounts);
|
|
||||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
||||||
vert.add(list, BorderLayout.CENTER);
|
|
||||||
JPanel bottom = new JPanel(new GridLayout(1, 2));
|
|
||||||
JButton btnAddAccount = new JButton("Add account");
|
|
||||||
bottom.add(btnAddAccount);
|
|
||||||
final JButton btnLogin = new JButton("Login");
|
|
||||||
btnLogin.setEnabled(false);
|
|
||||||
bottom.add(btnLogin);
|
|
||||||
vert.add(bottom, BorderLayout.SOUTH);
|
|
||||||
accountChooser.add(vert);
|
|
||||||
accountChooser.setVisible(true);
|
|
||||||
accountChooser.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
|
||||||
|
|
||||||
list.addListSelectionListener(new ListSelectionListener() {
|
|
||||||
@Override
|
|
||||||
public void valueChanged(ListSelectionEvent e) {
|
|
||||||
btnLogin.setEnabled(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnAddAccount.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
accountChooser.setVisible(false);
|
|
||||||
accountChooser.dispose();
|
|
||||||
addAccountDialog();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAccountDialog() {
|
|
||||||
JDialog loginDialog = new JDialog();
|
|
||||||
loginDialog.setTitle("Add an account");
|
|
||||||
loginDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
|
||||||
|
|
||||||
JPanel sections = new JPanel();
|
|
||||||
sections.setLayout(new BoxLayout(sections, BoxLayout.Y_AXIS));
|
|
||||||
|
|
||||||
JPanel top = new JPanel();
|
|
||||||
top.setLayout(new BoxLayout(top, BoxLayout.Y_AXIS));
|
|
||||||
top.add(new JLabel("Please enter your phone number in international format:"));
|
|
||||||
top.add(new JTextField("+49123773212"));
|
|
||||||
|
|
||||||
sections.add(top);
|
|
||||||
sections.add(Box.createVerticalStrut(5));
|
|
||||||
sections.add(new JSeparator(SwingConstants.HORIZONTAL));
|
|
||||||
|
|
||||||
JPanel middle = new JPanel();
|
|
||||||
middle.setLayout(new BoxLayout(middle, BoxLayout.Y_AXIS));
|
|
||||||
middle.add(new JLabel("Telegram sent you a code. Enter it here:"));
|
|
||||||
middle.add(new JTextField());
|
|
||||||
middle.setEnabled(false);
|
|
||||||
|
|
||||||
sections.add(middle);
|
|
||||||
sections.add(Box.createVerticalStrut(5));
|
|
||||||
sections.add(new JSeparator(SwingConstants.HORIZONTAL));
|
|
||||||
|
|
||||||
loginDialog.add(sections);
|
|
||||||
loginDialog.setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import java.lang.StringBuilder;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class StickerConverter {
|
|
||||||
public static String makeFilenameWithPath(TLDocumentAttributeSticker attr) {
|
|
||||||
StringBuilder file = new StringBuilder();
|
|
||||||
file.append(makePath());
|
|
||||||
file.append(makeFilename(attr));
|
|
||||||
return file.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String makeFilename(TLDocumentAttributeSticker attr) {
|
|
||||||
StringBuilder file = new StringBuilder();
|
|
||||||
if (attr.getStickerset() instanceof TLInputStickerSetShortName) {
|
|
||||||
file.append(((TLInputStickerSetShortName)attr.getStickerset()).getShortName());
|
|
||||||
} else if (attr.getStickerset() instanceof TLInputStickerSetID) {
|
|
||||||
file.append(((TLInputStickerSetID)attr.getStickerset()).getId());
|
|
||||||
}
|
|
||||||
file.append("_");
|
|
||||||
file.append(attr.getAlt().hashCode());
|
|
||||||
file.append(".webp");
|
|
||||||
return file.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String makePath() {
|
|
||||||
String path = Config.FILE_BASE +
|
|
||||||
File.separatorChar +
|
|
||||||
Config.FILE_STICKER_BASE +
|
|
||||||
File.separatorChar;
|
|
||||||
new File(path).mkdirs();
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.tl.TLContext;
|
|
||||||
import com.github.badoualy.telegram.tl.api.account.TLPassword;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLMethod;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import static com.github.badoualy.telegram.tl.StreamUtils.readTLObject;
|
|
||||||
|
|
||||||
public class TLRequestAccountGetPasswordWithCurrentSalt extends TLMethod<TLPassword> {
|
|
||||||
public static final int CONSTRUCTOR_ID = 0x548a30f5;
|
|
||||||
private final String _constructor = "account.getPassword#548a30f5";
|
|
||||||
public TLRequestAccountGetPasswordWithCurrentSalt() {}
|
|
||||||
public TLPassword deserializeResponse(InputStream stream, TLContext context) throws IOException {
|
|
||||||
final TLObject response = readTLObject(stream, context);
|
|
||||||
if (response == null) {
|
|
||||||
throw new IOException("Unable to parse response");
|
|
||||||
}
|
|
||||||
if (!(response instanceof TLPassword)) {
|
|
||||||
throw new IOException("Incorrect response type, expected getClass().getCanonicalName(), found response.getClass().getCanonicalName()");
|
|
||||||
}
|
|
||||||
return (TLPassword) response;
|
|
||||||
}
|
|
||||||
public String toString() { return _constructor; }
|
|
||||||
public int getConstructorId() { return CONSTRUCTOR_ID; }
|
|
||||||
}
|
|
@ -1,155 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.UpdateCallback;
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.api.Kotlogram;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLVector;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory;
|
|
||||||
|
|
||||||
class TelegramUpdateHandler implements UpdateCallback {
|
|
||||||
private UserManager user = null;
|
|
||||||
private Database db = null;
|
|
||||||
public boolean debug = false;
|
|
||||||
|
|
||||||
public void activate() { this.user = UserManager.getInstance(); this.db = Database.getInstance();}
|
|
||||||
|
|
||||||
public void onUpdates(TelegramClient c, TLUpdates u) {
|
|
||||||
if (db==null) return;
|
|
||||||
if (debug) System.out.println("onUpdates - " + u.getUpdates().size() + " Updates, " + u.getUsers().size() + " Users, " + u.getChats().size() + " Chats");
|
|
||||||
for(TLAbsUpdate update : u.getUpdates()) {
|
|
||||||
processUpdate(update, c);
|
|
||||||
if (debug) System.out.println(" " + update.getClass().getName());
|
|
||||||
}
|
|
||||||
db.saveUsers(u.getUsers());
|
|
||||||
db.saveChats(u.getChats());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUpdatesCombined(TelegramClient c, TLUpdatesCombined u) {
|
|
||||||
if (db==null) return;
|
|
||||||
if (debug) System.out.println("onUpdatesCombined");
|
|
||||||
for(TLAbsUpdate update : u.getUpdates()) {
|
|
||||||
processUpdate(update, c);
|
|
||||||
}
|
|
||||||
db.saveUsers(u.getUsers());
|
|
||||||
db.saveChats(u.getChats());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUpdateShort(TelegramClient c, TLUpdateShort u) {
|
|
||||||
if (db==null) return;
|
|
||||||
if (debug) System.out.println("onUpdateShort");
|
|
||||||
processUpdate(u.getUpdate(), c);
|
|
||||||
if (debug) System.out.println(" " + u.getUpdate().getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onShortChatMessage(TelegramClient c, TLUpdateShortChatMessage m) {
|
|
||||||
if (db==null) return;
|
|
||||||
if (debug) System.out.println("onShortChatMessage - " + m.getMessage());
|
|
||||||
TLMessage msg = new TLMessage(
|
|
||||||
m.getOut(),
|
|
||||||
m.getMentioned(),
|
|
||||||
m.getMediaUnread(),
|
|
||||||
m.getSilent(),
|
|
||||||
false,
|
|
||||||
m.getId(),
|
|
||||||
m.getFromId(),
|
|
||||||
new TLPeerChat(m.getChatId()),
|
|
||||||
m.getFwdFrom(),
|
|
||||||
m.getViaBotId(),
|
|
||||||
m.getReplyToMsgId(),
|
|
||||||
m.getDate(),
|
|
||||||
m.getMessage(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
m.getEntities(),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
TLVector<TLAbsMessage> vector = new TLVector<TLAbsMessage>(TLAbsMessage.class);
|
|
||||||
vector.add(msg);
|
|
||||||
db.saveMessages(vector, Kotlogram.API_LAYER);
|
|
||||||
System.out.print('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onShortMessage(TelegramClient c, TLUpdateShortMessage m) {
|
|
||||||
if (db==null) return;
|
|
||||||
if (debug) System.out.println("onShortMessage - " + m.getOut() + " - " + m.getUserId() + " - " + m.getMessage());
|
|
||||||
int from_id, to_id;
|
|
||||||
if (m.getOut()==true) {
|
|
||||||
from_id = user.getUser().getId();
|
|
||||||
to_id = m.getUserId();
|
|
||||||
} else {
|
|
||||||
to_id = user.getUser().getId();
|
|
||||||
from_id = m.getUserId();
|
|
||||||
}
|
|
||||||
TLMessage msg = new TLMessage(
|
|
||||||
m.getOut(),
|
|
||||||
m.getMentioned(),
|
|
||||||
m.getMediaUnread(),
|
|
||||||
m.getSilent(),
|
|
||||||
false,
|
|
||||||
m.getId(),
|
|
||||||
from_id,
|
|
||||||
new TLPeerUser(to_id),
|
|
||||||
m.getFwdFrom(),
|
|
||||||
m.getViaBotId(),
|
|
||||||
m.getReplyToMsgId(),
|
|
||||||
m.getDate(),
|
|
||||||
m.getMessage(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
m.getEntities(),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
TLVector<TLAbsMessage> vector = new TLVector<TLAbsMessage>(TLAbsMessage.class);
|
|
||||||
vector.add(msg);
|
|
||||||
db.saveMessages(vector, Kotlogram.API_LAYER);
|
|
||||||
System.out.print('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onShortSentMessage(TelegramClient c, TLUpdateShortSentMessage m) { if (db==null) return; System.out.println("onShortSentMessage"); }
|
|
||||||
public void onUpdateTooLong(TelegramClient c) { if (db==null) return; System.out.println("onUpdateTooLong"); }
|
|
||||||
|
|
||||||
private void processUpdate(TLAbsUpdate update, TelegramClient client) {
|
|
||||||
if (update instanceof TLUpdateNewMessage) {
|
|
||||||
TLAbsMessage abs_msg = ((TLUpdateNewMessage)update).getMessage();
|
|
||||||
TLVector<TLAbsMessage> vector = new TLVector<TLAbsMessage>(TLAbsMessage.class);
|
|
||||||
vector.add(abs_msg);
|
|
||||||
db.saveMessages(vector, Kotlogram.API_LAYER);
|
|
||||||
System.out.print('.');
|
|
||||||
if (abs_msg instanceof TLMessage) {
|
|
||||||
AbstractMediaFileManager fm = FileManagerFactory.getFileManager((TLMessage)abs_msg, user, client);
|
|
||||||
if (fm != null && !fm.isEmpty() && !fm.isDownloaded()) {
|
|
||||||
try {
|
|
||||||
fm.download();
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("We got an exception while downloading media, but we're going to ignore it.");
|
|
||||||
System.out.println("Here it is anyway:");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ignore everything else...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
class TestFeatures {
|
|
||||||
public static void test1() {
|
|
||||||
// Tests entries in a cache4.db in the current working directory for compatibility
|
|
||||||
try {
|
|
||||||
Class.forName("org.sqlite.JDBC");
|
|
||||||
} catch(ClassNotFoundException e) {
|
|
||||||
CommandLineController.show_error("Could not load jdbc-sqlite class.");
|
|
||||||
}
|
|
||||||
|
|
||||||
String path = "jdbc:sqlite:cache4.db";
|
|
||||||
|
|
||||||
Connection conn = null;
|
|
||||||
Statement stmt = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
conn = DriverManager.getConnection(path);
|
|
||||||
stmt = conn.createStatement();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
CommandLineController.show_error("Could not connect to SQLITE database.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int unsupported_constructor = 0;
|
|
||||||
int success = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
ResultSet rs = stmt.executeQuery("SELECT data FROM messages");
|
|
||||||
while (rs.next()) {
|
|
||||||
try {
|
|
||||||
TLApiContext.getInstance().deserializeMessage(rs.getBytes(1));
|
|
||||||
} catch (com.github.badoualy.telegram.tl.exception.UnsupportedConstructorException e) {
|
|
||||||
unsupported_constructor++;
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.out.println("IOException: " + e);
|
|
||||||
}
|
|
||||||
success++;
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
System.out.println("SQL exception: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("Success: " + success);
|
|
||||||
System.out.println("Unsupported constructor: " + unsupported_constructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void test2(UserManager user, TelegramClient client) {
|
|
||||||
// Prints system.encoding and default charset
|
|
||||||
System.out.println("Default Charset: " + Charset.defaultCharset());
|
|
||||||
System.out.println("file.encoding: " + System.getProperty("file.encoding"));
|
|
||||||
Database db = Database.getInstance();
|
|
||||||
System.out.println("Database encoding: " + db.getEncoding());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,131 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.api.auth.TLSentCode;
|
|
||||||
import com.github.badoualy.telegram.tl.api.auth.TLAuthorization;
|
|
||||||
import com.github.badoualy.telegram.tl.api.TLUser;
|
|
||||||
import com.github.badoualy.telegram.tl.api.TLUserFull;
|
|
||||||
import com.github.badoualy.telegram.tl.api.TLInputUserSelf;
|
|
||||||
import com.github.badoualy.telegram.tl.api.account.TLPassword;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLBytes;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
public class UserManager {
|
|
||||||
public TLUser user = null;
|
|
||||||
public String phone = null;
|
|
||||||
private String code = null;
|
|
||||||
private TelegramClient client = null;
|
|
||||||
private TLSentCode sent_code = null;
|
|
||||||
private TLAuthorization auth = null;
|
|
||||||
private boolean password_needed = false;
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(UserManager.class);
|
|
||||||
private static UserManager instance = null;
|
|
||||||
|
|
||||||
public static void init(TelegramClient c) throws IOException {
|
|
||||||
instance = new UserManager(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserManager(TelegramClient c) throws IOException {
|
|
||||||
this.client = c;
|
|
||||||
logger.debug("Calling getFullUser");
|
|
||||||
try {
|
|
||||||
TLUserFull full_user = this.client.usersGetFullUser(new TLInputUserSelf());
|
|
||||||
this.user = full_user.getUser().getAsUser();
|
|
||||||
} catch (RpcErrorException e) {
|
|
||||||
// This may happen. Ignoring it.
|
|
||||||
logger.debug("Ignoring exception:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UserManager getInstance() {
|
|
||||||
if (instance==null) throw new RuntimeException("UserManager is not yet initialized.");
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLoggedIn() { return user!=null; }
|
|
||||||
|
|
||||||
public void sendCodeToPhoneNumber(String number) throws RpcErrorException, IOException {
|
|
||||||
this.phone = number;
|
|
||||||
this.sent_code = this.client.authSendCode(false, this.phone, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void verifyCode(String code) throws RpcErrorException, IOException {
|
|
||||||
this.code = code;
|
|
||||||
try {
|
|
||||||
this.auth = client.authSignIn(phone, this.sent_code.getPhoneCodeHash(), this.code);
|
|
||||||
this.user = auth.getUser().getAsUser();
|
|
||||||
} catch (RpcErrorException e) {
|
|
||||||
if (e.getCode()!=401 || !e.getTag().equals("SESSION_PASSWORD_NEEDED")) throw e;
|
|
||||||
this.password_needed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPasswordNeeded() { return this.password_needed; }
|
|
||||||
|
|
||||||
public void verifyPassword(String pw) throws RpcErrorException, IOException {
|
|
||||||
byte[] password = pw.getBytes("UTF-8");
|
|
||||||
byte[] salt = ((TLPassword)client.accountGetPassword()).getCurrentSalt().getData();
|
|
||||||
MessageDigest md = null;
|
|
||||||
try {
|
|
||||||
md = MessageDigest.getInstance("SHA-256");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
byte[] salted = new byte[2*salt.length + password.length];
|
|
||||||
System.arraycopy(salt, 0, salted, 0, salt.length);
|
|
||||||
System.arraycopy(password, 0, salted, salt.length, password.length);
|
|
||||||
System.arraycopy(salt, 0, salted, salt.length+password.length, salt.length);
|
|
||||||
byte[] hash = md.digest(salted);
|
|
||||||
auth = client.authCheckPassword(new TLBytes(hash));
|
|
||||||
this.user = auth.getUser().getAsUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserString() {
|
|
||||||
if (this.user==null) return "Not logged in";
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (this.user.getFirstName()!=null) {
|
|
||||||
sb.append(this.user.getFirstName());
|
|
||||||
}
|
|
||||||
if (this.user.getLastName()!=null) {
|
|
||||||
sb.append(" ");
|
|
||||||
sb.append(this.user.getLastName());
|
|
||||||
}
|
|
||||||
if (this.user.getUsername()!=null) {
|
|
||||||
sb.append(" (@");
|
|
||||||
sb.append(this.user.getUsername());
|
|
||||||
sb.append(")");
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TLUser getUser() { return this.user; }
|
|
||||||
|
|
||||||
public String getFileBase() {
|
|
||||||
return Config.FILE_BASE + File.separatorChar + "+" + this.user.getPhone() + File.separatorChar;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import com.google.gson.*;
|
|
||||||
import java.net.URL;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import de.fabianonline.telegram_backup.Version;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class Utils {
|
|
||||||
public static final int VERSIONS_EQUAL = 0;
|
|
||||||
public static final int VERSION_1_NEWER = 1;
|
|
||||||
public static final int VERSION_2_NEWER = 2;
|
|
||||||
|
|
||||||
private static final Logger logger = (Logger)LoggerFactory.getLogger(Utils.class);
|
|
||||||
|
|
||||||
static Vector<String> getAccounts() {
|
|
||||||
Vector<String> accounts = new Vector<String>();
|
|
||||||
File folder = new File(Config.FILE_BASE);
|
|
||||||
File[] files = folder.listFiles();
|
|
||||||
if (files != null) for (File f : files) {
|
|
||||||
if (f.isDirectory() && f.getName().startsWith("+")) {
|
|
||||||
accounts.add(f.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void obeyFloodWaitException(RpcErrorException e) throws RpcErrorException {
|
|
||||||
obeyFloodWaitException(e, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void obeyFloodWaitException(RpcErrorException e, boolean silent) throws RpcErrorException {
|
|
||||||
if (e==null || e.getCode()!=420) return;
|
|
||||||
|
|
||||||
int delay = e.getTagInteger();
|
|
||||||
if(!silent) {
|
|
||||||
System.out.println("");
|
|
||||||
System.out.println(
|
|
||||||
"Telegram complained about us (okay, me) making too many requests in too short time by\n" +
|
|
||||||
"sending us \"" + e.getTag() + "\" as an error. So we now have to wait a bit. Telegram\n" +
|
|
||||||
"asked us to wait for " + delay + " seconds.\n" +
|
|
||||||
"\n" +
|
|
||||||
"So I'm going to do just that for now. If you don't want to wait, you can quit by pressing\n" +
|
|
||||||
"Ctrl+C. You can restart me at any time and I will just continue to download your\n" +
|
|
||||||
"messages and media. But be advised that just restarting me is not going to change\n" +
|
|
||||||
"the fact that Telegram won't talk to me until then.");
|
|
||||||
System.out.println("");
|
|
||||||
}
|
|
||||||
try { TimeUnit.SECONDS.sleep(delay + 1); } catch(InterruptedException e2) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Version getNewestVersion() {
|
|
||||||
try {
|
|
||||||
String data_url = "https://api.github.com/repos/fabianonline/telegram_backup/releases";
|
|
||||||
logger.debug("Requesting current release info from {}", data_url);
|
|
||||||
String json = IOUtils.toString(new URL(data_url));
|
|
||||||
JsonParser parser = new JsonParser();
|
|
||||||
JsonElement root_elm = parser.parse(json);
|
|
||||||
if (root_elm.isJsonArray()) {
|
|
||||||
JsonArray root = root_elm.getAsJsonArray();
|
|
||||||
JsonObject newest_version = null;
|
|
||||||
for (JsonElement e : root) if (e.isJsonObject()) {
|
|
||||||
JsonObject version = e.getAsJsonObject();
|
|
||||||
if (version.getAsJsonPrimitive("prerelease").getAsBoolean() == false) {
|
|
||||||
newest_version = version;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (newest_version == null) return null;
|
|
||||||
String new_v = newest_version.getAsJsonPrimitive("tag_name").getAsString();
|
|
||||||
logger.debug("Found current release version {}", new_v);
|
|
||||||
String cur_v = Config.APP_APPVER;
|
|
||||||
|
|
||||||
int result = compareVersions(cur_v, new_v);
|
|
||||||
|
|
||||||
return new Version(new_v, newest_version.getAsJsonPrimitive("html_url").getAsString(), newest_version.getAsJsonPrimitive("body").getAsString(), result == VERSION_2_NEWER);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch(Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int compareVersions(String v1, String v2) {
|
|
||||||
logger.debug("Comparing versions {} and {}.", v1, v2);
|
|
||||||
if (v1.equals(v2)) return VERSIONS_EQUAL;
|
|
||||||
|
|
||||||
String[] v1_p = v1.split("-", 2);
|
|
||||||
String[] v2_p = v2.split("-", 2);
|
|
||||||
|
|
||||||
logger.trace("Parts to compare without suffixes: {} and {}.", v1_p[0], v2_p[0]);
|
|
||||||
|
|
||||||
String[] v1_p2 = v1_p[0].split("\\.");
|
|
||||||
String[] v2_p2 = v2_p[0].split("\\.");
|
|
||||||
|
|
||||||
logger.trace("Length of the parts without suffixes: {} and {}.", v1_p2.length, v2_p2.length);
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for (i=0; i<v1_p2.length && i<v2_p2.length; i++) {
|
|
||||||
int i_1 = Integer.parseInt(v1_p2[i]);
|
|
||||||
int i_2 = Integer.parseInt(v2_p2[i]);
|
|
||||||
logger.trace("Comparing parts: {} and {}.", i_1, i_2);
|
|
||||||
if (i_1 > i_2) {
|
|
||||||
logger.debug("v1 is newer");
|
|
||||||
return VERSION_1_NEWER;
|
|
||||||
} else if (i_2 > i_1) {
|
|
||||||
logger.debug("v2 is newer");
|
|
||||||
return VERSION_2_NEWER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.trace("At least one of the versions has run out of parts.");
|
|
||||||
if (v1_p2.length > v2_p2.length) {
|
|
||||||
logger.debug("v1 is longer, so it is newer");
|
|
||||||
return VERSION_1_NEWER;
|
|
||||||
} else if (v2_p2.length > v1_p2.length) {
|
|
||||||
logger.debug("v2 is longer, so it is newer");
|
|
||||||
return VERSION_2_NEWER;
|
|
||||||
}
|
|
||||||
|
|
||||||
// startsWith
|
|
||||||
if (v1_p.length>1 && v2_p.length==1) {
|
|
||||||
logger.debug("v1 has a suffix, v2 not.");
|
|
||||||
if (v1_p[1].startsWith("pre")) {
|
|
||||||
logger.debug("v1 is a pre version, so v1 is newer");
|
|
||||||
return VERSION_2_NEWER;
|
|
||||||
} else {
|
|
||||||
return VERSION_1_NEWER;
|
|
||||||
}
|
|
||||||
} else if (v1_p.length==1 && v2_p.length>1) {
|
|
||||||
logger.debug("v1 has no suffix, but v2 has");
|
|
||||||
if (v2_p[1].startsWith("pre")) {
|
|
||||||
logger.debug("v2 is a pre version, so v1 is better");
|
|
||||||
return VERSION_1_NEWER;
|
|
||||||
} else {
|
|
||||||
return VERSION_2_NEWER;
|
|
||||||
}
|
|
||||||
} else if (v1_p.length>1 && v2_p.length>1) {
|
|
||||||
logger.debug("Both have a suffix");
|
|
||||||
if (v1_p[1].startsWith("pre") && !v2_p[1].startsWith("pre")) {
|
|
||||||
logger.debug("v1 is a 'pre' version, v2 not.");
|
|
||||||
return VERSION_2_NEWER;
|
|
||||||
} else if (!v1_p[1].startsWith("pre") && v2_p[1].startsWith("pre")) {
|
|
||||||
logger.debug("v2 is a 'pre' version, v2 not.");
|
|
||||||
return VERSION_1_NEWER;
|
|
||||||
}
|
|
||||||
return VERSIONS_EQUAL;
|
|
||||||
}
|
|
||||||
logger.debug("We couldn't find a real difference, so we're assuming the versions are equal-ish.");
|
|
||||||
return VERSIONS_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String anonymize(String str) {
|
|
||||||
if (!CommandLineOptions.cmd_anonymize) return str;
|
|
||||||
return str.replaceAll("[0-9]", "1").replaceAll("[A-Z]", "A").replaceAll("[a-z]", "a") + " (ANONYMIZED)";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,182 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.exporter;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.Utils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
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() 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<Database.Dialog> dialogs = db.getListOfDialogsForExport();
|
|
||||||
logger.trace("Got {} dialogs", dialogs.size());
|
|
||||||
logger.debug("Fetching chats");
|
|
||||||
LinkedList<Database.Chat> chats = db.getListOfChatsForExport();
|
|
||||||
logger.trace("Got {} chats", chats.size());
|
|
||||||
|
|
||||||
logger.debug("Generating index.html");
|
|
||||||
HashMap<String, Object> scope = new HashMap<String, Object>();
|
|
||||||
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) {
|
|
||||||
i++;
|
|
||||||
logger.trace("Dialog {}/{}: {}", i, dialogs.size(), Utils.anonymize(""+d.id));
|
|
||||||
LinkedList<HashMap<String, Object>> messages = db.getMessagesForExport(d);
|
|
||||||
scope.clear();
|
|
||||||
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) {
|
|
||||||
i++;
|
|
||||||
logger.trace("Chat {}/{}: {}", i, chats.size(), Utils.anonymize(""+c.id));
|
|
||||||
LinkedList<HashMap<String, Object>> messages = db.getMessagesForExport(c);
|
|
||||||
scope.clear();
|
|
||||||
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");
|
|
||||||
File dest = new File(base + "style.css");
|
|
||||||
FileUtils.copyURLToFile(cssFile, dest);
|
|
||||||
logger.debug("Done exporting.");
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("Caught an exception!", e);
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String intArrayToString(int[][] data) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("[");
|
|
||||||
for (int x=0; x<data.length; x++) {
|
|
||||||
for (int y=0; y<data[x].length; y++) {
|
|
||||||
if (x>0 || y>0) sb.append(",");
|
|
||||||
sb.append("[" + x + "," + y + "," + data[x][y] + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.append("]");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String mapToString(Map<String, Integer> map) {
|
|
||||||
StringBuilder sb = new StringBuilder("[");
|
|
||||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
|
||||||
sb.append("['" + entry.getKey() + "', " + entry.getValue() + "],");
|
|
||||||
}
|
|
||||||
sb.append("]");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,91 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.mediafilemanager;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.Config;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadManager;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public abstract class AbstractMediaFileManager {
|
|
||||||
protected UserManager user;
|
|
||||||
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();
|
|
||||||
public boolean isEmpty() { return isEmpty; }
|
|
||||||
public boolean isDownloaded() { return new File(getTargetPathAndFilename()).isFile(); }
|
|
||||||
public boolean isDownloading() { return new File(getTargetPathAndFilename() + ".downloading").isFile(); }
|
|
||||||
public abstract void download() throws RpcErrorException, IOException, TimeoutException;
|
|
||||||
public static void throwUnexpectedObjectError(Object o) {
|
|
||||||
throw new RuntimeException("Unexpected " + o.getClass().getName());
|
|
||||||
}
|
|
||||||
public String getTargetPath() {
|
|
||||||
String path = user.getFileBase() + Config.FILE_FILES_BASE + File.separatorChar;
|
|
||||||
new File(path).mkdirs();
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
@ -1,112 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.mediafilemanager;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadManager;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public class DocumentFileManager extends AbstractMediaFileManager {
|
|
||||||
protected TLDocument doc;
|
|
||||||
private String extension = null;
|
|
||||||
|
|
||||||
public DocumentFileManager(TLMessage msg, UserManager user, TelegramClient client) {
|
|
||||||
super(msg, user, client);
|
|
||||||
TLAbsDocument d = ((TLMessageMediaDocument)msg.getMedia()).getDocument();
|
|
||||||
if (d instanceof TLDocument) {
|
|
||||||
this.doc = (TLDocument)d;
|
|
||||||
} else if (d instanceof TLDocumentEmpty) {
|
|
||||||
this.isEmpty = true;
|
|
||||||
} else {
|
|
||||||
throwUnexpectedObjectError(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSticker() {
|
|
||||||
TLDocumentAttributeSticker sticker = null;
|
|
||||||
if (this.isEmpty || doc==null) return false;
|
|
||||||
if (doc.getAttributes() != null) for(TLAbsDocumentAttribute attr : doc.getAttributes()) {
|
|
||||||
if (attr instanceof TLDocumentAttributeSticker) {
|
|
||||||
sticker = (TLDocumentAttributeSticker)attr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sticker!=null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize() {
|
|
||||||
if (doc != null) return doc.getSize();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExtension() {
|
|
||||||
if (extension != null) return extension;
|
|
||||||
if (doc == null) return "empty";
|
|
||||||
String ext = null;
|
|
||||||
String original_filename = null;
|
|
||||||
if (doc.getAttributes() != null) for(TLAbsDocumentAttribute attr : doc.getAttributes()) {
|
|
||||||
if (attr instanceof TLDocumentAttributeFilename) {
|
|
||||||
original_filename = ((TLDocumentAttributeFilename)attr).getFileName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (original_filename != null) {
|
|
||||||
int i = original_filename.lastIndexOf('.');
|
|
||||||
if (i>0) ext = original_filename.substring(i+1);
|
|
||||||
|
|
||||||
}
|
|
||||||
if (ext==null) {
|
|
||||||
ext = extensionFromMimetype(doc.getMimeType());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sometimes, extensions contain a trailing double quote. Remove this. Fixes #12.
|
|
||||||
ext = ext.replace("\"", "");
|
|
||||||
|
|
||||||
this.extension = ext;
|
|
||||||
return ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void download() throws RpcErrorException, IOException, TimeoutException {
|
|
||||||
if (doc!=null) {
|
|
||||||
DownloadManager.downloadFile(client, getTargetPathAndFilename(), getSize(), doc.getDcId(), doc.getId(), doc.getAccessHash());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLetter() { return "d"; }
|
|
||||||
public String getName() { return "document"; }
|
|
||||||
public String getDescription() { return "Document"; }
|
|
||||||
}
|
|
@ -1,75 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.mediafilemanager;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public class FileManagerFactory {
|
|
||||||
public static AbstractMediaFileManager getFileManager(TLMessage m, UserManager u, TelegramClient c) {
|
|
||||||
if (m==null) return null;
|
|
||||||
TLAbsMessageMedia media = m.getMedia();
|
|
||||||
if (media==null) return null;
|
|
||||||
|
|
||||||
if (media instanceof TLMessageMediaPhoto) {
|
|
||||||
return new PhotoFileManager(m, u, c);
|
|
||||||
} else if (media instanceof TLMessageMediaDocument) {
|
|
||||||
DocumentFileManager d = new DocumentFileManager(m, u, c);
|
|
||||||
if (d.isSticker()) {
|
|
||||||
return new StickerFileManager(m, u, c);
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
} else if (media instanceof TLMessageMediaGeo) {
|
|
||||||
return new GeoFileManager(m, u, c);
|
|
||||||
} else if (media instanceof TLMessageMediaEmpty) {
|
|
||||||
return new UnsupportedFileManager(m, u, c, "empty");
|
|
||||||
} else if (media instanceof TLMessageMediaUnsupported) {
|
|
||||||
return new UnsupportedFileManager(m, u, c, "unsupported");
|
|
||||||
} else if (media instanceof TLMessageMediaWebPage) {
|
|
||||||
return new UnsupportedFileManager(m, u, c, "webpage");
|
|
||||||
} else if (media instanceof TLMessageMediaContact) {
|
|
||||||
return new UnsupportedFileManager(m, u, c, "contact");
|
|
||||||
} else if (media instanceof TLMessageMediaVenue) {
|
|
||||||
return new UnsupportedFileManager(m, u, c, "venue");
|
|
||||||
} else {
|
|
||||||
AbstractMediaFileManager.throwUnexpectedObjectError(media);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.mediafilemanager;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadManager;
|
|
||||||
import de.fabianonline.telegram_backup.Config;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public class GeoFileManager extends AbstractMediaFileManager {
|
|
||||||
protected TLGeoPoint geo;
|
|
||||||
|
|
||||||
public GeoFileManager(TLMessage msg, UserManager user, TelegramClient client) {
|
|
||||||
super(msg, user, client);
|
|
||||||
TLAbsGeoPoint g = ((TLMessageMediaGeo)msg.getMedia()).getGeo();
|
|
||||||
if (g instanceof TLGeoPoint) {
|
|
||||||
this.geo = (TLGeoPoint) g;
|
|
||||||
} else if (g instanceof TLGeoPointEmpty) {
|
|
||||||
this.isEmpty = true;
|
|
||||||
} else {
|
|
||||||
throwUnexpectedObjectError(g);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize() {
|
|
||||||
File f = new File(getTargetPathAndFilename());
|
|
||||||
if (f.isFile()) return (int)f.length();
|
|
||||||
|
|
||||||
// We don't know the size, so we just guess.
|
|
||||||
return 100000;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExtension() { return "png"; }
|
|
||||||
|
|
||||||
public void download() throws IOException {
|
|
||||||
String url = "https://maps.googleapis.com/maps/api/staticmap?" +
|
|
||||||
"center=" + geo.getLat() + "," + geo.getLong() + "&" +
|
|
||||||
"zoom=14&size=300x150&scale=2&format=png&" +
|
|
||||||
"key=" + Config.SECRET_GMAPS;
|
|
||||||
DownloadManager.downloadExternalFile(getTargetPathAndFilename(), url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLetter() { return "g"; }
|
|
||||||
public String getName() { return "geo"; }
|
|
||||||
public String getDescription() { return "Geolocation"; }
|
|
||||||
}
|
|
@ -1,88 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.mediafilemanager;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadManager;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public class PhotoFileManager extends AbstractMediaFileManager {
|
|
||||||
private TLPhoto photo;
|
|
||||||
private TLPhotoSize size = null;
|
|
||||||
public PhotoFileManager(TLMessage msg, UserManager user, TelegramClient client) {
|
|
||||||
super(msg, user, client);
|
|
||||||
TLAbsPhoto p = ((TLMessageMediaPhoto)msg.getMedia()).getPhoto();
|
|
||||||
if (p instanceof TLPhoto) {
|
|
||||||
this.photo = (TLPhoto)p;
|
|
||||||
|
|
||||||
TLPhotoSize biggest = null;
|
|
||||||
for (TLAbsPhotoSize s : photo.getSizes()) if (s instanceof TLPhotoSize) {
|
|
||||||
TLPhotoSize size = (TLPhotoSize) s;
|
|
||||||
if (biggest == null || (size.getW()>biggest.getW() && size.getH()>biggest.getH())) {
|
|
||||||
biggest = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (biggest==null) {
|
|
||||||
throw new RuntimeException("Could not find a size for a photo.");
|
|
||||||
}
|
|
||||||
this.size = biggest;
|
|
||||||
} else if (p instanceof TLPhotoEmpty) {
|
|
||||||
this.isEmpty = true;
|
|
||||||
} else {
|
|
||||||
throwUnexpectedObjectError(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize() {
|
|
||||||
if (size!=null) return size.getSize();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExtension() { return "jpg"; }
|
|
||||||
|
|
||||||
public void download() throws RpcErrorException, IOException, TimeoutException {
|
|
||||||
if (isEmpty) return;
|
|
||||||
TLFileLocation loc = (TLFileLocation) size.getLocation();
|
|
||||||
DownloadManager.downloadFile(client, getTargetPathAndFilename(), getSize(), loc.getDcId(), loc.getVolumeId(), loc.getLocalId(), loc.getSecret());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLetter() { return "p"; }
|
|
||||||
public String getName() { return "photo"; }
|
|
||||||
public String getDescription() { return "Photo"; }
|
|
||||||
}
|
|
@ -1,108 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.mediafilemanager;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadManager;
|
|
||||||
import de.fabianonline.telegram_backup.Config;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public class StickerFileManager extends DocumentFileManager {
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(StickerFileManager.class);
|
|
||||||
|
|
||||||
public StickerFileManager(TLMessage msg, UserManager user, TelegramClient client) {
|
|
||||||
super(msg, user, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSticker() { return true; }
|
|
||||||
|
|
||||||
private String getFilenameBase() {
|
|
||||||
TLDocumentAttributeSticker sticker = null;
|
|
||||||
for(TLAbsDocumentAttribute attr : doc.getAttributes()) {
|
|
||||||
if (attr instanceof TLDocumentAttributeSticker) {
|
|
||||||
sticker = (TLDocumentAttributeSticker)attr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder file = new StringBuilder();
|
|
||||||
if (sticker.getStickerset() instanceof TLInputStickerSetShortName) {
|
|
||||||
file.append(((TLInputStickerSetShortName)sticker.getStickerset()).getShortName());
|
|
||||||
} else if (sticker.getStickerset() instanceof TLInputStickerSetID) {
|
|
||||||
file.append(((TLInputStickerSetID)sticker.getStickerset()).getId());
|
|
||||||
}
|
|
||||||
file.append("_");
|
|
||||||
file.append(sticker.getAlt().hashCode());
|
|
||||||
return file.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTargetFilename() {
|
|
||||||
return getFilenameBase() + "." + getExtension();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTargetPath() {
|
|
||||||
String path = user.getFileBase() + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar;
|
|
||||||
new File(path).mkdirs();
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void download() throws RpcErrorException, IOException, TimeoutException {
|
|
||||||
String old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + getTargetFilename();
|
|
||||||
|
|
||||||
logger.trace("Old filename exists: {}", new File(old_file).exists());
|
|
||||||
|
|
||||||
if (new File(old_file).exists()) {
|
|
||||||
Files.copy(Paths.get(old_file), Paths.get(getTargetPathAndFilename()), StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.download();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExtension() { return "webp"; }
|
|
||||||
|
|
||||||
public String getLetter() { return "s"; }
|
|
||||||
public String getName() { return "sticker"; }
|
|
||||||
public String getDescription() { return "Sticker"; }
|
|
||||||
}
|
|
@ -1,72 +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 <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup.mediafilemanager;
|
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.UserManager;
|
|
||||||
import de.fabianonline.telegram_backup.Database;
|
|
||||||
import de.fabianonline.telegram_backup.StickerConverter;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadProgressInterface;
|
|
||||||
import de.fabianonline.telegram_backup.DownloadManager;
|
|
||||||
import de.fabianonline.telegram_backup.Config;
|
|
||||||
|
|
||||||
import com.github.badoualy.telegram.api.TelegramClient;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLIntVector;
|
|
||||||
import com.github.badoualy.telegram.tl.core.TLObject;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages;
|
|
||||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs;
|
|
||||||
import com.github.badoualy.telegram.tl.api.*;
|
|
||||||
import com.github.badoualy.telegram.tl.api.upload.TLFile;
|
|
||||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException;
|
|
||||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
public class UnsupportedFileManager extends AbstractMediaFileManager {
|
|
||||||
String type = null;
|
|
||||||
public UnsupportedFileManager(TLMessage msg, UserManager user, TelegramClient client, String type) {
|
|
||||||
super(msg, user, client);
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTargetFilename() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTargetPath() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExtension() { return ""; }
|
|
||||||
|
|
||||||
public int getSize() { return 0; }
|
|
||||||
|
|
||||||
public boolean isEmpty() { return false; }
|
|
||||||
public void download() {}
|
|
||||||
public boolean isDownloaded() { return false; }
|
|
||||||
|
|
||||||
public String getLetter() { return " "; }
|
|
||||||
public String getName() { return type; }
|
|
||||||
public String getDescription() { return "Unsupported / non-downloadable Media"; }
|
|
||||||
}
|
|
147
src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt
Normal file
147
src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramApiStorage
|
||||||
|
import com.github.badoualy.telegram.mtproto.model.DataCenter
|
||||||
|
import com.github.badoualy.telegram.mtproto.auth.AuthKey
|
||||||
|
import com.github.badoualy.telegram.mtproto.model.MTSession
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
internal class ApiStorage(prefix: String) : TelegramApiStorage {
|
||||||
|
private var prefix: String? = null
|
||||||
|
private var do_save = false
|
||||||
|
private var auth_key: AuthKey? = null
|
||||||
|
private var dc: DataCenter? = null
|
||||||
|
private var file_auth_key: File? = null
|
||||||
|
private var file_dc: File? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.setPrefix(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrefix(prefix: String) {
|
||||||
|
this.prefix = prefix
|
||||||
|
this.do_save = this.prefix != null
|
||||||
|
if (this.do_save) {
|
||||||
|
val base = Config.FILE_BASE +
|
||||||
|
File.separatorChar +
|
||||||
|
this.prefix +
|
||||||
|
File.separatorChar
|
||||||
|
this.file_auth_key = File(base + Config.FILE_NAME_AUTH_KEY)
|
||||||
|
this.file_dc = File(base + Config.FILE_NAME_DC)
|
||||||
|
this._saveAuthKey()
|
||||||
|
this._saveDc()
|
||||||
|
} else {
|
||||||
|
this.file_auth_key = null
|
||||||
|
this.file_dc = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAuthKey(authKey: AuthKey) {
|
||||||
|
this.auth_key = authKey
|
||||||
|
this._saveAuthKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun _saveAuthKey() {
|
||||||
|
if (this.do_save && this.auth_key != null) {
|
||||||
|
try {
|
||||||
|
FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key!!.getKey())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAuthKey(): AuthKey? {
|
||||||
|
if (this.auth_key != null) return this.auth_key
|
||||||
|
if (this.file_auth_key != null) {
|
||||||
|
try {
|
||||||
|
return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
if (e !is FileNotFoundException) e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDc(dc: DataCenter) {
|
||||||
|
this.dc = dc
|
||||||
|
this._saveDc()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun _saveDc() {
|
||||||
|
if (this.do_save && this.dc != null) {
|
||||||
|
try {
|
||||||
|
FileUtils.write(this.file_dc, this.dc!!.toString())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadDc(): DataCenter? {
|
||||||
|
if (this.dc != null) return this.dc
|
||||||
|
if (this.file_dc != null) {
|
||||||
|
try {
|
||||||
|
val infos = FileUtils.readFileToString(this.file_dc).split(":")
|
||||||
|
return DataCenter(infos[0], Integer.parseInt(infos[1]))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
if (e !is FileNotFoundException) e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteAuthKey() {
|
||||||
|
if (this.do_save) {
|
||||||
|
try {
|
||||||
|
FileUtils.forceDelete(this.file_auth_key)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteDc() {
|
||||||
|
if (this.do_save) {
|
||||||
|
try {
|
||||||
|
FileUtils.forceDelete(this.file_dc)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveSession(session: MTSession) {}
|
||||||
|
|
||||||
|
fun loadSession(): MTSession? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,87 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||||
|
|
||||||
|
internal class CommandLineDownloadProgress : DownloadProgressInterface {
|
||||||
|
private var mediaCount = 0
|
||||||
|
private var i = 0
|
||||||
|
|
||||||
|
fun onMessageDownloadStart(count: Int, source: String?) {
|
||||||
|
i = 0
|
||||||
|
if (source == null) {
|
||||||
|
System.out.println("Downloading $count messages.")
|
||||||
|
} else {
|
||||||
|
System.out.println("Downloading " + count + " messages from " + Utils.anonymize(source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageDownloaded(number: Int) {
|
||||||
|
i += number
|
||||||
|
System.out.print("..." + i)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageDownloadFinished() {
|
||||||
|
System.out.println(" done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMediaDownloadStart(count: Int) {
|
||||||
|
i = 0
|
||||||
|
mediaCount = count
|
||||||
|
System.out.println("Checking and downloading media.")
|
||||||
|
System.out.println("Legend:")
|
||||||
|
System.out.println("'V' - Video 'P' - Photo 'D' - Document")
|
||||||
|
System.out.println("'S' - Sticker 'A' - Audio 'G' - Geolocation")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMediaDownloaded(fm: AbstractMediaFileManager) {
|
||||||
|
show(fm.getLetter().toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMediaDownloadedEmpty() {
|
||||||
|
show("e")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMediaAlreadyPresent(fm: AbstractMediaFileManager) {
|
||||||
|
show(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMediaSkipped() {
|
||||||
|
show("x")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMediaDownloadFinished() {
|
||||||
|
showNewLine()
|
||||||
|
System.out.println("Done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun show(letter: String) {
|
||||||
|
System.out.print(letter)
|
||||||
|
i++
|
||||||
|
if (i % 100 == 0) showNewLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNewLine() {
|
||||||
|
System.out.println(" - $i/$mediaCount")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.CommandLineController
|
||||||
|
import de.fabianonline.telegram_backup.Utils
|
||||||
|
import de.fabianonline.telegram_backup.Version
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import ch.qos.logback.classic.Logger
|
||||||
|
import ch.qos.logback.classic.LoggerContext
|
||||||
|
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||||
|
import ch.qos.logback.core.ConsoleAppender
|
||||||
|
import ch.qos.logback.classic.Level
|
||||||
|
|
||||||
|
object CommandLineRunner {
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
CommandLineOptions.parseOptions(args)
|
||||||
|
|
||||||
|
setupLogging()
|
||||||
|
checkVersion()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (true || CommandLineOptions.cmd_console) {
|
||||||
|
// Always use the console for now.
|
||||||
|
CommandLineController()
|
||||||
|
} else {
|
||||||
|
GUIController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setupLogging() {
|
||||||
|
val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger
|
||||||
|
val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger
|
||||||
|
val rootContext = rootLogger.getLoggerContext()
|
||||||
|
rootContext.reset()
|
||||||
|
|
||||||
|
val encoder = PatternLayoutEncoder()
|
||||||
|
encoder.setContext(rootContext)
|
||||||
|
encoder.setPattern("%d{HH:mm:ss.SSS} %-5level %-35.-35(%logger{0}.%method): %message%n")
|
||||||
|
encoder.start()
|
||||||
|
|
||||||
|
val appender = ConsoleAppender<ILoggingEvent>()
|
||||||
|
appender.setContext(rootContext)
|
||||||
|
appender.setEncoder(encoder)
|
||||||
|
appender.start()
|
||||||
|
|
||||||
|
rootLogger.addAppender(appender)
|
||||||
|
rootLogger.setLevel(Level.OFF)
|
||||||
|
|
||||||
|
if (CommandLineOptions.cmd_trace) {
|
||||||
|
(LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.TRACE)
|
||||||
|
} else if (CommandLineOptions.cmd_debug) {
|
||||||
|
(LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CommandLineOptions.cmd_trace_telegram) {
|
||||||
|
(LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkVersion(): Boolean {
|
||||||
|
val v = Utils.getNewestVersion()
|
||||||
|
if (v != null && v!!.isNewer) {
|
||||||
|
System.out.println("A newer version is vailable!")
|
||||||
|
System.out.println("You are using: " + Config.APP_APPVER)
|
||||||
|
System.out.println("Available: " + v!!.version)
|
||||||
|
System.out.println("Get it here: " + v!!.url)
|
||||||
|
System.out.println()
|
||||||
|
System.out.println("Changes in this version:")
|
||||||
|
System.out.println(v!!.body)
|
||||||
|
System.out.println()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
60
src/main/kotlin/de/fabianonline/telegram_backup/Config.kt
Normal file
60
src/main/kotlin/de/fabianonline/telegram_backup/Config.kt
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
|
object Config {
|
||||||
|
val APP_ID = 32860
|
||||||
|
val APP_HASH = "16e4ff955cd0adfc058f95ca564f562d"
|
||||||
|
val APP_MODEL = "Desktop"
|
||||||
|
val APP_SYSVER = "1.0"
|
||||||
|
val APP_APPVER: String
|
||||||
|
val APP_LANG = "en"
|
||||||
|
|
||||||
|
var FILE_BASE = System.getProperty("user.home") + File.separatorChar + ".telegram_backup"
|
||||||
|
val FILE_NAME_AUTH_KEY = "auth.dat"
|
||||||
|
val FILE_NAME_DC = "dc.dat"
|
||||||
|
val FILE_NAME_DB = "database.sqlite"
|
||||||
|
val FILE_NAME_DB_BACKUP = "database.version_%d.backup.sqlite"
|
||||||
|
val FILE_FILES_BASE = "files"
|
||||||
|
val FILE_STICKER_BASE = "stickers"
|
||||||
|
|
||||||
|
var DELAY_AFTER_GET_MESSAGES = 400
|
||||||
|
var DELAY_AFTER_GET_FILE = 100
|
||||||
|
var GET_MESSAGES_BATCH_SIZE = 200
|
||||||
|
|
||||||
|
var RENAMING_MAX_TRIES = 5
|
||||||
|
var RENAMING_DELAY = 1000
|
||||||
|
|
||||||
|
val SECRET_GMAPS = "AIzaSyBEtUDhCQKEH6i2Mn1GAiQ9M_tLN0vxHIs"
|
||||||
|
|
||||||
|
init {
|
||||||
|
val p = Properties()
|
||||||
|
try {
|
||||||
|
p.load(Config::class.java!!.getResourceAsStream("/build.properties"))
|
||||||
|
APP_APPVER = p.getProperty("version")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
739
src/main/kotlin/de/fabianonline/telegram_backup/Database.kt
Normal file
739
src/main/kotlin/de/fabianonline/telegram_backup/Database.kt
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLVector
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.Logger
|
||||||
|
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.sql.DriverManager
|
||||||
|
import java.sql.Statement
|
||||||
|
import java.sql.SQLException
|
||||||
|
import java.sql.ResultSet
|
||||||
|
import java.sql.ResultSetMetaData
|
||||||
|
import java.sql.PreparedStatement
|
||||||
|
import java.sql.Types
|
||||||
|
import java.sql.Time
|
||||||
|
import java.io.File
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.LinkedHashMap
|
||||||
|
import java.util.HashMap
|
||||||
|
import java.util.Date
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.nio.file.FileAlreadyExistsException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||||
|
|
||||||
|
class Database private constructor(var client: TelegramClient) {
|
||||||
|
private var conn: Connection? = null
|
||||||
|
private var stmt: Statement? = null
|
||||||
|
var user_manager: UserManager
|
||||||
|
|
||||||
|
val topMessageID: Int
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog')")
|
||||||
|
rs.next()
|
||||||
|
return rs.getInt(1)
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val messageCount: Int
|
||||||
|
get() = queryInt("SELECT COUNT(*) FROM messages")
|
||||||
|
val chatCount: Int
|
||||||
|
get() = queryInt("SELECT COUNT(*) FROM chats")
|
||||||
|
val userCount: Int
|
||||||
|
get() = queryInt("SELECT COUNT(*) FROM users")
|
||||||
|
|
||||||
|
val missingIDs: LinkedList<Integer>
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val missing = LinkedList<Integer>()
|
||||||
|
val max = topMessageID
|
||||||
|
val rs = stmt!!.executeQuery("SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id")
|
||||||
|
rs.next()
|
||||||
|
var id = rs.getInt(1)
|
||||||
|
for (i in 1..max) {
|
||||||
|
if (i == id) {
|
||||||
|
rs.next()
|
||||||
|
if (rs.isClosed()) {
|
||||||
|
id = Integer.MAX_VALUE
|
||||||
|
} else {
|
||||||
|
id = rs.getInt(1)
|
||||||
|
}
|
||||||
|
} else if (i < id) {
|
||||||
|
missing.add(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missing
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Could not get list of ids.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val messagesWithMedia: LinkedList<TLMessage>
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val list = LinkedList<TLMessage>()
|
||||||
|
val rs = stmt!!.executeQuery("SELECT data FROM messages WHERE has_media=1")
|
||||||
|
while (rs.next()) {
|
||||||
|
list.add(bytesToTLMessage(rs.getBytes(1)))
|
||||||
|
}
|
||||||
|
rs.close()
|
||||||
|
return list
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Exception occured. See above.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val messagesFromUserCount: Int
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT COUNT(*) FROM messages WHERE sender_id=" + user_manager.getUser().getId())
|
||||||
|
rs.next()
|
||||||
|
return rs.getInt(1)
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val messageTypesWithCount: HashMap<String, Integer>
|
||||||
|
get() = getMessageTypesWithCount(GlobalChat())
|
||||||
|
|
||||||
|
val messageMediaTypesWithCount: HashMap<String, Integer>
|
||||||
|
get() = getMessageMediaTypesWithCount(GlobalChat())
|
||||||
|
|
||||||
|
val messageApiLayerWithCount: HashMap<String, Integer>
|
||||||
|
get() {
|
||||||
|
val map = HashMap<String, Integer>()
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer")
|
||||||
|
while (rs.next()) {
|
||||||
|
var layer = rs.getInt(2)
|
||||||
|
if (layer == null) layer = 0
|
||||||
|
map.put("count.messages.api_layer." + layer!!, rs.getInt(1))
|
||||||
|
}
|
||||||
|
rs.close()
|
||||||
|
return map
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val messageAuthorsWithCount: HashMap<String, Object>
|
||||||
|
get() = getMessageAuthorsWithCount(GlobalChat())
|
||||||
|
|
||||||
|
val messageTimesMatrix: Array<IntArray>
|
||||||
|
get() = getMessageTimesMatrix(GlobalChat())
|
||||||
|
|
||||||
|
val encoding: String
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("PRAGMA encoding")
|
||||||
|
rs.next()
|
||||||
|
return rs.getString(1)
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
logger.debug("SQLException: {}", e)
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val listOfChatsForExport: LinkedList<Chat>
|
||||||
|
get() {
|
||||||
|
val list = LinkedList<Chat>()
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c " +
|
||||||
|
"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(Chat(rs.getInt(1), rs.getString(2), rs.getInt(3)))
|
||||||
|
}
|
||||||
|
rs.close()
|
||||||
|
return list
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Exception above!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val listOfDialogsForExport: LinkedList<Dialog>
|
||||||
|
get() {
|
||||||
|
val list = LinkedList<Dialog>()
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery(
|
||||||
|
"SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " +
|
||||||
|
"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(Dialog(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getInt(5)))
|
||||||
|
}
|
||||||
|
rs.close()
|
||||||
|
return list
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Exception above!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.user_manager = UserManager.getInstance()
|
||||||
|
System.out.println("Opening database...")
|
||||||
|
try {
|
||||||
|
Class.forName("org.sqlite.JDBC")
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
CommandLineController.show_error("Could not load jdbc-sqlite class.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = "jdbc:sqlite:" +
|
||||||
|
user_manager.getFileBase() +
|
||||||
|
Config.FILE_NAME_DB
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn = DriverManager.getConnection(path)
|
||||||
|
stmt = conn!!.createStatement()
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
CommandLineController.show_error("Could not connect to SQLITE database.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run updates
|
||||||
|
val updates = DatabaseUpdates(conn, this)
|
||||||
|
updates.doUpdates()
|
||||||
|
|
||||||
|
System.out.println("Database is ready.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun backupDatabase(currentVersion: Int) {
|
||||||
|
val filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion)
|
||||||
|
System.out.println(" Creating a backup of your database as " + filename)
|
||||||
|
try {
|
||||||
|
val src = user_manager.getFileBase() + Config.FILE_NAME_DB
|
||||||
|
val dst = user_manager.getFileBase() + filename
|
||||||
|
logger.debug("Copying {} to {}", src, dst)
|
||||||
|
Files.copy(
|
||||||
|
File(src).toPath(),
|
||||||
|
File(dst).toPath())
|
||||||
|
} catch (e: FileAlreadyExistsException) {
|
||||||
|
logger.warn("Backup already exists:", e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Could not create backup.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTopMessageIDForChannel(id: Int): Int {
|
||||||
|
return queryInt("SELECT MAX(message_id) FROM messages WHERE source_id=$id AND source_type IN('channel', 'supergroup')")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logRun(start_id: Int, end_id: Int, count: Int) {
|
||||||
|
try {
|
||||||
|
val ps = conn!!.prepareStatement("INSERT INTO runs " +
|
||||||
|
"(time, start_id, end_id, count_missing) " +
|
||||||
|
"VALUES " +
|
||||||
|
"(DateTime('now'), ?, ?, ? )")
|
||||||
|
ps.setInt(1, start_id)
|
||||||
|
ps.setInt(2, end_id)
|
||||||
|
ps.setInt(3, count)
|
||||||
|
ps.execute()
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryInt(query: String): Int {
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery(query)
|
||||||
|
rs.next()
|
||||||
|
return rs.getInt(1)
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw RuntimeException("Could not get count of messages.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun saveMessages(all: TLVector<TLAbsMessage>, api_layer: Integer) {
|
||||||
|
try {
|
||||||
|
//"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " +
|
||||||
|
//"VALUES " +
|
||||||
|
//"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
val columns = "(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
|
||||||
|
val ps = conn!!.prepareStatement("INSERT OR REPLACE INTO messages " + columns)
|
||||||
|
val ps_insert_or_ignore = conn!!.prepareStatement("INSERT OR IGNORE INTO messages " + columns)
|
||||||
|
|
||||||
|
for (abs in all) {
|
||||||
|
if (abs is TLMessage) {
|
||||||
|
val msg = abs as TLMessage
|
||||||
|
ps.setInt(1, msg.getId())
|
||||||
|
ps.setString(2, "message")
|
||||||
|
val peer = msg.getToId()
|
||||||
|
if (peer is TLPeerChat) {
|
||||||
|
ps.setString(3, "group")
|
||||||
|
ps.setInt(4, (peer as TLPeerChat).getChatId())
|
||||||
|
} else if (peer is TLPeerUser) {
|
||||||
|
var id = (peer as TLPeerUser).getUserId()
|
||||||
|
if (id == this.user_manager.getUser().getId()) {
|
||||||
|
id = msg.getFromId()
|
||||||
|
}
|
||||||
|
ps.setString(3, "dialog")
|
||||||
|
ps.setInt(4, id)
|
||||||
|
} else if (peer is TLPeerChannel) {
|
||||||
|
ps.setString(3, "channel")
|
||||||
|
ps.setInt(4, (peer as TLPeerChannel).getChannelId())
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("Unexpected Peer type: " + peer.getClass().getName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peer is 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 {
|
||||||
|
ps.setNull(6, Types.INTEGER)
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = msg.getMessage()
|
||||||
|
if ((text == null || text!!.equals("")) && msg.getMedia() != null) {
|
||||||
|
if (msg.getMedia() is TLMessageMediaDocument) {
|
||||||
|
text = (msg.getMedia() as TLMessageMediaDocument).getCaption()
|
||||||
|
} else if (msg.getMedia() is TLMessageMediaPhoto) {
|
||||||
|
text = (msg.getMedia() as TLMessageMediaPhoto).getCaption()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ps.setString(7, text)
|
||||||
|
ps.setString(8, "" + msg.getDate())
|
||||||
|
val f = FileManagerFactory.getFileManager(msg, user_manager, client)
|
||||||
|
if (f == null) {
|
||||||
|
ps.setNull(9, Types.BOOLEAN)
|
||||||
|
ps.setNull(10, Types.VARCHAR)
|
||||||
|
ps.setNull(11, Types.VARCHAR)
|
||||||
|
ps.setNull(12, Types.INTEGER)
|
||||||
|
} else {
|
||||||
|
ps.setBoolean(9, true)
|
||||||
|
ps.setString(10, f!!.getName())
|
||||||
|
ps.setString(11, f!!.getTargetFilename())
|
||||||
|
ps.setInt(12, f!!.getSize())
|
||||||
|
}
|
||||||
|
val stream = ByteArrayOutputStream()
|
||||||
|
msg.serializeBody(stream)
|
||||||
|
ps.setBytes(13, stream.toByteArray())
|
||||||
|
ps.setInt(14, api_layer)
|
||||||
|
ps.addBatch()
|
||||||
|
} else if (abs is TLMessageService) {
|
||||||
|
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||||
|
ps_insert_or_ignore.setString(2, "service_message")
|
||||||
|
ps_insert_or_ignore.setNull(3, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(4, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(5, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(6, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(7, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(8, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(9, Types.BOOLEAN)
|
||||||
|
ps_insert_or_ignore.setNull(10, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(11, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(12, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(13, Types.BLOB)
|
||||||
|
ps_insert_or_ignore.setInt(14, api_layer)
|
||||||
|
ps_insert_or_ignore.addBatch()
|
||||||
|
} else if (abs is TLMessageEmpty) {
|
||||||
|
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||||
|
ps_insert_or_ignore.setString(2, "empty_message")
|
||||||
|
ps_insert_or_ignore.setNull(3, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(4, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(5, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(6, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(7, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(8, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(9, Types.BOOLEAN)
|
||||||
|
ps_insert_or_ignore.setNull(10, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(11, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(12, Types.INTEGER)
|
||||||
|
ps_insert_or_ignore.setNull(13, Types.BLOB)
|
||||||
|
ps_insert_or_ignore.setInt(14, api_layer)
|
||||||
|
ps_insert_or_ignore.addBatch()
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("Unexpected Message type: " + abs.getClass().getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn!!.setAutoCommit(false)
|
||||||
|
ps.executeBatch()
|
||||||
|
ps.clearBatch()
|
||||||
|
ps_insert_or_ignore.executeBatch()
|
||||||
|
ps_insert_or_ignore.clearBatch()
|
||||||
|
conn!!.commit()
|
||||||
|
conn!!.setAutoCommit(true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Exception shown above happened.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun saveChats(all: TLVector<TLAbsChat>) {
|
||||||
|
try {
|
||||||
|
val ps_insert_or_replace = conn!!.prepareStatement(
|
||||||
|
"INSERT OR REPLACE INTO chats " +
|
||||||
|
"(id, name, type) " +
|
||||||
|
"VALUES " +
|
||||||
|
"(?, ?, ?)")
|
||||||
|
val ps_insert_or_ignore = conn!!.prepareStatement(
|
||||||
|
"INSERT OR IGNORE INTO chats " +
|
||||||
|
"(id, name, type) " +
|
||||||
|
"VALUES " +
|
||||||
|
"(?, ?, ?)")
|
||||||
|
|
||||||
|
for (abs in all) {
|
||||||
|
ps_insert_or_replace.setInt(1, abs.getId())
|
||||||
|
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||||
|
if (abs is TLChatEmpty) {
|
||||||
|
ps_insert_or_ignore.setNull(2, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setString(3, "empty_chat")
|
||||||
|
ps_insert_or_ignore.addBatch()
|
||||||
|
} else if (abs is TLChatForbidden) {
|
||||||
|
ps_insert_or_replace.setString(2, (abs as TLChatForbidden).getTitle())
|
||||||
|
ps_insert_or_replace.setString(3, "chat")
|
||||||
|
ps_insert_or_replace.addBatch()
|
||||||
|
} else if (abs is TLChannelForbidden) {
|
||||||
|
ps_insert_or_replace.setString(2, (abs as TLChannelForbidden).getTitle())
|
||||||
|
ps_insert_or_replace.setString(3, "channel")
|
||||||
|
ps_insert_or_replace.addBatch()
|
||||||
|
} else if (abs is TLChat) {
|
||||||
|
ps_insert_or_replace.setString(2, (abs as TLChat).getTitle())
|
||||||
|
ps_insert_or_replace.setString(3, "chat")
|
||||||
|
ps_insert_or_replace.addBatch()
|
||||||
|
} else if (abs is TLChannel) {
|
||||||
|
ps_insert_or_replace.setString(2, (abs as TLChannel).getTitle())
|
||||||
|
ps_insert_or_replace.setString(3, "channel")
|
||||||
|
ps_insert_or_replace.addBatch()
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("Unexpected " + abs.getClass().getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn!!.setAutoCommit(false)
|
||||||
|
ps_insert_or_ignore.executeBatch()
|
||||||
|
ps_insert_or_ignore.clearBatch()
|
||||||
|
ps_insert_or_replace.executeBatch()
|
||||||
|
ps_insert_or_replace.clearBatch()
|
||||||
|
conn!!.commit()
|
||||||
|
conn!!.setAutoCommit(true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Exception shown above happened.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun saveUsers(all: TLVector<TLAbsUser>) {
|
||||||
|
try {
|
||||||
|
val ps_insert_or_replace = conn!!.prepareStatement(
|
||||||
|
"INSERT OR REPLACE INTO users " +
|
||||||
|
"(id, first_name, last_name, username, type, phone) " +
|
||||||
|
"VALUES " +
|
||||||
|
"(?, ?, ?, ?, ?, ?)")
|
||||||
|
val ps_insert_or_ignore = conn!!.prepareStatement(
|
||||||
|
"INSERT OR IGNORE INTO users " +
|
||||||
|
"(id, first_name, last_name, username, type, phone) " +
|
||||||
|
"VALUES " +
|
||||||
|
"(?, ?, ?, ?, ?, ?)")
|
||||||
|
for (abs in all) {
|
||||||
|
if (abs is TLUser) {
|
||||||
|
val user = abs as TLUser
|
||||||
|
ps_insert_or_replace.setInt(1, user.getId())
|
||||||
|
ps_insert_or_replace.setString(2, user.getFirstName())
|
||||||
|
ps_insert_or_replace.setString(3, user.getLastName())
|
||||||
|
ps_insert_or_replace.setString(4, user.getUsername())
|
||||||
|
ps_insert_or_replace.setString(5, "user")
|
||||||
|
ps_insert_or_replace.setString(6, user.getPhone())
|
||||||
|
ps_insert_or_replace.addBatch()
|
||||||
|
} else if (abs is TLUserEmpty) {
|
||||||
|
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||||
|
ps_insert_or_ignore.setNull(2, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(3, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setNull(4, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.setString(5, "empty_user")
|
||||||
|
ps_insert_or_ignore.setNull(6, Types.VARCHAR)
|
||||||
|
ps_insert_or_ignore.addBatch()
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("Unexpected " + abs.getClass().getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn!!.setAutoCommit(false)
|
||||||
|
ps_insert_or_ignore.executeBatch()
|
||||||
|
ps_insert_or_ignore.clearBatch()
|
||||||
|
ps_insert_or_replace.executeBatch()
|
||||||
|
ps_insert_or_replace.clearBatch()
|
||||||
|
conn!!.commit()
|
||||||
|
conn!!.setAutoCommit(true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Exception shown above happened.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIdsFromQuery(query: String): LinkedList<Integer> {
|
||||||
|
try {
|
||||||
|
val list = LinkedList<Integer>()
|
||||||
|
val rs = stmt!!.executeQuery(query)
|
||||||
|
while (rs.next()) {
|
||||||
|
list.add(rs.getInt(1))
|
||||||
|
}
|
||||||
|
rs.close()
|
||||||
|
return list
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMessageTypesWithCount(c: AbstractChat): HashMap<String, Integer> {
|
||||||
|
val map = HashMap<String, Integer>()
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT message_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY message_type")
|
||||||
|
while (rs.next()) {
|
||||||
|
map.put("count.messages.type." + rs.getString(1), rs.getInt(2))
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMessageMediaTypesWithCount(c: AbstractChat): HashMap<String, Integer> {
|
||||||
|
val map = HashMap<String, Integer>()
|
||||||
|
try {
|
||||||
|
var count = 0
|
||||||
|
val rs = stmt!!.executeQuery("SELECT media_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY media_type")
|
||||||
|
while (rs.next()) {
|
||||||
|
var s = rs.getString(1)
|
||||||
|
if (s == null) {
|
||||||
|
s = "null"
|
||||||
|
} else {
|
||||||
|
count += rs.getInt(2)
|
||||||
|
}
|
||||||
|
map.put("count.messages.media_type." + s!!, rs.getInt(2))
|
||||||
|
}
|
||||||
|
map.put("count.messages.media_type.any", count)
|
||||||
|
return map
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMessageAuthorsWithCount(c: AbstractChat): HashMap<String, Object> {
|
||||||
|
val map = HashMap<String, Object>()
|
||||||
|
val user_map = HashMap<User, Integer>()
|
||||||
|
var 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 {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) " +
|
||||||
|
"FROM messages " +
|
||||||
|
"LEFT JOIN users ON users.id=messages.sender_id " +
|
||||||
|
"WHERE " + c.query + " GROUP BY sender_id")
|
||||||
|
while (rs.next()) {
|
||||||
|
val u: User
|
||||||
|
if (rs.getString(2) != null || rs.getString(3) != null || rs.getString(4) != null) {
|
||||||
|
u = User(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4))
|
||||||
|
} else {
|
||||||
|
u = User(rs.getInt(1), "Unknown", "", "")
|
||||||
|
}
|
||||||
|
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 (e: Exception) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMessageTimesMatrix(c: AbstractChat): Array<IntArray> {
|
||||||
|
val result = Array(7) { IntArray(24) }
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT STRFTIME('%w', time, 'unixepoch') as DAY, " +
|
||||||
|
"STRFTIME('%H', time, 'unixepoch') AS hour, " +
|
||||||
|
"COUNT(id) FROM messages WHERE " + c.query + " GROUP BY hour, day " +
|
||||||
|
"ORDER BY hour, day")
|
||||||
|
while (rs.next()) {
|
||||||
|
result[if (rs.getInt(1) === 0) 6 else rs.getInt(1) - 1][rs.getInt(2)] = rs.getInt(3)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMessagesForExport(c: AbstractChat): LinkedList<HashMap<String, Object>> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
val 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 " +
|
||||||
|
"LEFT JOIN users ON users.id=messages.sender_id " +
|
||||||
|
"LEFT JOIN users AS users_fwd ON users_fwd.id=fwd_from_id WHERE " +
|
||||||
|
c.query + " " +
|
||||||
|
"ORDER BY messages.message_id")
|
||||||
|
val format_time = SimpleDateFormat("HH:mm:ss")
|
||||||
|
val format_date = SimpleDateFormat("d MMM yy")
|
||||||
|
val meta = rs.getMetaData()
|
||||||
|
val columns = meta.getColumnCount()
|
||||||
|
val list = LinkedList<HashMap<String, Object>>()
|
||||||
|
|
||||||
|
var count: Integer = 0
|
||||||
|
var old_date: String? = null
|
||||||
|
var old_user: Integer? = null
|
||||||
|
while (rs.next()) {
|
||||||
|
val h = HashMap<String, Object>(columns)
|
||||||
|
for (i in 1..columns) {
|
||||||
|
h.put(meta.getColumnName(i), rs.getObject(i))
|
||||||
|
}
|
||||||
|
// Additional values to make up for Mustache's inability to format dates
|
||||||
|
val d = rs.getTime("time")
|
||||||
|
val date = format_date.format(d)
|
||||||
|
h.put("formatted_time", format_time.format(d))
|
||||||
|
h.put("formatted_date", date)
|
||||||
|
if (rs.getString("media_type") != null) {
|
||||||
|
h.put("media_" + rs.getString("media_type"), true)
|
||||||
|
}
|
||||||
|
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", if (count % 2 == 0) "even" else "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
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Exception above!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract inner class AbstractChat {
|
||||||
|
abstract val query: String
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Dialog(var id: Int, var first_name: String, var last_name: String, var username: String, var count: Int) : AbstractChat() {
|
||||||
|
|
||||||
|
override val query: String
|
||||||
|
get() = "source_type='dialog' AND source_id=" + id
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Chat(var id: Int, var name: String, var count: Int) : AbstractChat() {
|
||||||
|
|
||||||
|
override val query: String
|
||||||
|
get() = "source_type IN('group', 'supergroup', 'channel') AND source_id=" + id
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class User(id: Int, first_name: String?, last_name: String?, username: String) {
|
||||||
|
var name: String
|
||||||
|
var isMe: Boolean = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
isMe = id == user_manager.getUser().getId()
|
||||||
|
val s = StringBuilder()
|
||||||
|
if (first_name != null) s.append(first_name + " ")
|
||||||
|
if (last_name != null) s.append(last_name)
|
||||||
|
name = s.toString().trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class GlobalChat : AbstractChat() {
|
||||||
|
override val query: String
|
||||||
|
get() = "1=1"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(Database::class.java)
|
||||||
|
private var instance: Database? = null
|
||||||
|
|
||||||
|
fun init(c: TelegramClient) {
|
||||||
|
instance = Database(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(): Database {
|
||||||
|
if (instance == null) throw RuntimeException("Database is not initialized but getInstance() was called.")
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bytesToTLMessage(b: ByteArray?): TLMessage? {
|
||||||
|
try {
|
||||||
|
if (b == null) return null
|
||||||
|
val stream = ByteArrayInputStream(b)
|
||||||
|
val msg = TLMessage()
|
||||||
|
msg.deserializeBody(stream, TLApiContext.getInstance())
|
||||||
|
return msg
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
throw RuntimeException("Could not deserialize message.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,379 @@
|
|||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import java.util.HashMap
|
||||||
|
import java.util.LinkedHashMap
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.sql.SQLException
|
||||||
|
import java.sql.Statement
|
||||||
|
import java.sql.Types
|
||||||
|
import java.sql.ResultSet
|
||||||
|
import java.sql.PreparedStatement
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||||
|
|
||||||
|
class DatabaseUpdates(protected var conn: Connection, protected var db: Database) {
|
||||||
|
|
||||||
|
private val maxPossibleVersion: Int
|
||||||
|
get() = updates.size()
|
||||||
|
|
||||||
|
init {
|
||||||
|
logger.debug("Registering Database Updates...")
|
||||||
|
register(DB_Update_1(conn, db))
|
||||||
|
register(DB_Update_2(conn, db))
|
||||||
|
register(DB_Update_3(conn, db))
|
||||||
|
register(DB_Update_4(conn, db))
|
||||||
|
register(DB_Update_5(conn, db))
|
||||||
|
register(DB_Update_6(conn, db))
|
||||||
|
register(DB_Update_7(conn, db))
|
||||||
|
register(DB_Update_8(conn, db))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doUpdates() {
|
||||||
|
try {
|
||||||
|
val stmt = conn.createStatement()
|
||||||
|
var rs: ResultSet
|
||||||
|
logger.debug("DatabaseUpdate.doUpdates running")
|
||||||
|
|
||||||
|
logger.debug("Getting current database version")
|
||||||
|
val version: Int
|
||||||
|
logger.debug("Checking if table database_versions exists")
|
||||||
|
rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'")
|
||||||
|
rs.next()
|
||||||
|
if (rs.getInt(1) === 0) {
|
||||||
|
logger.debug("Table does not exist")
|
||||||
|
version = 0
|
||||||
|
} else {
|
||||||
|
logger.debug("Table exists. Checking max version")
|
||||||
|
rs.close()
|
||||||
|
rs = stmt.executeQuery("SELECT MAX(version) FROM database_versions")
|
||||||
|
rs.next()
|
||||||
|
version = rs.getInt(1)
|
||||||
|
}
|
||||||
|
rs.close()
|
||||||
|
logger.debug("version: {}", version)
|
||||||
|
System.out.println("Database version: " + version)
|
||||||
|
logger.debug("Max available database version is {}", maxPossibleVersion)
|
||||||
|
|
||||||
|
if (version < maxPossibleVersion) {
|
||||||
|
logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion)
|
||||||
|
var backup = false
|
||||||
|
for (i in version + 1..maxPossibleVersion) {
|
||||||
|
if (getUpdateToVersion(i).needsBackup()) {
|
||||||
|
logger.debug("Update to version {} needs a backup", i)
|
||||||
|
backup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (backup) {
|
||||||
|
if (version > 0) {
|
||||||
|
logger.debug("Performing backup")
|
||||||
|
db.backupDatabase(version)
|
||||||
|
} else {
|
||||||
|
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 (i in version + 1..maxPossibleVersion) {
|
||||||
|
getUpdateToVersion(i).doUpdate()
|
||||||
|
}
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logger.debug("No update necessary.")
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUpdateToVersion(i: Int): DatabaseUpdate {
|
||||||
|
return updates.get(i - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun register(d: DatabaseUpdate) {
|
||||||
|
logger.debug("Registering {} as update to version {}", d.getClass().getName(), d.version)
|
||||||
|
if (d.version != updates.size() + 1) {
|
||||||
|
throw RuntimeException("Tried to register DB update to version " + d.version + ", but would need update to version " + (updates.size() + 1))
|
||||||
|
}
|
||||||
|
updates.add(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(DatabaseUpdates::class.java)
|
||||||
|
private val updates = LinkedList<DatabaseUpdate>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class DatabaseUpdate(protected var conn: Connection, protected var db: Database) {
|
||||||
|
protected var stmt: Statement
|
||||||
|
abstract val version: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
stmt = conn.createStatement()
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
fun doUpdate() {
|
||||||
|
logger.debug("Applying update to version {}", version)
|
||||||
|
System.out.println(" Updating to version $version...")
|
||||||
|
_doUpdate()
|
||||||
|
logger.debug("Saving current database version to the db")
|
||||||
|
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES ($version)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
protected abstract fun _doUpdate()
|
||||||
|
|
||||||
|
fun needsBackup(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
protected fun execute(sql: String) {
|
||||||
|
logger.debug("Executing: {}", sql)
|
||||||
|
stmt.executeUpdate(sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_1(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 1
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
stmt.executeUpdate("CREATE TABLE messages ("
|
||||||
|
+ "id INTEGER PRIMARY KEY ASC, "
|
||||||
|
+ "dialog_id INTEGER, "
|
||||||
|
+ "to_id INTEGER, "
|
||||||
|
+ "from_id INTEGER, "
|
||||||
|
+ "from_type TEXT, "
|
||||||
|
+ "text TEXT, "
|
||||||
|
+ "time TEXT, "
|
||||||
|
+ "has_media BOOLEAN, "
|
||||||
|
+ "sticker TEXT, "
|
||||||
|
+ "data BLOB,"
|
||||||
|
+ "type TEXT)")
|
||||||
|
stmt.executeUpdate("CREATE TABLE dialogs ("
|
||||||
|
+ "id INTEGER PRIMARY KEY ASC, "
|
||||||
|
+ "name TEXT, "
|
||||||
|
+ "type TEXT)")
|
||||||
|
stmt.executeUpdate("CREATE TABLE people ("
|
||||||
|
+ "id INTEGER PRIMARY KEY ASC, "
|
||||||
|
+ "first_name TEXT, "
|
||||||
|
+ "last_name TEXT, "
|
||||||
|
+ "username TEXT, "
|
||||||
|
+ "type TEXT)")
|
||||||
|
stmt.executeUpdate("CREATE TABLE database_versions (" + "version INTEGER)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_2(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 2
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'")
|
||||||
|
stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_3(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 3
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_4(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 4
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
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")
|
||||||
|
stmt.executeUpdate("DROP TABLE messages")
|
||||||
|
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO 'messages'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_5(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 5
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_6(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 6
|
||||||
|
|
||||||
|
override fun needsBackup(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
stmt.executeUpdate(
|
||||||
|
"CREATE TABLE messages_new (\n" +
|
||||||
|
" id INTEGER PRIMARY KEY ASC,\n" +
|
||||||
|
" message_type TEXT,\n" +
|
||||||
|
" dialog_id INTEGER,\n" +
|
||||||
|
" chat_id INTEGER,\n" +
|
||||||
|
" sender_id INTEGER,\n" +
|
||||||
|
" fwd_from_id INTEGER,\n" +
|
||||||
|
" text TEXT,\n" +
|
||||||
|
" time INTEGER,\n" +
|
||||||
|
" has_media BOOLEAN,\n" +
|
||||||
|
" media_type TEXT,\n" +
|
||||||
|
" media_file TEXT,\n" +
|
||||||
|
" media_size INTEGER,\n" +
|
||||||
|
" media_json TEXT,\n" +
|
||||||
|
" markup_json TEXT,\n" +
|
||||||
|
" data BLOB)")
|
||||||
|
val mappings = LinkedHashMap<String, String>()
|
||||||
|
mappings.put("id", "id")
|
||||||
|
mappings.put("message_type", "type")
|
||||||
|
mappings.put("dialog_id", "CASE from_type WHEN 'user' THEN dialog_id ELSE NULL END")
|
||||||
|
mappings.put("chat_id", "CASE from_type WHEN 'chat' THEN dialog_id ELSE NULL END")
|
||||||
|
mappings.put("sender_id", "from_id")
|
||||||
|
mappings.put("text", "text")
|
||||||
|
mappings.put("time", "time")
|
||||||
|
mappings.put("has_media", "has_media")
|
||||||
|
mappings.put("data", "data")
|
||||||
|
val query = StringBuilder("INSERT INTO messages_new\n(")
|
||||||
|
var first: Boolean
|
||||||
|
first = true
|
||||||
|
for (s in mappings.keySet()) {
|
||||||
|
if (!first) query.append(", ")
|
||||||
|
query.append(s)
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
query.append(")\nSELECT \n")
|
||||||
|
first = true
|
||||||
|
for (s in mappings.values()) {
|
||||||
|
if (!first) query.append(", ")
|
||||||
|
query.append(s)
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
query.append("\nFROM messages")
|
||||||
|
stmt.executeUpdate(query.toString())
|
||||||
|
|
||||||
|
System.out.println(" Updating the data (this might take some time)...")
|
||||||
|
val rs = stmt.executeQuery("SELECT id, data FROM messages_new")
|
||||||
|
val ps = conn.prepareStatement("UPDATE messages_new SET fwd_from_id=?, media_type=?, media_file=?, media_size=? WHERE id=?")
|
||||||
|
while (rs.next()) {
|
||||||
|
ps.setInt(5, rs.getInt(1))
|
||||||
|
val msg = db.bytesToTLMessage(rs.getBytes(2))
|
||||||
|
if (msg == null || msg!!.getFwdFrom() == null) {
|
||||||
|
ps.setNull(1, Types.INTEGER)
|
||||||
|
} else {
|
||||||
|
ps.setInt(1, msg!!.getFwdFrom().getFromId())
|
||||||
|
}
|
||||||
|
val f = FileManagerFactory.getFileManager(msg, db.user_manager, db.client)
|
||||||
|
if (f == null) {
|
||||||
|
ps.setNull(2, Types.VARCHAR)
|
||||||
|
ps.setNull(3, Types.VARCHAR)
|
||||||
|
ps.setNull(4, Types.INTEGER)
|
||||||
|
} else {
|
||||||
|
ps.setString(2, f!!.getName())
|
||||||
|
ps.setString(3, f!!.getTargetFilename())
|
||||||
|
ps.setInt(4, f!!.getSize())
|
||||||
|
}
|
||||||
|
ps.addBatch()
|
||||||
|
}
|
||||||
|
rs.close()
|
||||||
|
conn.setAutoCommit(false)
|
||||||
|
ps.executeBatch()
|
||||||
|
conn.commit()
|
||||||
|
conn.setAutoCommit(true)
|
||||||
|
stmt.executeUpdate("DROP TABLE messages")
|
||||||
|
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO messages")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_7(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 7
|
||||||
|
|
||||||
|
override fun needsBackup(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER")
|
||||||
|
|
||||||
|
stmt.executeUpdate("UPDATE messages SET api_layer=51")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DB_Update_8(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||||
|
override val version: Int
|
||||||
|
get() = 8
|
||||||
|
|
||||||
|
override fun needsBackup(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
|
override fun _doUpdate() {
|
||||||
|
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)")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,495 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.api.Kotlogram
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
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
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.HashMap
|
||||||
|
import java.util.Random
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressInterface) {
|
||||||
|
internal var user: UserManager? = null
|
||||||
|
internal var db: Database? = null
|
||||||
|
internal var prog: DownloadProgressInterface? = null
|
||||||
|
internal var has_seen_flood_wait_message = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.user = UserManager.getInstance()
|
||||||
|
this.prog = p
|
||||||
|
this.db = Database.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class)
|
||||||
|
fun downloadMessages(limit: Integer) {
|
||||||
|
var completed = true
|
||||||
|
do {
|
||||||
|
completed = true
|
||||||
|
try {
|
||||||
|
_downloadMessages(limit)
|
||||||
|
} catch (e: RpcErrorException) {
|
||||||
|
if (e.getCode() === 420) { // FLOOD_WAIT
|
||||||
|
completed = false
|
||||||
|
Utils.obeyFloodWaitException(e)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} catch (e: TimeoutException) {
|
||||||
|
completed = false
|
||||||
|
System.out.println("")
|
||||||
|
System.out.println("Telegram took too long to respond to our request.")
|
||||||
|
System.out.println("I'm going to wait a minute and then try again.")
|
||||||
|
try {
|
||||||
|
TimeUnit.MINUTES.sleep(1)
|
||||||
|
} catch (e2: InterruptedException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("")
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (!completed)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
fun _downloadMessages(limit: Integer?) {
|
||||||
|
logger.info("This is _downloadMessages with limit {}", limit)
|
||||||
|
val dialog_limit = 100
|
||||||
|
logger.info("Downloading the last {} dialogs", dialog_limit)
|
||||||
|
System.out.println("Downloading most recent dialogs... ")
|
||||||
|
var max_message_id = 0
|
||||||
|
val dialogs = client!!.messagesGetDialogs(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
TLInputPeerEmpty(),
|
||||||
|
dialog_limit)
|
||||||
|
logger.debug("Got {} dialogs", dialogs.getDialogs().size())
|
||||||
|
|
||||||
|
for (d in dialogs.getDialogs()) {
|
||||||
|
if (d.getTopMessage() > max_message_id && d.getPeer() !is TLPeerChannel) {
|
||||||
|
logger.trace("Updating top message id: {} => {}. Dialog type: {}", max_message_id, d.getTopMessage(), d.getPeer().getClass().getName())
|
||||||
|
max_message_id = d.getTopMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("Top message ID is " + max_message_id)
|
||||||
|
var max_database_id = db!!.getTopMessageID()
|
||||||
|
System.out.println("Top message ID in database is " + max_database_id)
|
||||||
|
if (limit != null) {
|
||||||
|
System.out.println("Limit is set to " + limit!!)
|
||||||
|
max_database_id = Math.max(max_database_id, max_message_id - limit!!)
|
||||||
|
System.out.println("New top message id 'in database' is " + max_database_id)
|
||||||
|
}
|
||||||
|
if (max_message_id - max_database_id > 1000000) {
|
||||||
|
System.out.println("Would have to load more than 1 million messages which is not supported by telegram. Capping the list.")
|
||||||
|
logger.debug("max_message_id={}, max_database_id={}, difference={}", max_message_id, max_database_id, max_message_id - max_database_id)
|
||||||
|
max_database_id = Math.max(0, max_message_id - 1000000)
|
||||||
|
logger.debug("new max_database_id: {}", max_database_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_database_id == max_message_id) {
|
||||||
|
System.out.println("No new messages to download.")
|
||||||
|
} else if (max_database_id > max_message_id) {
|
||||||
|
throw RuntimeException("max_database_id is bigger then max_message_id. This shouldn't happen. But the telegram api nonetheless does that sometimes. Just ignore this error, wait a few seconds and then try again.")
|
||||||
|
} else {
|
||||||
|
val start_id = max_database_id + 1
|
||||||
|
val end_id = max_message_id
|
||||||
|
|
||||||
|
val ids = makeIdList(start_id, end_id)
|
||||||
|
downloadMessages(ids, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Searching for missing messages in the db")
|
||||||
|
val count_missing = 0
|
||||||
|
System.out.println("Checking message database for completeness...")
|
||||||
|
val db_count = db!!.getMessageCount()
|
||||||
|
val db_max = db!!.getTopMessageID()
|
||||||
|
logger.debug("db_count: {}", db_count)
|
||||||
|
logger.debug("db_max: {}", 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 {
|
||||||
|
LinkedList<Integer> all_missing_ids = db.getMissingIDs();
|
||||||
|
LinkedList<Integer> downloadable_missing_ids = new LinkedList<Integer>();
|
||||||
|
for (Integer id : all_missing_ids) {
|
||||||
|
if (id > max_message_id - 1000000) downloadable_missing_ids.add(id);
|
||||||
|
}
|
||||||
|
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, 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 || 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.")
|
||||||
|
|
||||||
|
val channel_access_hashes = HashMap<Integer, Long>()
|
||||||
|
val channel_names = HashMap<Integer, String>()
|
||||||
|
val channels = LinkedList<Integer>()
|
||||||
|
val supergroups = LinkedList<Integer>()
|
||||||
|
|
||||||
|
// TODO Add chat title (and other stuff?) to the database
|
||||||
|
for (c in dialogs.getChats()) {
|
||||||
|
if (c is TLChannel) {
|
||||||
|
val ch = c as TLChannel
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (d in dialogs.getDialogs()) {
|
||||||
|
if (d.getPeer() is TLPeerChannel) {
|
||||||
|
val channel_id = (d.getPeer() as TLPeerChannel).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
|
||||||
|
}
|
||||||
|
val max_known_id = db!!.getTopMessageIDForChannel(channel_id)
|
||||||
|
if (d.getTopMessage() > max_known_id) {
|
||||||
|
val ids = makeIdList(max_known_id + 1, d.getTopMessage())
|
||||||
|
val access_hash = channel_access_hashes.get(channel_id) ?: throw RuntimeException("AccessHash for Channel missing.")
|
||||||
|
var channel_name = channel_names.get(channel_id)
|
||||||
|
if (channel_name == null) {
|
||||||
|
channel_name = "?"
|
||||||
|
}
|
||||||
|
val channel = TLInputChannel(channel_id, access_hash)
|
||||||
|
downloadMessages(ids, channel, "channel " + channel_name!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class)
|
||||||
|
private fun downloadMessages(ids: List<Integer>, channel: TLInputChannel?, source_string: String?) {
|
||||||
|
prog!!.onMessageDownloadStart(ids.size(), source_string)
|
||||||
|
|
||||||
|
logger.debug("Entering download loop")
|
||||||
|
while (ids.size() > 0) {
|
||||||
|
logger.trace("Loop")
|
||||||
|
val vector = TLIntVector()
|
||||||
|
val download_count = Config.GET_MESSAGES_BATCH_SIZE
|
||||||
|
logger.trace("download_count: {}", download_count)
|
||||||
|
for (i in 0 until download_count) {
|
||||||
|
if (ids.size() === 0) break
|
||||||
|
vector.add(ids.remove(0))
|
||||||
|
}
|
||||||
|
logger.trace("vector.size(): {}", vector.size())
|
||||||
|
logger.trace("ids.size(): {}", ids.size())
|
||||||
|
|
||||||
|
var response: TLAbsMessages
|
||||||
|
var tries = 0
|
||||||
|
while (true) {
|
||||||
|
logger.trace("Trying getMessages(), tries={}", tries)
|
||||||
|
if (tries >= 5) {
|
||||||
|
CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.")
|
||||||
|
}
|
||||||
|
tries++
|
||||||
|
try {
|
||||||
|
if (channel == null) {
|
||||||
|
response = client!!.messagesGetMessages(vector)
|
||||||
|
} else {
|
||||||
|
response = client!!.channelsGetMessages(channel, vector)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} catch (e: RpcErrorException) {
|
||||||
|
if (e.getCode() === 420) { // FLOOD_WAIT
|
||||||
|
Utils.obeyFloodWaitException(e, has_seen_flood_wait_message)
|
||||||
|
has_seen_flood_wait_message = true
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
logger.trace("response.getMessages().size(): {}", response.getMessages().size())
|
||||||
|
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())
|
||||||
|
db!!.saveUsers(response.getUsers())
|
||||||
|
logger.trace("Sleeping")
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
logger.debug("Finished.")
|
||||||
|
|
||||||
|
prog!!.onMessageDownloadFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class)
|
||||||
|
fun downloadMedia() {
|
||||||
|
download_client = client!!.getDownloaderClient()
|
||||||
|
var completed = true
|
||||||
|
do {
|
||||||
|
completed = true
|
||||||
|
try {
|
||||||
|
_downloadMedia()
|
||||||
|
} catch (e: RpcErrorException) {
|
||||||
|
if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
|
||||||
|
completed = false
|
||||||
|
Utils.obeyFloodWaitException(e)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*catch (TimeoutException e) {
|
||||||
|
completed = false;
|
||||||
|
System.out.println("");
|
||||||
|
System.out.println("Telegram took too long to respond to our request.");
|
||||||
|
System.out.println("I'm going to wait a minute and then try again.");
|
||||||
|
logger.warn("TimeoutException caught", e);
|
||||||
|
try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {}
|
||||||
|
System.out.println("");
|
||||||
|
}*/
|
||||||
|
} while (!completed)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class)
|
||||||
|
private fun _downloadMedia() {
|
||||||
|
logger.info("This is _downloadMedia")
|
||||||
|
logger.info("Checking if there are messages in the DB with a too old API layer")
|
||||||
|
val ids = db!!.getIdsFromQuery("SELECT id FROM messages WHERE has_media=1 AND api_layer<" + Kotlogram.API_LAYER)
|
||||||
|
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, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val messages = this.db!!.getMessagesWithMedia()
|
||||||
|
logger.debug("Database returned {} messages with media", messages.size())
|
||||||
|
prog!!.onMediaDownloadStart(messages.size())
|
||||||
|
for (msg in messages) {
|
||||||
|
val m = FileManagerFactory.getFileManager(msg, user, client)
|
||||||
|
logger.trace("message {}, {}, {}, {}, {}",
|
||||||
|
msg.getId(),
|
||||||
|
msg.getMedia().getClass().getSimpleName().replace("TLMessageMedia", "…"),
|
||||||
|
m.getClass().getSimpleName(),
|
||||||
|
if (m.isEmpty()) "empty" else "non-empty",
|
||||||
|
if (m.isDownloaded()) "downloaded" else "not downloaded")
|
||||||
|
if (m.isEmpty()) {
|
||||||
|
prog!!.onMediaDownloadedEmpty()
|
||||||
|
} else if (m.isDownloaded()) {
|
||||||
|
prog!!.onMediaAlreadyPresent(m)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
m.download()
|
||||||
|
prog!!.onMediaDownloaded(m)
|
||||||
|
} catch (e: TimeoutException) {
|
||||||
|
// do nothing - skip this file
|
||||||
|
prog!!.onMediaSkipped()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prog!!.onMediaDownloadFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeIdList(start: Int, end: Int): List<Integer> {
|
||||||
|
val a = LinkedList<Integer>()
|
||||||
|
for (i in start..end) a.add(i)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal var download_client: TelegramClient? = null
|
||||||
|
internal var last_download_succeeded = true
|
||||||
|
internal val logger = LoggerFactory.getLogger(DownloadManager::class.java)
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
fun downloadFile(client: TelegramClient, targetFilename: String, size: Int, dcId: Int, volumeId: Long, localId: Int, secret: Long) {
|
||||||
|
val loc = TLInputFileLocation(volumeId, localId, secret)
|
||||||
|
downloadFileFromDc(client, targetFilename, loc, dcId, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
fun downloadFile(client: TelegramClient, targetFilename: String, size: Int, dcId: Int, id: Long, accessHash: Long) {
|
||||||
|
val loc = TLInputDocumentFileLocation(id, accessHash)
|
||||||
|
downloadFileFromDc(client, targetFilename, loc, dcId, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
private fun downloadFileFromDc(client: TelegramClient, target: String, loc: TLAbsInputFileLocation, dcID: Integer?, size: Int): Boolean {
|
||||||
|
var fos: FileOutputStream? = null
|
||||||
|
try {
|
||||||
|
val temp_filename = target + ".downloading"
|
||||||
|
logger.debug("Downloading file {}", target)
|
||||||
|
logger.trace("Temporary filename: {}", temp_filename)
|
||||||
|
|
||||||
|
var offset = 0
|
||||||
|
if (File(temp_filename).isFile()) {
|
||||||
|
logger.info("Temporary filename already exists; continuing this file")
|
||||||
|
offset = File(temp_filename).length()
|
||||||
|
if (offset >= size) {
|
||||||
|
logger.warn("Temporary file size is >= the target size. Assuming corrupt file & deleting it")
|
||||||
|
File(temp_filename).delete()
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.trace("offset before the loop is {}", offset)
|
||||||
|
fos = FileOutputStream(temp_filename, true)
|
||||||
|
var response: TLFile? = null
|
||||||
|
var try_again: Boolean
|
||||||
|
do {
|
||||||
|
try_again = false
|
||||||
|
logger.trace("offset: {} block_size: {} size: {}", offset, size, size)
|
||||||
|
val req = TLRequestUploadGetFile(loc, offset, size)
|
||||||
|
try {
|
||||||
|
if (dcID == null) {
|
||||||
|
response = download_client!!.executeRpcQuery(req) as TLFile
|
||||||
|
} else {
|
||||||
|
response = download_client!!.executeRpcQuery(req, dcID) as TLFile
|
||||||
|
}
|
||||||
|
} catch (e: RpcErrorException) {
|
||||||
|
if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
|
||||||
|
try_again = true
|
||||||
|
Utils.obeyFloodWaitException(e)
|
||||||
|
} else if (e.getCode() === 400) {
|
||||||
|
//Somehow this file is broken. No idea why. Let's skip it for now
|
||||||
|
try_again = true
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
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 (e: InterruptedException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (offset < size && (response!!.getBytes().getData().length > 0 || try_again))
|
||||||
|
fos!!.close()
|
||||||
|
if (offset < size) {
|
||||||
|
System.out.println("Requested file $target with $size bytes, but got only $offset bytes.")
|
||||||
|
File(temp_filename).delete()
|
||||||
|
System.exit(1)
|
||||||
|
}
|
||||||
|
logger.trace("Renaming {} to {}", temp_filename, target)
|
||||||
|
var rename_tries = 0
|
||||||
|
var last_exception: IOException? = null
|
||||||
|
while (rename_tries <= Config.RENAMING_MAX_TRIES) {
|
||||||
|
rename_tries++
|
||||||
|
try {
|
||||||
|
Files.move(File(temp_filename).toPath(), File(target).toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
last_exception = null
|
||||||
|
break
|
||||||
|
} catch (e: IOException) {
|
||||||
|
logger.debug("Exception during move. rename_tries: {}. Exception: {}", rename_tries, e)
|
||||||
|
last_exception = e
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(Config.RENAMING_DELAY)
|
||||||
|
} catch (e2: InterruptedException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (last_exception != null) {
|
||||||
|
throw last_exception
|
||||||
|
}
|
||||||
|
last_download_succeeded = true
|
||||||
|
return true
|
||||||
|
} catch (ex: java.io.IOException) {
|
||||||
|
if (fos != null) fos!!.close()
|
||||||
|
System.out.println("IOException happened while downloading " + target)
|
||||||
|
throw ex
|
||||||
|
} catch (ex: RpcErrorException) {
|
||||||
|
if (fos != null) fos!!.close()
|
||||||
|
if (ex.getCode() === 500) {
|
||||||
|
if (!last_download_succeeded) {
|
||||||
|
System.out.println("Got an Internal Server Error from Telegram. Since the file downloaded before also happened to get this error, we will stop downloading now. Please try again later.")
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
last_download_succeeded = false
|
||||||
|
System.out.println("Got an Internal Server Error from Telegram. Skipping this file for now. Next run of telegram_backup will continue to download this file.")
|
||||||
|
logger.warn(ex.toString())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
System.out.println("RpcErrorException happened while downloading " + target)
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun downloadExternalFile(target: String, url: String): Boolean {
|
||||||
|
FileUtils.copyURLToFile(URL(url), File(target), 5000, 5000)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,19 +14,19 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
|
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||||
|
|
||||||
public interface DownloadProgressInterface {
|
interface DownloadProgressInterface {
|
||||||
public void onMessageDownloadStart(int count, String source);
|
fun onMessageDownloadStart(count: Int, source: String)
|
||||||
public void onMessageDownloaded(int number);
|
fun onMessageDownloaded(number: Int)
|
||||||
public void onMessageDownloadFinished();
|
fun onMessageDownloadFinished()
|
||||||
|
|
||||||
public void onMediaDownloadStart(int count);
|
fun onMediaDownloadStart(count: Int)
|
||||||
public void onMediaDownloaded(AbstractMediaFileManager a);
|
fun onMediaDownloaded(a: AbstractMediaFileManager)
|
||||||
public void onMediaDownloadedEmpty();
|
fun onMediaDownloadedEmpty()
|
||||||
public void onMediaSkipped();
|
fun onMediaSkipped()
|
||||||
public void onMediaAlreadyPresent(AbstractMediaFileManager a);
|
fun onMediaAlreadyPresent(a: AbstractMediaFileManager)
|
||||||
public void onMediaDownloadFinished();
|
fun onMediaDownloadFinished()
|
||||||
}
|
}
|
101
src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt
Normal file
101
src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import javax.swing.*
|
||||||
|
import javax.swing.event.ListSelectionEvent
|
||||||
|
import javax.swing.event.ListSelectionListener
|
||||||
|
import java.awt.*
|
||||||
|
import java.awt.event.ActionEvent
|
||||||
|
import java.awt.event.ActionListener
|
||||||
|
import java.util.Vector
|
||||||
|
|
||||||
|
class GUIController {
|
||||||
|
init {
|
||||||
|
showAccountChooserDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showAccountChooserDialog() {
|
||||||
|
val accountChooser = JDialog()
|
||||||
|
accountChooser.setTitle("Choose account")
|
||||||
|
accountChooser.setSize(400, 200)
|
||||||
|
val vert = JPanel()
|
||||||
|
vert.setLayout(BorderLayout())
|
||||||
|
vert.add(JLabel("Please select the account to use or create a new one."), BorderLayout.NORTH)
|
||||||
|
val accounts = Utils.getAccounts()
|
||||||
|
val list = JList<String>(accounts)
|
||||||
|
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
|
vert.add(list, BorderLayout.CENTER)
|
||||||
|
val bottom = JPanel(GridLayout(1, 2))
|
||||||
|
val btnAddAccount = JButton("Add account")
|
||||||
|
bottom.add(btnAddAccount)
|
||||||
|
val btnLogin = JButton("Login")
|
||||||
|
btnLogin.setEnabled(false)
|
||||||
|
bottom.add(btnLogin)
|
||||||
|
vert.add(bottom, BorderLayout.SOUTH)
|
||||||
|
accountChooser.add(vert)
|
||||||
|
accountChooser.setVisible(true)
|
||||||
|
accountChooser.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
|
||||||
|
list.addListSelectionListener(object : ListSelectionListener() {
|
||||||
|
@Override
|
||||||
|
fun valueChanged(e: ListSelectionEvent) {
|
||||||
|
btnLogin.setEnabled(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
btnAddAccount.addActionListener(object : ActionListener() {
|
||||||
|
@Override
|
||||||
|
fun actionPerformed(e: ActionEvent) {
|
||||||
|
accountChooser.setVisible(false)
|
||||||
|
accountChooser.dispose()
|
||||||
|
addAccountDialog()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAccountDialog() {
|
||||||
|
val loginDialog = JDialog()
|
||||||
|
loginDialog.setTitle("Add an account")
|
||||||
|
loginDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
|
||||||
|
val sections = JPanel()
|
||||||
|
sections.setLayout(BoxLayout(sections, BoxLayout.Y_AXIS))
|
||||||
|
|
||||||
|
val top = JPanel()
|
||||||
|
top.setLayout(BoxLayout(top, BoxLayout.Y_AXIS))
|
||||||
|
top.add(JLabel("Please enter your phone number in international format:"))
|
||||||
|
top.add(JTextField("+49123773212"))
|
||||||
|
|
||||||
|
sections.add(top)
|
||||||
|
sections.add(Box.createVerticalStrut(5))
|
||||||
|
sections.add(JSeparator(SwingConstants.HORIZONTAL))
|
||||||
|
|
||||||
|
val middle = JPanel()
|
||||||
|
middle.setLayout(BoxLayout(middle, BoxLayout.Y_AXIS))
|
||||||
|
middle.add(JLabel("Telegram sent you a code. Enter it here:"))
|
||||||
|
middle.add(JTextField())
|
||||||
|
middle.setEnabled(false)
|
||||||
|
|
||||||
|
sections.add(middle)
|
||||||
|
sections.add(Box.createVerticalStrut(5))
|
||||||
|
sections.add(JSeparator(SwingConstants.HORIZONTAL))
|
||||||
|
|
||||||
|
loginDialog.add(sections)
|
||||||
|
loginDialog.setVisible(true)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import java.lang.StringBuilder
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object StickerConverter {
|
||||||
|
fun makeFilenameWithPath(attr: TLDocumentAttributeSticker): String {
|
||||||
|
val file = StringBuilder()
|
||||||
|
file.append(makePath())
|
||||||
|
file.append(makeFilename(attr))
|
||||||
|
return file.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeFilename(attr: TLDocumentAttributeSticker): String {
|
||||||
|
val file = StringBuilder()
|
||||||
|
if (attr.getStickerset() is TLInputStickerSetShortName) {
|
||||||
|
file.append((attr.getStickerset() as TLInputStickerSetShortName).getShortName())
|
||||||
|
} else if (attr.getStickerset() is TLInputStickerSetID) {
|
||||||
|
file.append((attr.getStickerset() as TLInputStickerSetID).getId())
|
||||||
|
}
|
||||||
|
file.append("_")
|
||||||
|
file.append(attr.getAlt().hashCode())
|
||||||
|
file.append(".webp")
|
||||||
|
return file.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makePath(): String {
|
||||||
|
val path = Config.FILE_BASE +
|
||||||
|
File.separatorChar +
|
||||||
|
Config.FILE_STICKER_BASE +
|
||||||
|
File.separatorChar
|
||||||
|
File(path).mkdirs()
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.tl.TLContext
|
||||||
|
import com.github.badoualy.telegram.tl.api.account.TLPassword
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLMethod
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.tl.StreamUtils.readTLObject
|
||||||
|
|
||||||
|
class TLRequestAccountGetPasswordWithCurrentSalt : TLMethod<TLPassword>() {
|
||||||
|
private val _constructor = "account.getPassword#548a30f5"
|
||||||
|
val constructorId: Int
|
||||||
|
get() = CONSTRUCTOR_ID
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun deserializeResponse(stream: InputStream, context: TLContext): TLPassword {
|
||||||
|
val response = (readTLObject(stream, context) ?: throw IOException("Unable to parse response")) as? TLPassword ?: throw IOException("Incorrect response type, expected getClass().getCanonicalName(), found response.getClass().getCanonicalName()")
|
||||||
|
return response as TLPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toString(): String {
|
||||||
|
return _constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CONSTRUCTOR_ID = 0x548a30f5
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.UpdateCallback
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.api.Kotlogram
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLVector
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||||
|
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||||
|
|
||||||
|
internal class TelegramUpdateHandler : UpdateCallback {
|
||||||
|
private var user: UserManager? = null
|
||||||
|
private var db: Database? = null
|
||||||
|
var debug = false
|
||||||
|
|
||||||
|
fun activate() {
|
||||||
|
this.user = UserManager.getInstance()
|
||||||
|
this.db = Database.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUpdates(c: TelegramClient, u: TLUpdates) {
|
||||||
|
if (db == null) return
|
||||||
|
if (debug) System.out.println("onUpdates - " + u.getUpdates().size() + " Updates, " + u.getUsers().size() + " Users, " + u.getChats().size() + " Chats")
|
||||||
|
for (update in u.getUpdates()) {
|
||||||
|
processUpdate(update, c)
|
||||||
|
if (debug) System.out.println(" " + update.getClass().getName())
|
||||||
|
}
|
||||||
|
db!!.saveUsers(u.getUsers())
|
||||||
|
db!!.saveChats(u.getChats())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUpdatesCombined(c: TelegramClient, u: TLUpdatesCombined) {
|
||||||
|
if (db == null) return
|
||||||
|
if (debug) System.out.println("onUpdatesCombined")
|
||||||
|
for (update in u.getUpdates()) {
|
||||||
|
processUpdate(update, c)
|
||||||
|
}
|
||||||
|
db!!.saveUsers(u.getUsers())
|
||||||
|
db!!.saveChats(u.getChats())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUpdateShort(c: TelegramClient, u: TLUpdateShort) {
|
||||||
|
if (db == null) return
|
||||||
|
if (debug) System.out.println("onUpdateShort")
|
||||||
|
processUpdate(u.getUpdate(), c)
|
||||||
|
if (debug) System.out.println(" " + u.getUpdate().getClass().getName())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onShortChatMessage(c: TelegramClient, m: TLUpdateShortChatMessage) {
|
||||||
|
if (db == null) return
|
||||||
|
if (debug) System.out.println("onShortChatMessage - " + m.getMessage())
|
||||||
|
val msg = TLMessage(
|
||||||
|
m.getOut(),
|
||||||
|
m.getMentioned(),
|
||||||
|
m.getMediaUnread(),
|
||||||
|
m.getSilent(),
|
||||||
|
false,
|
||||||
|
m.getId(),
|
||||||
|
m.getFromId(),
|
||||||
|
TLPeerChat(m.getChatId()),
|
||||||
|
m.getFwdFrom(),
|
||||||
|
m.getViaBotId(),
|
||||||
|
m.getReplyToMsgId(),
|
||||||
|
m.getDate(),
|
||||||
|
m.getMessage(), null, null,
|
||||||
|
m.getEntities(), null, null)
|
||||||
|
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
|
||||||
|
vector.add(msg)
|
||||||
|
db!!.saveMessages(vector, Kotlogram.API_LAYER)
|
||||||
|
System.out.print('.')
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onShortMessage(c: TelegramClient, m: TLUpdateShortMessage) {
|
||||||
|
if (db == null) return
|
||||||
|
if (debug) System.out.println("onShortMessage - " + m.getOut() + " - " + m.getUserId() + " - " + m.getMessage())
|
||||||
|
val from_id: Int
|
||||||
|
val to_id: Int
|
||||||
|
if (m.getOut() === true) {
|
||||||
|
from_id = user!!.getUser().getId()
|
||||||
|
to_id = m.getUserId()
|
||||||
|
} else {
|
||||||
|
to_id = user!!.getUser().getId()
|
||||||
|
from_id = m.getUserId()
|
||||||
|
}
|
||||||
|
val msg = TLMessage(
|
||||||
|
m.getOut(),
|
||||||
|
m.getMentioned(),
|
||||||
|
m.getMediaUnread(),
|
||||||
|
m.getSilent(),
|
||||||
|
false,
|
||||||
|
m.getId(),
|
||||||
|
from_id,
|
||||||
|
TLPeerUser(to_id),
|
||||||
|
m.getFwdFrom(),
|
||||||
|
m.getViaBotId(),
|
||||||
|
m.getReplyToMsgId(),
|
||||||
|
m.getDate(),
|
||||||
|
m.getMessage(), null, null,
|
||||||
|
m.getEntities(), null, null)
|
||||||
|
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
|
||||||
|
vector.add(msg)
|
||||||
|
db!!.saveMessages(vector, Kotlogram.API_LAYER)
|
||||||
|
System.out.print('.')
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onShortSentMessage(c: TelegramClient, m: TLUpdateShortSentMessage) {
|
||||||
|
if (db == null) return
|
||||||
|
System.out.println("onShortSentMessage")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUpdateTooLong(c: TelegramClient) {
|
||||||
|
if (db == null) return
|
||||||
|
System.out.println("onUpdateTooLong")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processUpdate(update: TLAbsUpdate, client: TelegramClient) {
|
||||||
|
if (update is TLUpdateNewMessage) {
|
||||||
|
val abs_msg = (update as TLUpdateNewMessage).getMessage()
|
||||||
|
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
|
||||||
|
vector.add(abs_msg)
|
||||||
|
db!!.saveMessages(vector, Kotlogram.API_LAYER)
|
||||||
|
System.out.print('.')
|
||||||
|
if (abs_msg is TLMessage) {
|
||||||
|
val fm = FileManagerFactory.getFileManager(abs_msg as TLMessage, user, client)
|
||||||
|
if (fm != null && !fm!!.isEmpty() && !fm!!.isDownloaded()) {
|
||||||
|
try {
|
||||||
|
fm!!.download()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
System.out.println("We got an exception while downloading media, but we're going to ignore it.")
|
||||||
|
System.out.println("Here it is anyway:")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ignore everything else...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.sql.DriverManager
|
||||||
|
import java.sql.Statement
|
||||||
|
import java.sql.SQLException
|
||||||
|
import java.sql.ResultSet
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
internal object TestFeatures {
|
||||||
|
fun test1() {
|
||||||
|
// Tests entries in a cache4.db in the current working directory for compatibility
|
||||||
|
try {
|
||||||
|
Class.forName("org.sqlite.JDBC")
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
CommandLineController.show_error("Could not load jdbc-sqlite class.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = "jdbc:sqlite:cache4.db"
|
||||||
|
|
||||||
|
var conn: Connection? = null
|
||||||
|
var stmt: Statement? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn = DriverManager.getConnection(path)
|
||||||
|
stmt = conn!!.createStatement()
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
CommandLineController.show_error("Could not connect to SQLITE database.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var unsupported_constructor = 0
|
||||||
|
var success = 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
val rs = stmt!!.executeQuery("SELECT data FROM messages")
|
||||||
|
while (rs.next()) {
|
||||||
|
try {
|
||||||
|
TLApiContext.getInstance().deserializeMessage(rs.getBytes(1))
|
||||||
|
} catch (e: com.github.badoualy.telegram.tl.exception.UnsupportedConstructorException) {
|
||||||
|
unsupported_constructor++
|
||||||
|
} catch (e: IOException) {
|
||||||
|
System.out.println("IOException: " + e)
|
||||||
|
}
|
||||||
|
|
||||||
|
success++
|
||||||
|
}
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
System.out.println("SQL exception: " + e)
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Success: " + success)
|
||||||
|
System.out.println("Unsupported constructor: " + unsupported_constructor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test2(user: UserManager, client: TelegramClient) {
|
||||||
|
// Prints system.encoding and default charset
|
||||||
|
System.out.println("Default Charset: " + Charset.defaultCharset())
|
||||||
|
System.out.println("file.encoding: " + System.getProperty("file.encoding"))
|
||||||
|
val db = Database.getInstance()
|
||||||
|
System.out.println("Database encoding: " + db.getEncoding())
|
||||||
|
}
|
||||||
|
}
|
140
src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt
Normal file
140
src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.api.auth.TLSentCode
|
||||||
|
import com.github.badoualy.telegram.tl.api.auth.TLAuthorization
|
||||||
|
import com.github.badoualy.telegram.tl.api.TLUser
|
||||||
|
import com.github.badoualy.telegram.tl.api.TLUserFull
|
||||||
|
import com.github.badoualy.telegram.tl.api.TLInputUserSelf
|
||||||
|
import com.github.badoualy.telegram.tl.api.account.TLPassword
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLBytes
|
||||||
|
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.Logger
|
||||||
|
|
||||||
|
class UserManager @Throws(IOException::class)
|
||||||
|
private constructor(c: TelegramClient) {
|
||||||
|
var user: TLUser? = null
|
||||||
|
var phone: String? = null
|
||||||
|
private var code: String? = null
|
||||||
|
private val client: TelegramClient? = null
|
||||||
|
private var sent_code: TLSentCode? = null
|
||||||
|
private var auth: TLAuthorization? = null
|
||||||
|
var isPasswordNeeded = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
val isLoggedIn: Boolean
|
||||||
|
get() = user != null
|
||||||
|
|
||||||
|
val userString: String
|
||||||
|
get() {
|
||||||
|
if (this.user == null) return "Not logged in"
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (this.user!!.getFirstName() != null) {
|
||||||
|
sb.append(this.user!!.getFirstName())
|
||||||
|
}
|
||||||
|
if (this.user!!.getLastName() != null) {
|
||||||
|
sb.append(" ")
|
||||||
|
sb.append(this.user!!.getLastName())
|
||||||
|
}
|
||||||
|
if (this.user!!.getUsername() != null) {
|
||||||
|
sb.append(" (@")
|
||||||
|
sb.append(this.user!!.getUsername())
|
||||||
|
sb.append(")")
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileBase: String
|
||||||
|
get() = Config.FILE_BASE + File.separatorChar + "+" + this.user!!.getPhone() + File.separatorChar
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.client = c
|
||||||
|
logger.debug("Calling getFullUser")
|
||||||
|
try {
|
||||||
|
val full_user = this.client!!.usersGetFullUser(TLInputUserSelf())
|
||||||
|
this.user = full_user.getUser().getAsUser()
|
||||||
|
} catch (e: RpcErrorException) {
|
||||||
|
// This may happen. Ignoring it.
|
||||||
|
logger.debug("Ignoring exception:", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class)
|
||||||
|
fun sendCodeToPhoneNumber(number: String) {
|
||||||
|
this.phone = number
|
||||||
|
this.sent_code = this.client!!.authSendCode(false, this.phone, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class)
|
||||||
|
fun verifyCode(code: String) {
|
||||||
|
this.code = code
|
||||||
|
try {
|
||||||
|
this.auth = client!!.authSignIn(phone, this.sent_code!!.getPhoneCodeHash(), this.code)
|
||||||
|
this.user = auth!!.getUser().getAsUser()
|
||||||
|
} catch (e: RpcErrorException) {
|
||||||
|
if (e.getCode() !== 401 || !e.getTag().equals("SESSION_PASSWORD_NEEDED")) throw e
|
||||||
|
this.isPasswordNeeded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class)
|
||||||
|
fun verifyPassword(pw: String) {
|
||||||
|
val password = pw.getBytes("UTF-8")
|
||||||
|
val salt = (client!!.accountGetPassword() as TLPassword).getCurrentSalt().getData()
|
||||||
|
var md: MessageDigest? = null
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("SHA-256")
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val salted = ByteArray(2 * salt.size + password.size)
|
||||||
|
System.arraycopy(salt, 0, salted, 0, salt.size)
|
||||||
|
System.arraycopy(password, 0, salted, salt.size, password.size)
|
||||||
|
System.arraycopy(salt, 0, salted, salt.size + password.size, salt.size)
|
||||||
|
val hash = md!!.digest(salted)
|
||||||
|
auth = client!!.authCheckPassword(TLBytes(hash))
|
||||||
|
this.user = auth!!.getUser().getAsUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(UserManager::class.java)
|
||||||
|
private var instance: UserManager? = null
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun init(c: TelegramClient) {
|
||||||
|
instance = UserManager(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(): UserManager {
|
||||||
|
if (instance == null) throw RuntimeException("UserManager is not yet initialized.")
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
184
src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt
Normal file
184
src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Vector
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import com.google.gson.*
|
||||||
|
import java.net.URL
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
|
import de.fabianonline.telegram_backup.Version
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
object Utils {
|
||||||
|
val VERSIONS_EQUAL = 0
|
||||||
|
val VERSION_1_NEWER = 1
|
||||||
|
val VERSION_2_NEWER = 2
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(Utils::class.java) as Logger
|
||||||
|
|
||||||
|
internal val accounts: Vector<String>
|
||||||
|
get() {
|
||||||
|
val accounts = Vector<String>()
|
||||||
|
val folder = File(Config.FILE_BASE)
|
||||||
|
val files = folder.listFiles()
|
||||||
|
if (files != null)
|
||||||
|
for (f in files!!) {
|
||||||
|
if (f.isDirectory() && f.getName().startsWith("+")) {
|
||||||
|
accounts.add(f.getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val newestVersion: Version?
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val data_url = "https://api.github.com/repos/fabianonline/telegram_backup/releases"
|
||||||
|
logger.debug("Requesting current release info from {}", data_url)
|
||||||
|
val json = IOUtils.toString(URL(data_url))
|
||||||
|
val parser = JsonParser()
|
||||||
|
val root_elm = parser.parse(json)
|
||||||
|
if (root_elm.isJsonArray()) {
|
||||||
|
val root = root_elm.getAsJsonArray()
|
||||||
|
var newest_version: JsonObject? = null
|
||||||
|
for (e in root)
|
||||||
|
if (e.isJsonObject()) {
|
||||||
|
val version = e.getAsJsonObject()
|
||||||
|
if (version.getAsJsonPrimitive("prerelease").getAsBoolean() === false) {
|
||||||
|
newest_version = version
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newest_version == null) return null
|
||||||
|
val new_v = newest_version!!.getAsJsonPrimitive("tag_name").getAsString()
|
||||||
|
logger.debug("Found current release version {}", new_v)
|
||||||
|
val cur_v = Config.APP_APPVER
|
||||||
|
|
||||||
|
val result = compareVersions(cur_v, new_v)
|
||||||
|
|
||||||
|
return Version(new_v, newest_version!!.getAsJsonPrimitive("html_url").getAsString(), newest_version!!.getAsJsonPrimitive("body").getAsString(), result == VERSION_2_NEWER)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class)
|
||||||
|
@JvmOverloads internal fun obeyFloodWaitException(e: RpcErrorException?, silent: Boolean = false) {
|
||||||
|
if (e == null || e!!.getCode() !== 420) return
|
||||||
|
|
||||||
|
val delay = e!!.getTagInteger()
|
||||||
|
if (!silent) {
|
||||||
|
System.out.println("")
|
||||||
|
System.out.println(
|
||||||
|
"Telegram complained about us (okay, me) making too many requests in too short time by\n" +
|
||||||
|
"sending us \"" + e!!.getTag() + "\" as an error. So we now have to wait a bit. Telegram\n" +
|
||||||
|
"asked us to wait for " + delay + " seconds.\n" +
|
||||||
|
"\n" +
|
||||||
|
"So I'm going to do just that for now. If you don't want to wait, you can quit by pressing\n" +
|
||||||
|
"Ctrl+C. You can restart me at any time and I will just continue to download your\n" +
|
||||||
|
"messages and media. But be advised that just restarting me is not going to change\n" +
|
||||||
|
"the fact that Telegram won't talk to me until then.")
|
||||||
|
System.out.println("")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(delay + 1)
|
||||||
|
} catch (e2: InterruptedException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compareVersions(v1: String, v2: String): Int {
|
||||||
|
logger.debug("Comparing versions {} and {}.", v1, v2)
|
||||||
|
if (v1.equals(v2)) return VERSIONS_EQUAL
|
||||||
|
|
||||||
|
val v1_p = v1.split("-", 2)
|
||||||
|
val v2_p = v2.split("-", 2)
|
||||||
|
|
||||||
|
logger.trace("Parts to compare without suffixes: {} and {}.", v1_p[0], v2_p[0])
|
||||||
|
|
||||||
|
val v1_p2 = v1_p[0].split("\\.")
|
||||||
|
val v2_p2 = v2_p[0].split("\\.")
|
||||||
|
|
||||||
|
logger.trace("Length of the parts without suffixes: {} and {}.", v1_p2.size, v2_p2.size)
|
||||||
|
|
||||||
|
var i: Int
|
||||||
|
i = 0
|
||||||
|
while (i < v1_p2.size && i < v2_p2.size) {
|
||||||
|
val i_1 = Integer.parseInt(v1_p2[i])
|
||||||
|
val i_2 = Integer.parseInt(v2_p2[i])
|
||||||
|
logger.trace("Comparing parts: {} and {}.", i_1, i_2)
|
||||||
|
if (i_1 > i_2) {
|
||||||
|
logger.debug("v1 is newer")
|
||||||
|
return VERSION_1_NEWER
|
||||||
|
} else if (i_2 > i_1) {
|
||||||
|
logger.debug("v2 is newer")
|
||||||
|
return VERSION_2_NEWER
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
logger.trace("At least one of the versions has run out of parts.")
|
||||||
|
if (v1_p2.size > v2_p2.size) {
|
||||||
|
logger.debug("v1 is longer, so it is newer")
|
||||||
|
return VERSION_1_NEWER
|
||||||
|
} else if (v2_p2.size > v1_p2.size) {
|
||||||
|
logger.debug("v2 is longer, so it is newer")
|
||||||
|
return VERSION_2_NEWER
|
||||||
|
}
|
||||||
|
|
||||||
|
// startsWith
|
||||||
|
if (v1_p.size > 1 && v2_p.size == 1) {
|
||||||
|
logger.debug("v1 has a suffix, v2 not.")
|
||||||
|
if (v1_p[1].startsWith("pre")) {
|
||||||
|
logger.debug("v1 is a pre version, so v1 is newer")
|
||||||
|
return VERSION_2_NEWER
|
||||||
|
} else {
|
||||||
|
return VERSION_1_NEWER
|
||||||
|
}
|
||||||
|
} else if (v1_p.size == 1 && v2_p.size > 1) {
|
||||||
|
logger.debug("v1 has no suffix, but v2 has")
|
||||||
|
if (v2_p[1].startsWith("pre")) {
|
||||||
|
logger.debug("v2 is a pre version, so v1 is better")
|
||||||
|
return VERSION_1_NEWER
|
||||||
|
} else {
|
||||||
|
return VERSION_2_NEWER
|
||||||
|
}
|
||||||
|
} else if (v1_p.size > 1 && v2_p.size > 1) {
|
||||||
|
logger.debug("Both have a suffix")
|
||||||
|
if (v1_p[1].startsWith("pre") && !v2_p[1].startsWith("pre")) {
|
||||||
|
logger.debug("v1 is a 'pre' version, v2 not.")
|
||||||
|
return VERSION_2_NEWER
|
||||||
|
} else if (!v1_p[1].startsWith("pre") && v2_p[1].startsWith("pre")) {
|
||||||
|
logger.debug("v2 is a 'pre' version, v2 not.")
|
||||||
|
return VERSION_1_NEWER
|
||||||
|
}
|
||||||
|
return VERSIONS_EQUAL
|
||||||
|
}
|
||||||
|
logger.debug("We couldn't find a real difference, so we're assuming the versions are equal-ish.")
|
||||||
|
return VERSIONS_EQUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
fun anonymize(str: String): String {
|
||||||
|
return if (!CommandLineOptions.cmd_anonymize) str else str.replaceAll("[0-9]", "1").replaceAll("[A-Z]", "A").replaceAll("[a-z]", "a") + " (ANONYMIZED)"
|
||||||
|
}
|
||||||
|
}
|
@ -14,18 +14,6 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
package de.fabianonline.telegram_backup;
|
package de.fabianonline.telegram_backup
|
||||||
|
|
||||||
public class Version {
|
class Version(val version: String, val url: String, val body: String, val isNewer: Boolean)
|
||||||
public final String version;
|
|
||||||
public final String url;
|
|
||||||
public final String body;
|
|
||||||
public final boolean isNewer;
|
|
||||||
|
|
||||||
public Version(String v, String u, String b, boolean n) {
|
|
||||||
this.version = v;
|
|
||||||
this.url = u;
|
|
||||||
this.body = b;
|
|
||||||
this.isNewer = n;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,186 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.exporter
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.Utils
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.net.URL
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
import com.github.mustachejava.DefaultMustacheFactory
|
||||||
|
import com.github.mustachejava.Mustache
|
||||||
|
import com.github.mustachejava.MustacheFactory
|
||||||
|
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
class HTMLExporter {
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun export() {
|
||||||
|
try {
|
||||||
|
val user = UserManager.getInstance()
|
||||||
|
val db = Database.getInstance()
|
||||||
|
|
||||||
|
// Create base dir
|
||||||
|
logger.debug("Creating base dir")
|
||||||
|
val base = user.getFileBase() + "files" + File.separatorChar
|
||||||
|
File(base).mkdirs()
|
||||||
|
File(base + "dialogs").mkdirs()
|
||||||
|
|
||||||
|
logger.debug("Fetching dialogs")
|
||||||
|
val dialogs = db.getListOfDialogsForExport()
|
||||||
|
logger.trace("Got {} dialogs", dialogs.size())
|
||||||
|
logger.debug("Fetching chats")
|
||||||
|
val chats = db.getListOfChatsForExport()
|
||||||
|
logger.trace("Got {} chats", chats.size())
|
||||||
|
|
||||||
|
logger.debug("Generating index.html")
|
||||||
|
val scope = HashMap<String, Object>()
|
||||||
|
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())
|
||||||
|
|
||||||
|
var count_messages_chats = 0
|
||||||
|
var count_messages_dialogs = 0
|
||||||
|
for (c in chats) count_messages_chats += c.count
|
||||||
|
for (d in 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())
|
||||||
|
|
||||||
|
val mf = DefaultMustacheFactory()
|
||||||
|
var mustache = mf.compile("templates/html/index.mustache")
|
||||||
|
var w = getWriter(base + "index.html")
|
||||||
|
mustache.execute(w, scope)
|
||||||
|
w.close()
|
||||||
|
|
||||||
|
mustache = mf.compile("templates/html/chat.mustache")
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
logger.debug("Generating {} dialog pages", dialogs.size())
|
||||||
|
for (d in dialogs) {
|
||||||
|
i++
|
||||||
|
logger.trace("Dialog {}/{}: {}", i, dialogs.size(), Utils.anonymize("" + d.id))
|
||||||
|
val messages = db.getMessagesForExport(d)
|
||||||
|
scope.clear()
|
||||||
|
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 (c in chats) {
|
||||||
|
i++
|
||||||
|
logger.trace("Chat {}/{}: {}", i, chats.size(), Utils.anonymize("" + c.id))
|
||||||
|
val messages = db.getMessagesForExport(c)
|
||||||
|
scope.clear()
|
||||||
|
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
|
||||||
|
val cssFile = getClass().getResource("/templates/html/style.css")
|
||||||
|
val dest = File(base + "style.css")
|
||||||
|
FileUtils.copyURLToFile(cssFile, dest)
|
||||||
|
logger.debug("Done exporting.")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
logger.error("Caught an exception!", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(FileNotFoundException::class)
|
||||||
|
private fun getWriter(filename: String): OutputStreamWriter {
|
||||||
|
logger.trace("Creating writer for file {}", Utils.anonymize(filename))
|
||||||
|
return OutputStreamWriter(FileOutputStream(filename), Charset.forName("UTF-8").newEncoder())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun intArrayToString(data: Array<IntArray>): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append("[")
|
||||||
|
for (x in data.indices) {
|
||||||
|
for (y in 0 until data[x].size) {
|
||||||
|
if (x > 0 || y > 0) sb.append(",")
|
||||||
|
sb.append("[" + x + "," + y + "," + data[x][y] + "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("]")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapToString(map: Map<String, Integer>): String {
|
||||||
|
val sb = StringBuilder("[")
|
||||||
|
for (entry in map.entrySet()) {
|
||||||
|
sb.append("['" + entry.getKey() + "', " + entry.getValue() + "],")
|
||||||
|
}
|
||||||
|
sb.append("]")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(HTMLExporter::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.mediafilemanager
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.Config
|
||||||
|
import de.fabianonline.telegram_backup.DownloadManager
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
abstract class AbstractMediaFileManager(protected var message: TLMessage, protected var user: UserManager, protected var client: TelegramClient) {
|
||||||
|
var isEmpty = false
|
||||||
|
protected set
|
||||||
|
abstract val size: Int
|
||||||
|
abstract val extension: String
|
||||||
|
val isDownloaded: Boolean
|
||||||
|
get() = File(targetPathAndFilename).isFile()
|
||||||
|
val isDownloading: Boolean
|
||||||
|
get() = File(targetPathAndFilename + ".downloading").isFile()
|
||||||
|
val targetPath: String
|
||||||
|
get() {
|
||||||
|
val path = user.getFileBase() + Config.FILE_FILES_BASE + File.separatorChar
|
||||||
|
File(path).mkdirs()
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
val targetFilename: String
|
||||||
|
get() = if (message.getToId() is TLPeerChannel) {
|
||||||
|
"channel_" + (message.getToId() as TLPeerChannel).getChannelId() + "_" + message.getId() + "." + extension
|
||||||
|
} else "" + message.getId() + "." + extension
|
||||||
|
val targetPathAndFilename: String
|
||||||
|
get() = targetPath + targetFilename
|
||||||
|
|
||||||
|
abstract val letter: String
|
||||||
|
abstract val name: String
|
||||||
|
abstract val description: String
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
abstract fun download()
|
||||||
|
|
||||||
|
protected fun extensionFromMimetype(mime: String): String {
|
||||||
|
when (mime) {
|
||||||
|
"text/plain" -> return "txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
val i = mime.lastIndexOf('/')
|
||||||
|
val ext = mime.substring(i + 1).toLowerCase()
|
||||||
|
|
||||||
|
return if (ext === "unknown") "dat" else ext
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun throwUnexpectedObjectError(o: Object) {
|
||||||
|
throw RuntimeException("Unexpected " + o.getClass().getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.mediafilemanager
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.DownloadManager
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
class DocumentFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
|
||||||
|
protected var doc: TLDocument? = null
|
||||||
|
private var extension: String? = null
|
||||||
|
|
||||||
|
val isSticker: Boolean
|
||||||
|
get() {
|
||||||
|
var sticker: TLDocumentAttributeSticker? = null
|
||||||
|
if (this.isEmpty || doc == null) return false
|
||||||
|
if (doc!!.getAttributes() != null)
|
||||||
|
for (attr in doc!!.getAttributes()) {
|
||||||
|
if (attr is TLDocumentAttributeSticker) {
|
||||||
|
sticker = attr as TLDocumentAttributeSticker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sticker != null
|
||||||
|
}
|
||||||
|
|
||||||
|
val size: Int
|
||||||
|
get() = if (doc != null) doc!!.getSize() else 0
|
||||||
|
|
||||||
|
val letter: String
|
||||||
|
get() = "d"
|
||||||
|
val name: String
|
||||||
|
get() = "document"
|
||||||
|
val description: String
|
||||||
|
get() = "Document"
|
||||||
|
|
||||||
|
init {
|
||||||
|
val d = (msg.getMedia() as TLMessageMediaDocument).getDocument()
|
||||||
|
if (d is TLDocument) {
|
||||||
|
this.doc = d as TLDocument
|
||||||
|
} else if (d is TLDocumentEmpty) {
|
||||||
|
this.isEmpty = true
|
||||||
|
} else {
|
||||||
|
throwUnexpectedObjectError(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getExtension(): String? {
|
||||||
|
if (extension != null) return extension
|
||||||
|
if (doc == null) return "empty"
|
||||||
|
var ext: String? = null
|
||||||
|
var original_filename: String? = null
|
||||||
|
if (doc!!.getAttributes() != null)
|
||||||
|
for (attr in doc!!.getAttributes()) {
|
||||||
|
if (attr is TLDocumentAttributeFilename) {
|
||||||
|
original_filename = (attr as TLDocumentAttributeFilename).getFileName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (original_filename != null) {
|
||||||
|
val i = original_filename.lastIndexOf('.')
|
||||||
|
if (i > 0) ext = original_filename.substring(i + 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
if (ext == null) {
|
||||||
|
ext = extensionFromMimetype(doc!!.getMimeType())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sometimes, extensions contain a trailing double quote. Remove this. Fixes #12.
|
||||||
|
ext = ext!!.replace("\"", "")
|
||||||
|
|
||||||
|
this.extension = ext
|
||||||
|
return ext
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
fun download() {
|
||||||
|
if (doc != null) {
|
||||||
|
DownloadManager.downloadFile(client, getTargetPathAndFilename(), size, doc!!.getDcId(), doc!!.getId(), doc!!.getAccessHash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.mediafilemanager
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
object FileManagerFactory {
|
||||||
|
fun getFileManager(m: TLMessage?, u: UserManager, c: TelegramClient): AbstractMediaFileManager? {
|
||||||
|
if (m == null) return null
|
||||||
|
val media = m!!.getMedia() ?: return null
|
||||||
|
|
||||||
|
if (media is TLMessageMediaPhoto) {
|
||||||
|
return PhotoFileManager(m, u, c)
|
||||||
|
} else if (media is TLMessageMediaDocument) {
|
||||||
|
val d = DocumentFileManager(m, u, c)
|
||||||
|
return if (d.isSticker()) {
|
||||||
|
StickerFileManager(m, u, c)
|
||||||
|
} else d
|
||||||
|
} else if (media is TLMessageMediaGeo) {
|
||||||
|
return GeoFileManager(m, u, c)
|
||||||
|
} else if (media is TLMessageMediaEmpty) {
|
||||||
|
return UnsupportedFileManager(m, u, c, "empty")
|
||||||
|
} else if (media is TLMessageMediaUnsupported) {
|
||||||
|
return UnsupportedFileManager(m, u, c, "unsupported")
|
||||||
|
} else if (media is TLMessageMediaWebPage) {
|
||||||
|
return UnsupportedFileManager(m, u, c, "webpage")
|
||||||
|
} else if (media is TLMessageMediaContact) {
|
||||||
|
return UnsupportedFileManager(m, u, c, "contact")
|
||||||
|
} else if (media is TLMessageMediaVenue) {
|
||||||
|
return UnsupportedFileManager(m, u, c, "venue")
|
||||||
|
} else {
|
||||||
|
AbstractMediaFileManager.throwUnexpectedObjectError(media)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.mediafilemanager
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.DownloadManager
|
||||||
|
import de.fabianonline.telegram_backup.Config
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
class GeoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
|
||||||
|
protected var geo: TLGeoPoint
|
||||||
|
|
||||||
|
// We don't know the size, so we just guess.
|
||||||
|
val size: Int
|
||||||
|
get() {
|
||||||
|
val f = File(getTargetPathAndFilename())
|
||||||
|
return if (f.isFile()) f.length() else 100000
|
||||||
|
}
|
||||||
|
|
||||||
|
val extension: String
|
||||||
|
get() = "png"
|
||||||
|
|
||||||
|
val letter: String
|
||||||
|
get() = "g"
|
||||||
|
val name: String
|
||||||
|
get() = "geo"
|
||||||
|
val description: String
|
||||||
|
get() = "Geolocation"
|
||||||
|
|
||||||
|
init {
|
||||||
|
val g = (msg.getMedia() as TLMessageMediaGeo).getGeo()
|
||||||
|
if (g is TLGeoPoint) {
|
||||||
|
this.geo = g as TLGeoPoint
|
||||||
|
} else if (g is TLGeoPointEmpty) {
|
||||||
|
this.isEmpty = true
|
||||||
|
} else {
|
||||||
|
throwUnexpectedObjectError(g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun download() {
|
||||||
|
val url = "https://maps.googleapis.com/maps/api/staticmap?" +
|
||||||
|
"center=" + geo.getLat() + "," + geo.getLong() + "&" +
|
||||||
|
"zoom=14&size=300x150&scale=2&format=png&" +
|
||||||
|
"key=" + Config.SECRET_GMAPS
|
||||||
|
DownloadManager.downloadExternalFile(getTargetPathAndFilename(), url)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.mediafilemanager
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.DownloadManager
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
class PhotoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
|
||||||
|
private var photo: TLPhoto? = null
|
||||||
|
private var size: TLPhotoSize? = null
|
||||||
|
|
||||||
|
val extension: String
|
||||||
|
get() = "jpg"
|
||||||
|
|
||||||
|
val letter: String
|
||||||
|
get() = "p"
|
||||||
|
val name: String
|
||||||
|
get() = "photo"
|
||||||
|
val description: String
|
||||||
|
get() = "Photo"
|
||||||
|
|
||||||
|
init {
|
||||||
|
val p = (msg.getMedia() as TLMessageMediaPhoto).getPhoto()
|
||||||
|
if (p is TLPhoto) {
|
||||||
|
this.photo = p as TLPhoto
|
||||||
|
|
||||||
|
var biggest: TLPhotoSize? = null
|
||||||
|
for (s in photo!!.getSizes())
|
||||||
|
if (s is TLPhotoSize) {
|
||||||
|
val size = s as TLPhotoSize
|
||||||
|
if (biggest == null || size.getW() > biggest!!.getW() && size.getH() > biggest!!.getH()) {
|
||||||
|
biggest = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (biggest == null) {
|
||||||
|
throw RuntimeException("Could not find a size for a photo.")
|
||||||
|
}
|
||||||
|
this.size = biggest
|
||||||
|
} else if (p is TLPhotoEmpty) {
|
||||||
|
this.isEmpty = true
|
||||||
|
} else {
|
||||||
|
throwUnexpectedObjectError(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSize(): Int {
|
||||||
|
return if (size != null) size!!.getSize() else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
fun download() {
|
||||||
|
if (isEmpty) return
|
||||||
|
val loc = size!!.getLocation() as TLFileLocation
|
||||||
|
DownloadManager.downloadFile(client, getTargetPathAndFilename(), getSize(), loc.getDcId(), loc.getVolumeId(), loc.getLocalId(), loc.getSecret())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.mediafilemanager
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.DownloadManager
|
||||||
|
import de.fabianonline.telegram_backup.Config
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||||
|
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : DocumentFileManager(msg, user, client) {
|
||||||
|
|
||||||
|
val isSticker: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
private val filenameBase: String
|
||||||
|
get() {
|
||||||
|
var sticker: TLDocumentAttributeSticker? = null
|
||||||
|
for (attr in doc.getAttributes()) {
|
||||||
|
if (attr is TLDocumentAttributeSticker) {
|
||||||
|
sticker = attr as TLDocumentAttributeSticker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = StringBuilder()
|
||||||
|
if (sticker!!.getStickerset() is TLInputStickerSetShortName) {
|
||||||
|
file.append((sticker!!.getStickerset() as TLInputStickerSetShortName).getShortName())
|
||||||
|
} else if (sticker!!.getStickerset() is TLInputStickerSetID) {
|
||||||
|
file.append((sticker!!.getStickerset() as TLInputStickerSetID).getId())
|
||||||
|
}
|
||||||
|
file.append("_")
|
||||||
|
file.append(sticker!!.getAlt().hashCode())
|
||||||
|
return file.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetFilename: String
|
||||||
|
get() = filenameBase + "." + extension
|
||||||
|
|
||||||
|
val targetPath: String
|
||||||
|
get() {
|
||||||
|
val path = user.getFileBase() + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar
|
||||||
|
File(path).mkdirs()
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
val extension: String
|
||||||
|
get() = "webp"
|
||||||
|
|
||||||
|
val letter: String
|
||||||
|
get() = "s"
|
||||||
|
val name: String
|
||||||
|
get() = "sticker"
|
||||||
|
val description: String
|
||||||
|
get() = "Sticker"
|
||||||
|
|
||||||
|
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||||
|
fun download() {
|
||||||
|
val old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + targetFilename
|
||||||
|
|
||||||
|
logger.trace("Old filename exists: {}", File(old_file).exists())
|
||||||
|
|
||||||
|
if (File(old_file).exists()) {
|
||||||
|
Files.copy(Paths.get(old_file), Paths.get(getTargetPathAndFilename()), StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.download()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(StickerFileManager::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/* 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 <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package de.fabianonline.telegram_backup.mediafilemanager
|
||||||
|
|
||||||
|
import de.fabianonline.telegram_backup.UserManager
|
||||||
|
import de.fabianonline.telegram_backup.Database
|
||||||
|
import de.fabianonline.telegram_backup.StickerConverter
|
||||||
|
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||||
|
import de.fabianonline.telegram_backup.DownloadManager
|
||||||
|
import de.fabianonline.telegram_backup.Config
|
||||||
|
|
||||||
|
import com.github.badoualy.telegram.api.TelegramClient
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||||
|
import com.github.badoualy.telegram.tl.core.TLObject
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||||
|
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||||
|
import com.github.badoualy.telegram.tl.api.*
|
||||||
|
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||||
|
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||||
|
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
class UnsupportedFileManager(msg: TLMessage, user: UserManager, client: TelegramClient, type: String) : AbstractMediaFileManager(msg, user, client) {
|
||||||
|
var name: String? = null
|
||||||
|
internal set
|
||||||
|
|
||||||
|
val targetFilename: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
val targetPath: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
val extension: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
val size: Int
|
||||||
|
get() = 0
|
||||||
|
|
||||||
|
val isEmpty: Boolean
|
||||||
|
get() = false
|
||||||
|
val isDownloaded: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
val letter: String
|
||||||
|
get() = " "
|
||||||
|
val description: String
|
||||||
|
get() = "Unsupported / non-downloadable Media"
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.name = type
|
||||||
|
}
|
||||||
|
|
||||||
|
fun download() {}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user