mirror of
https://github.com/fabianonline/telegram_backup.git
synced 2024-12-25 22:35:35 +00:00
Added a simple 51converter to convert messages with api layer 51 to JSON.
This commit is contained in:
parent
f5d8b0c2e1
commit
803919ca4a
@ -11,7 +11,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('com.github.badoualy:kotlogram:666a81ef9d6707f117a3fecc2d21c91d51c7d075') {
|
||||
compile('com.github.badoualy:kotlogram:0.0.6') {
|
||||
exclude module: 'slf4j-simple'
|
||||
}
|
||||
compile 'org.xerial:sqlite-jdbc:3.16.1'
|
||||
|
@ -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; }
|
||||
}
|
@ -16,10 +16,6 @@
|
||||
|
||||
package de.fabianonline.telegram_backup;
|
||||
|
||||
import de.fabianonline.telegram_backup.TelegramUpdateHandler;
|
||||
import de.fabianonline.telegram_backup.exporter.HTMLExporter;
|
||||
import de.fabianonline.telegram_backup.models.Message;
|
||||
|
||||
import com.github.badoualy.telegram.api.Kotlogram;
|
||||
import com.github.badoualy.telegram.api.TelegramApp;
|
||||
import com.github.badoualy.telegram.api.TelegramClient;
|
||||
@ -38,7 +34,6 @@ import org.slf4j.Logger;
|
||||
|
||||
public class CommandLineController {
|
||||
private static Logger logger = LoggerFactory.getLogger(CommandLineController.class);
|
||||
private ApiStorage storage;
|
||||
public TelegramApp app;
|
||||
|
||||
public CommandLineController() {
|
||||
@ -70,103 +65,29 @@ public class CommandLineController {
|
||||
|
||||
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);
|
||||
TelegramClient client = null; //Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler);
|
||||
|
||||
try {
|
||||
logger.info("Initializing UserManager");
|
||||
UserManager.init(client);
|
||||
UserManager.init(account);
|
||||
Database.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");
|
||||
}
|
||||
}
|
||||
// do stuff
|
||||
Database.getInstance().jsonify();
|
||||
|
||||
if (CommandLineOptions.cmd_stats) {
|
||||
cmd_stats();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login);
|
||||
if (CommandLineOptions.cmd_login) {
|
||||
cmd_login(account);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (user.isLoggedIn()) {
|
||||
System.out.println("You are logged in as " + Utils.anonymize(user.getUserString()));
|
||||
} else {
|
||||
System.out.println("You are not logged in.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
logger.info("Initializing Download Manager");
|
||||
|
||||
if (CommandLineOptions.val_test != null) {
|
||||
if (CommandLineOptions.val_test == 1) {
|
||||
TestFeatures.test1();
|
||||
} else if (CommandLineOptions.val_test == 2) {
|
||||
TestFeatures.test2(user, client);
|
||||
} else if (CommandLineOptions.val_test == 3) {
|
||||
logger.debug(Message.get(39925).getMessage());
|
||||
} else {
|
||||
System.out.println("Unknown test " + CommandLineOptions.val_test);
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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("Telegram_Backup 51convert 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.");
|
||||
@ -221,18 +142,6 @@ public class CommandLineController {
|
||||
return account;
|
||||
}
|
||||
|
||||
private void cmd_stats() {
|
||||
HashMap<String, Integer> map = new HashMap<String, Integer>();
|
||||
map.put("count.accounts", Utils.getAccounts().size());
|
||||
map.put("count.messages", Database.getInstance().getMessageCount());
|
||||
map.put("messages.top_id", Database.getInstance().getTopMessageID());
|
||||
for(Map.Entry<String, Integer> pair : Database.getInstance().getMessageMediaTypesWithCount().entrySet()) {
|
||||
map.put(pair.getKey(), pair.getValue());
|
||||
}
|
||||
|
||||
System.out.println(map.toString());
|
||||
}
|
||||
|
||||
private void cmd_login(String phone) throws RpcErrorException, IOException {
|
||||
UserManager user = UserManager.getInstance();
|
||||
if (phone==null) {
|
||||
@ -251,7 +160,6 @@ public class CommandLineController {
|
||||
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.");
|
||||
}
|
||||
@ -275,22 +183,6 @@ public class CommandLineController {
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
private void list_accounts() {
|
||||
|
@ -1,58 +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) { i=0; System.out.println("Downloading " + count + " messages."); }
|
||||
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); }
|
||||
}
|
||||
|
@ -75,18 +75,6 @@ public class CommandLineRunner {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import com.github.badoualy.telegram.api.TelegramClient;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import com.google.gson.Gson;
|
||||
import com.github.badoualy.telegram.api.Kotlogram;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
@ -45,9 +46,6 @@ 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;
|
||||
@ -77,10 +75,6 @@ public class Database {
|
||||
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.");
|
||||
}
|
||||
|
||||
@ -93,537 +87,6 @@ public class Database {
|
||||
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(id) FROM messages");
|
||||
rs.next();
|
||||
return rs.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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 getMessageCount() {
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM messages");
|
||||
rs.next();
|
||||
return rs.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Could not get count of messages.");
|
||||
}
|
||||
}
|
||||
|
||||
public LinkedList<Integer> getMissingIDs() {
|
||||
try {
|
||||
LinkedList<Integer> missing = new LinkedList<Integer>();
|
||||
int max = getTopMessageID();
|
||||
ResultSet rs = stmt.executeQuery("SELECT id FROM messages 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, Gson gson) {
|
||||
try {
|
||||
//"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " +
|
||||
//"VALUES " +
|
||||
//"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
String columns =
|
||||
"(id, message_type, dialog_id, chat_id, sender_id, fwd_from_id, text, time, has_media, media_type, media_file, media_size, data, api_layer, json) "+
|
||||
"VALUES " +
|
||||
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
//1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
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.setNull(3, Types.INTEGER);
|
||||
ps.setInt(4, ((TLPeerChat)peer).getChatId());
|
||||
} else if (peer instanceof TLPeerUser) {
|
||||
int id = ((TLPeerUser)peer).getUserId();
|
||||
if (id==this.user_manager.getUser().getId()) {
|
||||
id = msg.getFromId();
|
||||
}
|
||||
ps.setInt(3, id);
|
||||
ps.setNull(4, Types.INTEGER);
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected Peer type: " + peer.getClass().getName());
|
||||
}
|
||||
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.setString(15, gson.toJson(msg));
|
||||
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.setString(15, gson.toJson((TLMessageService)abs));
|
||||
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.setString(15, gson.toJson((TLMessageEmpty)abs));
|
||||
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, Gson gson) {
|
||||
try {
|
||||
PreparedStatement ps_insert_or_replace = conn.prepareStatement(
|
||||
"INSERT OR REPLACE INTO chats " +
|
||||
"(id, name, type, json) "+
|
||||
"VALUES " +
|
||||
"(?, ?, ?, ?)");
|
||||
PreparedStatement ps_insert_or_ignore = conn.prepareStatement(
|
||||
"INSERT OR IGNORE INTO chats " +
|
||||
"(id, name, type, json) "+
|
||||
"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.setString(4, gson.toJson((TLChatEmpty)abs));
|
||||
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.setString(4, gson.toJson((TLChatForbidden)abs));
|
||||
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.setString(4, gson.toJson((TLChannelForbidden)abs));
|
||||
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.setString(4, gson.toJson((TLChat) abs));
|
||||
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.setString(4, gson.toJson((TLChannel)abs));
|
||||
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, Gson gson) {
|
||||
try {
|
||||
PreparedStatement ps_insert_or_replace = conn.prepareStatement(
|
||||
"INSERT OR REPLACE INTO users " +
|
||||
"(id, first_name, last_name, username, type, phone, json) " +
|
||||
"VALUES " +
|
||||
"(?, ?, ?, ?, ?, ?, ?)");
|
||||
PreparedStatement ps_insert_or_ignore = conn.prepareStatement(
|
||||
"INSERT OR IGNORE INTO users " +
|
||||
"(id, first_name, last_name, username, type, phone, json) " +
|
||||
"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.setString(7, gson.toJson(user));
|
||||
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_replace.setString(7, gson.toJson((TLUserEmpty)abs));
|
||||
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 String queryString(String query) {
|
||||
String result = null;
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
rs.next();
|
||||
result = rs.getString(1);
|
||||
rs.close();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Exception happened in queryString:", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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(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(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, 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;
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) "+
|
||||
"FROM messages, users WHERE users.id=messages.sender_id AND " + c.getQuery() + " GROUP BY sender_id");
|
||||
while (rs.next()) {
|
||||
User u = new User(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4));
|
||||
if (u.isMe) {
|
||||
map.put("authors.count.me", rs.getInt(5));
|
||||
} else {
|
||||
user_map.put(u, rs.getInt(5));
|
||||
count_others += rs.getInt(5);
|
||||
}
|
||||
}
|
||||
map.put("authors.count.others", count_others);
|
||||
map.put("authors.all", user_map.entrySet());
|
||||
return map;
|
||||
} catch (Exception e) { throw new RuntimeException(e); }
|
||||
}
|
||||
|
||||
public int[][] getMessageTimesMatrix() {
|
||||
return getMessageTimesMatrix(new GlobalChat());
|
||||
}
|
||||
|
||||
public int[][] getMessageTimesMatrix(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.chat_id IS NOT NULL AND messages.chat_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.dialog_id IS NOT NULL AND messages.dialog_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.id as message_id, text, time*1000 as time, has_media, " +
|
||||
"media_type, media_file, media_size, users.first_name as user_first_name, users.last_name as user_last_name, " +
|
||||
"users.username as user_username, users.id as user_id, " +
|
||||
"users_fwd.first_name as user_fwd_first_name, users_fwd.last_name as user_fwd_last_name, users_fwd.username as user_fwd_username " +
|
||||
"FROM messages, users LEFT JOIN users AS users_fwd ON users_fwd.id=fwd_from_id WHERE " +
|
||||
"users.id=messages.sender_id AND " + c.getQuery() + " " +
|
||||
"ORDER BY messages.id");
|
||||
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;
|
||||
@ -637,6 +100,27 @@ public class Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void jsonify() {
|
||||
try {
|
||||
ResultSet rs = stmt.executeQuery("SELECT id, data FROM messages WHERE api_layer=51");
|
||||
PreparedStatement ps = conn.prepareStatement("UPDATE messages SET json=? WHERE id=?");
|
||||
Gson gson = Utils.getGson();
|
||||
while(rs.next()) {
|
||||
TLMessage msg = bytesToTLMessage(rs.getBytes(2));
|
||||
ps.setInt(2, rs.getInt(1));
|
||||
ps.setString(1, gson.toJson(msg));
|
||||
ps.addBatch();
|
||||
}
|
||||
rs.close();
|
||||
conn.setAutoCommit(false);
|
||||
ps.executeBatch();
|
||||
conn.commit();
|
||||
conn.setAutoCommit(true);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,332 +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 com.google.gson.Gson;
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory;
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager;
|
||||
import com.github.badoualy.telegram.api.Kotlogram;
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
protected void _doUpdate() throws SQLException {
|
||||
stmt.executeUpdate("ALTER TABLE messages ADD COLUMN json TEXT");
|
||||
stmt.executeUpdate("ALTER TABLE chats ADD COLUMN json TEXT");
|
||||
stmt.executeUpdate("ALTER TABLE users ADD COLUMN json TEXT");
|
||||
|
||||
ResultSet rs = stmt.executeQuery("SELECT id, data FROM messages WHERE api_layer=" + Kotlogram.API_LAYER);
|
||||
PreparedStatement ps = conn.prepareStatement("UPDATE messages SET json=? WHERE id=?");
|
||||
Gson gson = Utils.getGson();
|
||||
while(rs.next()) {
|
||||
TLMessage msg = db.bytesToTLMessage(rs.getBytes(2));
|
||||
ps.setInt(2, rs.getInt(1));
|
||||
ps.setString(1, gson.toJson(msg));
|
||||
ps.addBatch();
|
||||
}
|
||||
rs.close();
|
||||
conn.setAutoCommit(false);
|
||||
ps.executeBatch();
|
||||
conn.commit();
|
||||
conn.setAutoCommit(true);
|
||||
}
|
||||
}
|
@ -1,409 +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 com.google.gson.GsonBuilder;
|
||||
|
||||
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.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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Logging this run");
|
||||
db.logRun(Math.min(max_database_id + 1, max_message_id), max_message_id, count_missing);
|
||||
}
|
||||
|
||||
private void downloadMessages(List<Integer> ids) throws RpcErrorException, IOException {
|
||||
prog.onMessageDownloadStart(ids.size());
|
||||
boolean has_seen_flood_wait_message = false;
|
||||
|
||||
logger.debug("Entering download loop");
|
||||
while (ids.size()>0) {
|
||||
logger.trace("Loop");
|
||||
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 {
|
||||
response = client.messagesGetMessages(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.");
|
||||
}
|
||||
|
||||
//ObjectMapper om = new ObjectMapper();
|
||||
//String json = om.writerWithDefaultPrettyPrinter().writeValueAsString(response.getMessages().get(1));
|
||||
Gson gson = Utils.getGson();
|
||||
|
||||
prog.onMessageDownloaded(response.getMessages().size());
|
||||
db.saveMessages(response.getMessages(), Kotlogram.API_LAYER, gson);
|
||||
db.saveChats(response.getChats(), gson);
|
||||
db.saveUsers(response.getUsers(), gson);
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
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,32 +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.mediafilemanager.AbstractMediaFileManager;
|
||||
|
||||
public interface DownloadProgressInterface {
|
||||
public void onMessageDownloadStart(int count);
|
||||
public void onMessageDownloaded(int number);
|
||||
public void onMessageDownloadFinished();
|
||||
|
||||
public void onMediaDownloadStart(int count);
|
||||
public void onMediaDownloaded(AbstractMediaFileManager a);
|
||||
public void onMediaDownloadedEmpty();
|
||||
public void onMediaSkipped();
|
||||
public void onMediaAlreadyPresent(AbstractMediaFileManager a);
|
||||
public void onMediaDownloadFinished();
|
||||
}
|
@ -1,159 +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 com.google.gson.Gson;
|
||||
|
||||
import de.fabianonline.telegram_backup.Database;
|
||||
import de.fabianonline.telegram_backup.Utils;
|
||||
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;
|
||||
private Gson gson = Utils.getGson();
|
||||
|
||||
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(), gson);
|
||||
db.saveChats(u.getChats(), gson);
|
||||
}
|
||||
|
||||
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(), gson);
|
||||
db.saveChats(u.getChats(), gson);
|
||||
}
|
||||
|
||||
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, gson);
|
||||
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, gson);
|
||||
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, gson);
|
||||
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());
|
||||
}
|
||||
}
|
@ -45,20 +45,12 @@ public class UserManager {
|
||||
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);
|
||||
public static void init(String phone) throws IOException {
|
||||
instance = new UserManager(phone);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
private UserManager(String phone) throws IOException {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public static UserManager getInstance() {
|
||||
@ -69,19 +61,9 @@ public class UserManager {
|
||||
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; }
|
||||
@ -126,6 +108,6 @@ public class UserManager {
|
||||
public TLUser getUser() { return this.user; }
|
||||
|
||||
public String getFileBase() {
|
||||
return Config.FILE_BASE + File.separatorChar + "+" + this.user.getPhone() + File.separatorChar;
|
||||
return Config.FILE_BASE + File.separatorChar + this.phone + File.separatorChar;
|
||||
}
|
||||
}
|
||||
|
@ -48,130 +48,6 @@ public class Utils {
|
||||
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,86 +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() { 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"; }
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package de.fabianonline.telegram_backup.models;
|
||||
|
||||
import de.fabianonline.telegram_backup.Database;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class Message {
|
||||
protected static String tableName = "messages";
|
||||
private JsonObject json;
|
||||
private String message = null;
|
||||
|
||||
public Message(String json) {
|
||||
this.json = new JsonParser().parse(json).getAsJsonObject();
|
||||
}
|
||||
|
||||
public static Message get(int id) {
|
||||
String json = Database.getInstance().queryString("SELECT json FROM " + tableName + " WHERE id=" + id);
|
||||
return new Message(json);
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
if (message != null) return message;
|
||||
return message = json.getAsJsonPrimitive("message").getAsString();
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
class Peer {
|
||||
int userId;
|
||||
String _constructor;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
import de.fabianonline.telegram_backup.Utils;
|
||||
|
||||
public class CompareVersionsTest {
|
||||
@Test
|
||||
public void tests() {
|
||||
assertEquals(Utils.compareVersions("1.0.3", "1.0.4"), Utils.VERSION_2_NEWER);
|
||||
assertEquals(Utils.compareVersions("1.0.4", "1.0.3"), Utils.VERSION_1_NEWER);
|
||||
assertEquals(Utils.compareVersions("1.0.4", "1.0.4.1"), Utils.VERSION_2_NEWER);
|
||||
assertEquals(Utils.compareVersions("1.0.4", "1.0.4-pre.1"), Utils.VERSION_1_NEWER);
|
||||
assertEquals(Utils.compareVersions("1.0.4", "1.0.4"), Utils.VERSIONS_EQUAL);
|
||||
assertEquals(Utils.compareVersions("1.0.4-pre.2", "1.0.4-pre.1"), Utils.VERSIONS_EQUAL);
|
||||
assertEquals(Utils.compareVersions("1.0.4", "1.0.4-abcdef-dirty"), Utils.VERSION_2_NEWER);
|
||||
assertEquals(Utils.compareVersions("1.0.5", "1.0.5-test.1"), Utils.VERSION_2_NEWER);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user