1
0
mirror of https://github.com/fabianonline/telegram_backup.git synced 2024-11-22 16:56:16 +00:00

Merge branch 'feature-rewrite'

This commit is contained in:
Fabian Schlenz 2018-04-11 06:11:07 +02:00
commit 1ff540977e
28 changed files with 1177 additions and 1657 deletions

View File

@ -1,4 +1,4 @@
#Thu Oct 06 11:24:39 CEST 2016 #Fri Mar 23 06:07:28 CET 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -0,0 +1,5 @@
package de.fabianonline.telegram_backup
class Account(val file_base: String, val phone_number: String) {
}

View File

@ -22,120 +22,64 @@ import com.github.badoualy.telegram.mtproto.auth.AuthKey
import com.github.badoualy.telegram.mtproto.model.MTSession import com.github.badoualy.telegram.mtproto.model.MTSession
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
import org.slf4j.Logger
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
internal class ApiStorage(prefix: String?) : TelegramApiStorage { internal class ApiStorage(val base_dir: String) : TelegramApiStorage {
private var prefix: String? = null var auth_key: AuthKey? = null
private var do_save = false var dc: DataCenter? = null
private var auth_key: AuthKey? = null val file_auth_key: File
private var dc: DataCenter? = null val file_dc: File
private var file_auth_key: File? = null val logger = LoggerFactory.getLogger(ApiStorage::class.java)
private var file_dc: File? = null
init { init {
this.setPrefix(prefix) file_auth_key = File(base_dir + Config.FILE_NAME_AUTH_KEY)
} file_dc = File(base_dir + Config.FILE_NAME_DC)
fun setPrefix(prefix: String?) {
this.prefix = prefix
this.do_save = this.prefix != null
if (this.do_save) {
val base = Config.FILE_BASE +
File.separatorChar +
this.prefix +
File.separatorChar
this.file_auth_key = File(base + Config.FILE_NAME_AUTH_KEY)
this.file_dc = File(base + Config.FILE_NAME_DC)
this._saveAuthKey()
this._saveDc()
} else {
this.file_auth_key = null
this.file_dc = null
}
} }
override fun saveAuthKey(authKey: AuthKey) { override fun saveAuthKey(authKey: AuthKey) {
this.auth_key = authKey FileUtils.writeByteArrayToFile(file_auth_key, authKey.key)
this._saveAuthKey()
}
private fun _saveAuthKey() {
if (this.do_save && this.auth_key != null) {
try {
FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key!!.key)
} catch (e: IOException) {
e.printStackTrace()
}
}
} }
override fun loadAuthKey(): AuthKey? { override fun loadAuthKey(): AuthKey? {
if (this.auth_key != null) return this.auth_key
if (this.file_auth_key != null) {
try { try {
return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key)) return AuthKey(FileUtils.readFileToByteArray(file_auth_key))
} catch (e: IOException) { } catch (e: FileNotFoundException) {
if (e !is FileNotFoundException) e.printStackTrace()
}
}
return null return null
} }
}
override fun saveDc(dataCenter: DataCenter) { override fun saveDc(dataCenter: DataCenter) {
this.dc = dataCenter FileUtils.write(file_dc, dataCenter.toString())
this._saveDc()
}
private fun _saveDc() {
if (this.do_save && this.dc != null) {
try {
FileUtils.write(this.file_dc, this.dc!!.toString())
} catch (e: IOException) {
e.printStackTrace()
}
}
} }
override fun loadDc(): DataCenter? { override fun loadDc(): DataCenter? {
if (this.dc != null) return this.dc
if (this.file_dc != null) {
try { try {
val infos = FileUtils.readFileToString(this.file_dc).split(":") val infos = FileUtils.readFileToString(this.file_dc).split(":")
return DataCenter(infos[0], Integer.parseInt(infos[1])) return DataCenter(infos[0], Integer.parseInt(infos[1]))
} catch (e: IOException) { } catch (e: FileNotFoundException) {
if (e !is FileNotFoundException) e.printStackTrace()
}
}
return null return null
} }
}
override fun deleteAuthKey() { override fun deleteAuthKey() {
if (this.do_save) {
try { try {
FileUtils.forceDelete(this.file_auth_key) FileUtils.forceDelete(file_auth_key)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() logger.warn("Exception in deleteAuthKey(): {}", e)
}
} }
} }
override fun deleteDc() { override fun deleteDc() {
if (this.do_save) {
try { try {
FileUtils.forceDelete(this.file_dc) FileUtils.forceDelete(file_dc)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() logger.warn("Exception in deleteDc(): {}", e)
}
} }
} }

View File

@ -29,121 +29,127 @@ import java.util.HashMap
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.Logger import org.slf4j.Logger
class CommandLineController { class CommandLineController(val options: CommandLineOptions) {
private val storage: ApiStorage val logger = LoggerFactory.getLogger(CommandLineController::class.java)
var app: TelegramApp
private fun getLine(): String {
if (System.console() != null) {
return System.console().readLine("> ")
} else {
print("> ")
return Scanner(System.`in`).nextLine()
}
}
private fun getPassword(): String {
if (System.console() != null) {
return String(System.console().readPassword("> "))
} else {
return getLine()
}
}
init { init {
val storage: ApiStorage
val app: TelegramApp
val target_dir: String
val file_base: String
val phone_number: String
val handler: TelegramUpdateHandler
var client: TelegramClient
val user_manager: UserManager
val settings: Settings
val database: Database
logger.info("CommandLineController started. App version {}", Config.APP_APPVER) logger.info("CommandLineController started. App version {}", Config.APP_APPVER)
this.printHeader() printHeader()
if (CommandLineOptions.cmd_version) { if (options.isSet("version")) {
System.exit(0) System.exit(0)
} else if (CommandLineOptions.cmd_help) { } else if (options.isSet("help")) {
this.show_help() show_help()
System.exit(0) System.exit(0)
} else if (CommandLineOptions.cmd_license) { } else if (options.isSet("license")) {
CommandLineController.show_license() show_license()
System.exit(0)
}
this.setupFileBase()
if (CommandLineOptions.cmd_list_accounts) {
this.list_accounts()
System.exit(0) System.exit(0)
} }
// Setup TelegramApp
logger.debug("Initializing TelegramApp") logger.debug("Initializing TelegramApp")
app = TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG) app = TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG)
// Setup file_base
logger.debug("Target dir from Config: {}", Config.TARGET_DIR.anonymize())
target_dir = options.get("target") ?: Config.TARGET_DIR
logger.debug("Target dir after options: {}", target_dir)
println("Base directory for files: ${target_dir.anonymize()}")
if (options.isSet("list_accounts")) {
Utils.print_accounts(target_dir)
System.exit(0)
}
if (options.isSet("login")) {
cmd_login(app, target_dir, options.get("account"))
}
logger.trace("Checking accounts") logger.trace("Checking accounts")
val account = this.selectAccount() phone_number = try { selectAccount(target_dir, options.get("account"))
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login) } catch(e: AccountNotFoundException) {
show_error("The specified account could not be found.")
} catch(e: NoAccountsException) {
println("No accounts found. Starting login process...")
cmd_login(app, target_dir, options.get("account"))
}
// TODO: Create a new TelegramApp if the user set his/her own TelegramApp credentials
// At this point we can assume that the selected user account ("phone_number") exists.
// So we can create some objects:
file_base = build_file_base(target_dir, phone_number)
logger.info("Initializing ApiStorage") logger.info("Initializing ApiStorage")
storage = ApiStorage(account) storage = ApiStorage(file_base)
logger.info("Initializing TelegramUpdateHandler")
val handler = TelegramUpdateHandler()
logger.info("Creating Client") logger.info("Creating Client")
val client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler) client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, null)
// From now on we have a new catch-all-block that will terminate it's TelegramClient when an exception happens.
try { try {
logger.info("Initializing UserManager") logger.info("Initializing UserManager")
UserManager.init(client) user_manager = UserManager(client)
val user = UserManager.getInstance()
if (!CommandLineOptions.cmd_login && !user.loggedIn) { // TODO
/*if (!options.cmd_login && !user.loggedIn) {
println("Your authorization data is invalid or missing. You will have to login with Telegram again.") println("Your authorization data is invalid or missing. You will have to login with Telegram again.")
CommandLineOptions.cmd_login = true options.cmd_login = true
} }*/
if (account != null && user.loggedIn) {
if (account != "+" + user.user!!.getPhone()) { if (phone_number != user_manager.phone) {
logger.error("Account: {}, user.user!!.getPhone(): +{}", account.anonymize(), user.user!!.getPhone().anonymize()) logger.error("phone_number: {}, user_manager.phone: {}", phone_number.anonymize(), user_manager.phone.anonymize())
throw RuntimeException("Account / User mismatch") show_error("Account / User mismatch")
}
} }
// Load the ini file.
IniSettings.load()
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login)
if (CommandLineOptions.cmd_login) {
cmd_login(CommandLineOptions.val_account)
System.exit(0)
}
// If we reach this point, we can assume that there is an account and a database can be loaded / created. // If we reach this point, we can assume that there is an account and a database can be loaded / created.
Database.init(client) database = Database(file_base, user_manager)
if (CommandLineOptions.cmd_stats) {
cmd_stats()
System.exit(0)
}
if (CommandLineOptions.val_test != null) {
if (CommandLineOptions.val_test == 1) {
TestFeatures.test1()
} else if (CommandLineOptions.val_test == 2) {
TestFeatures.test2()
} else {
System.out.println("Unknown test " + CommandLineOptions.val_test)
}
System.exit(1)
}
logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export)
if (CommandLineOptions.val_export != null) {
if (CommandLineOptions.val_export!!.toLowerCase().equals("html")) {
(HTMLExporter()).export()
System.exit(0)
} else {
show_error("Unknown export format.")
}
}
if (user.loggedIn) {
System.out.println("You are logged in as ${user.userString.anonymize()}")
} else {
println("You are not logged in.")
System.exit(1)
}
logger.info("Initializing Download Manager")
val d = DownloadManager(client, CommandLineDownloadProgress())
if (CommandLineOptions.cmd_list_channels) { // Load the settings and stuff.
settings = Settings(file_base, database, options)
if (options.isSet("stats")) {
cmd_stats(file_base, database)
System.exit(0)
} else if (options.isSet("settings")) {
settings.print()
System.exit(0)
}
val export = options.get("export")
logger.debug("options.val_export: {}", export)
if (export != null) {
if (export.toLowerCase() == "html") {
HTMLExporter(database, user_manager, settings=settings, file_base=file_base).export()
System.exit(0)
} else {
show_error("Unknown export format '${export}'.")
}
}
println("You are logged in as ${user_manager.toString().anonymize()}")
logger.info("Initializing Download Manager")
val d = DownloadManager(client, CommandLineDownloadProgress(), database, user_manager, settings, file_base)
if (options.isSet("list_channels")) {
val chats = d.getChats() val chats = d.getChats()
val print_header = {download: Boolean -> println("%-15s %-40s %s".format("ID", "Title", if (download) "Download" else "")); println("-".repeat(65)) } val print_header = {download: Boolean -> println("%-15s %-40s %s".format("ID", "Title", if (download) "Download" else "")); println("-".repeat(65)) }
val format = {c: DownloadManager.Channel, download: Boolean -> "%-15s %-40s %s".format(c.id.toString().anonymize(), c.title.anonymize(), if (download) (if(c.download) "YES" else "no") else "")} val format = {c: DownloadManager.Channel, download: Boolean -> "%-15s %-40s %s".format(c.id.toString().anonymize(), c.title.anonymize(), if (download) (if(c.download) "YES" else "no") else "")}
var download: Boolean var download: Boolean
println("Channels:") println("Channels:")
download = IniSettings.download_channels download = settings.download_channels
if (!download) println("Download of channels is disabled - see download_channels in config.ini") if (!download) println("Download of channels is disabled - see download_channels in config.ini")
print_header(download) print_header(download)
for (c in chats.channels) { for (c in chats.channels) {
@ -151,7 +157,7 @@ class CommandLineController {
} }
println() println()
println("Supergroups:") println("Supergroups:")
download = IniSettings.download_supergroups download = settings.download_supergroups
if (!download) println("Download of supergroups is disabled - see download_supergroups in config.ini") if (!download) println("Download of supergroups is disabled - see download_supergroups in config.ini")
print_header(download) print_header(download)
for (c in chats.supergroups) { for (c in chats.supergroups) {
@ -160,33 +166,35 @@ class CommandLineController {
System.exit(0) System.exit(0)
} }
logger.debug("Calling DownloadManager.downloadMessages with limit {}", CommandLineOptions.val_limit_messages) logger.debug("Calling DownloadManager.downloadMessages with limit {}", options.get("limit_messages"))
d.downloadMessages(CommandLineOptions.val_limit_messages) d.downloadMessages(options.get("limit_messages")?.toInt())
logger.debug("IniSettings.download_media: {}", IniSettings.download_media) logger.debug("IniSettings#download_media: {}", settings.download_media)
if (IniSettings.download_media) { if (settings.download_media) {
logger.debug("Calling DownloadManager.downloadMedia") logger.debug("Calling DownloadManager.downloadMedia")
d.downloadMedia() d.downloadMedia()
} else { } else {
println("Skipping media download because download_media is set to false.") println("Skipping media download because download_media is set to false.")
} }
if (options.isSet("daemon")) {
logger.info("Initializing TelegramUpdateHandler")
handler = TelegramUpdateHandler(user_manager, database, file_base, settings)
client.close()
logger.info("Creating new client")
client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler)
println("DAEMON mode requested - keeping running.")
}
} catch (e: Throwable) { } catch (e: Throwable) {
println("An error occured!") println("An error occurred!")
e.printStackTrace() e.printStackTrace()
logger.error("Exception caught!", e) logger.error("Exception caught!", e)
// If we encountered an exception, we definitely don't want to start the daemon mode now.
CommandLineOptions.cmd_daemon = false
} finally { } finally {
if (CommandLineOptions.cmd_daemon) {
handler.activate()
println("DAEMON mode requested - keeping running.")
} else {
client.close() client.close()
println() println()
println("----- EXIT -----") println("----- EXIT -----")
System.exit(0) System.exit(0)
} }
} }
}
private fun printHeader() { private fun printHeader() {
System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz") System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz")
@ -196,150 +204,102 @@ class CommandLineController {
println() println()
} }
private fun setupFileBase() { private fun selectAccount(file_base: String, requested_account: String?): String {
logger.debug("Target dir at startup: {}", Config.FILE_BASE.anonymize()) var found_account: String?
if (CommandLineOptions.val_target != null) { val accounts = Utils.getAccounts(file_base)
Config.FILE_BASE = CommandLineOptions.val_target!! if (requested_account != null) {
} logger.debug("Account requested: {}", requested_account.anonymize())
logger.debug("Target dir after options: {}", Config.FILE_BASE.anonymize())
System.out.println("Base directory for files: " + Config.FILE_BASE.anonymize())
}
private fun selectAccount(): String? {
var account = "none"
val accounts = Utils.getAccounts()
if (CommandLineOptions.cmd_login) {
logger.debug("Login requested, doing nothing.")
// do nothing
} else if (CommandLineOptions.val_account != null) {
logger.debug("Account requested: {}", CommandLineOptions.val_account!!.anonymize())
logger.trace("Checking accounts for match.") logger.trace("Checking accounts for match.")
var found = false found_account = accounts.find{it == requested_account}
for (acc in accounts) {
logger.trace("Checking {}", acc.anonymize())
if (acc == CommandLineOptions.val_account) {
found = true
logger.trace("Matches.")
break
}
}
if (!found) {
show_error("Couldn't find account '" + CommandLineOptions.val_account!!.anonymize() + "'. Maybe you want to use '--login' first?")
}
account = CommandLineOptions.val_account!!
} else if (accounts.size == 0) { } else if (accounts.size == 0) {
println("No accounts found. Starting login process...") throw NoAccountsException()
CommandLineOptions.cmd_login = true
return null
} else if (accounts.size == 1) { } else if (accounts.size == 1) {
account = accounts.firstElement() found_account = accounts.firstElement()
System.out.println("Using only available account: " + account.anonymize()) println("Using only available account: " + found_account.anonymize())
} else { } else {
show_error(("You didn't specify which account to use.\n" + show_error(("You have more than one account but didn't specify which one to use.\n" +
"Use '--account <x>' to use account <x>.\n" + "Use '--account <x>' to use account <x>.\n" +
"Use '--list-accounts' to see all available accounts.")) "Use '--list-accounts' to see all available accounts."))
System.exit(1)
}
logger.debug("accounts.size: {}", accounts.size)
logger.debug("account: {}", account.anonymize())
return account
} }
private fun cmd_stats() { if (found_account == null) {
throw AccountNotFoundException()
}
logger.debug("accounts.size: {}", accounts.size)
logger.debug("account: {}", found_account.anonymize())
return found_account
}
private fun cmd_stats(file_base: String, db: Database) {
println() println()
println("Stats:") println("Stats:")
val format = "%40s: %d%n" val format = "%40s: %d%n"
System.out.format(format, "Number of accounts", Utils.getAccounts().size) System.out.format(format, "Number of accounts", Utils.getAccounts(file_base).size)
System.out.format(format, "Number of messages", Database.getInstance().getMessageCount()) System.out.format(format, "Number of messages", db.getMessageCount())
System.out.format(format, "Number of chats", Database.getInstance().getChatCount()) System.out.format(format, "Number of chats", db.getChatCount())
System.out.format(format, "Number of users", Database.getInstance().getUserCount()) System.out.format(format, "Number of users", db.getUserCount())
System.out.format(format, "Top message ID", Database.getInstance().getTopMessageID()) System.out.format(format, "Top message ID", db.getTopMessageID())
println() println()
println("Media Types:") println("Media Types:")
for ((key, value) in Database.getInstance().getMessageMediaTypesWithCount()) { for ((key, value) in db.getMessageMediaTypesWithCount()) {
System.out.format(format, key, value) System.out.format(format, key, value)
} }
println() println()
println("Api layers of messages:") println("Api layers of messages:")
for ((key, value) in Database.getInstance().getMessageApiLayerWithCount()) { for ((key, value) in db.getMessageApiLayerWithCount()) {
System.out.format(format, key, value) System.out.format(format, key, value)
} }
println() println()
println("Message source types:") println("Message source types:")
for ((key, value) in Database.getInstance().getMessageSourceTypeWithCount()) { for ((key, value) in db.getMessageSourceTypeWithCount()) {
System.out.format(format, key, value) System.out.format(format, key, value)
} }
} }
@Throws(RpcErrorException::class, IOException::class) private fun cmd_login(app: TelegramApp, target_dir: String, phoneToUse: String?): Nothing {
private fun cmd_login(phoneToUse: String?) { LoginManager(app, target_dir, phoneToUse).run()
val user = UserManager.getInstance() System.exit(0)
val phone: String throw RuntimeException("Code never reaches this. This exists just to keep the Kotlin compiler happy.")
if (phoneToUse == null) {
println("Please enter your phone number in international format.")
println("Example: +4917077651234")
phone = getLine()
} else {
phone = phoneToUse
}
user.sendCodeToPhoneNumber(phone)
println("Telegram sent you a code. Please enter it here.")
val code = getLine()
user.verifyCode(code)
if (user.isPasswordNeeded) {
println("We also need your account password. Please enter it now. It should not be printed, so it's okay if you see nothing while typing it.")
val pw = getPassword()
user.verifyPassword(pw)
}
storage.setPrefix("+" + user.user!!.getPhone())
System.out.println("Everything seems fine. Please run this tool again with '--account +" + user.user!!.getPhone().anonymize() + " to use this account.")
} }
private fun show_help() { private fun show_help() {
println("Valid options are:") println("Valid options are:")
println(" -h, --help Shows this help.") println(" --help Shows this help.")
println(" -a, --account <x> Use account <x>.") println(" --account <x> Use account <x>.")
println(" -l, --login Login to an existing telegram account.") println(" --login Login to an existing telegram account.")
println(" --debug Shows some debug information.") println(" --debug Shows some debug information.")
println(" --trace Shows lots of debug information. Overrides --debug.") println(" --trace Shows lots of debug information. Overrides --debug.")
println(" --trace-telegram Shows lots of debug messages from the library used to access Telegram.") println(" --trace-telegram Shows lots of debug messages from the library used to access Telegram.")
println(" -A, --list-accounts List all existing accounts ") println(" --list-accounts List all existing accounts ")
println(" --limit-messages <x> Downloads at most the most recent <x> messages.") println(" --limit-messages <x> Downloads at most the most recent <x> messages.")
println(" -t, --target <x> Target directory for the files.") println(" --target <x> Target directory for the files.")
println(" -e, --export <format> Export the database. Valid formats are:") println(" --export <format> Export the database. Valid formats are:")
println(" html - Creates HTML files.") println(" html - Creates HTML files.")
println(" --license Displays the license of this program.") println(" --license Displays the license of this program.")
println(" -d, --daemon Keep running after the backup and automatically save new messages.") println(" --daemon Keep running after the backup and automatically save new messages.")
println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support.") println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support.")
println(" --stats Print some usage statistics.") println(" --stats Print some usage statistics.")
println(" --list-channels Lists all channels together with their ID") println(" --list-channels Lists all channels together with their ID")
} }
private fun list_accounts() {
println("List of available accounts:")
val accounts = Utils.getAccounts()
if (accounts.size > 0) {
for (str in accounts) {
System.out.println(" " + str.anonymize())
}
println("Use '--account <x>' to use one of those accounts.")
} else {
println("NO ACCOUNTS FOUND")
println("Use '--login' to login to a telegram account.")
}
}
companion object { companion object {
private val logger = LoggerFactory.getLogger(CommandLineController::class.java) private val logger = LoggerFactory.getLogger(CommandLineController::class.java)
public fun show_error(error: String) { public fun show_error(error: String): Nothing {
logger.error(error) logger.error(error)
println("ERROR: " + error) println("ERROR: " + error)
System.exit(1) System.exit(1)
throw RuntimeException("Code never reaches this. This exists just to keep the Kotlin compiler happy.")
} }
fun show_license() { fun show_license() {
println("TODO: Print the GPL.") println("TODO: Print the GPL.")
} }
fun build_file_base(target_dir: String, account_to_use: String) = target_dir + File.separatorChar + account_to_use + File.separatorChar
} }
class AccountNotFoundException() : Exception("Account not found") {}
class NoAccountsException() : Exception("No accounts found") {}
} }

View File

@ -15,83 +15,52 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. */ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup package de.fabianonline.telegram_backup
internal object CommandLineOptions { class CommandLineOptions(args: Array<String>) {
public var cmd_console = false private val values = mutableMapOf<String, String>()
public var cmd_help = false var last_key: String? = null
public var cmd_login = false val substitutions = mapOf("-t" to "--target")
var cmd_debug = false
var cmd_trace = false init {
var cmd_trace_telegram = false val list = args.toMutableList()
var cmd_list_accounts = false
var cmd_version = false while (list.isNotEmpty()) {
var cmd_license = false
var cmd_daemon = false var current_arg = list.removeAt(0)
var cmd_anonymize = false if (!current_arg.startsWith("-")) throw RuntimeException("Unexpected unnamed parameter ${current_arg}")
var cmd_stats = false
var cmd_list_channels = false var next_arg: String? = null
var val_account: String? = null
var val_limit_messages: Int? = null if (current_arg.contains("=")) {
var val_target: String? = null val parts = current_arg.split("=", limit=2)
var val_export: String? = null current_arg = parts[0]
var val_test: Int? = null next_arg = parts[1]
@JvmStatic } else if (list.isNotEmpty() && !list[0].startsWith("--")) {
fun parseOptions(args: Array<String>) { next_arg = list.removeAt(0)
var last_cmd: String? = null
loop@ for (arg in args) {
if (last_cmd != null) {
when (last_cmd) {
"--account" -> val_account = arg
"--limit-messages" -> val_limit_messages = Integer.parseInt(arg)
"--target" -> val_target = arg
"--export" -> val_export = arg
"--test" -> val_test = Integer.parseInt(arg)
} }
last_cmd = null
continue if (!current_arg.startsWith("--") && current_arg.startsWith("-")) {
val replacement = substitutions.get(current_arg)
if (replacement == null) throw RuntimeException("Unknown short parameter ${current_arg}")
current_arg = replacement
} }
when (arg) {
"-a", "--account" -> { current_arg = current_arg.substring(2)
last_cmd = "--account"
continue@loop if (next_arg == null) {
// current_arg seems to be a boolean value
values.put(current_arg, "true")
if (current_arg.startsWith("no-")) {
current_arg = current_arg.substring(3)
values.put(current_arg, "false")
} }
"-h", "--help" -> cmd_help = true } else {
"-l", "--login" -> cmd_login = true // current_arg has the value next_arg
"--debug" -> cmd_debug = true values.put(current_arg, next_arg)
"--trace" -> cmd_trace = true
"--trace-telegram" -> cmd_trace_telegram = true
"-A", "--list-accounts" -> cmd_list_accounts = true
"--limit-messages" -> {
last_cmd = arg
continue@loop
}
"--console" -> cmd_console = true
"-t", "--target" -> {
last_cmd = "--target"
continue@loop
}
"-V", "--version" -> cmd_version = true
"-e", "--export" -> {
last_cmd = "--export"
continue@loop
}
"--pagination" -> {
last_cmd = "--pagination"
continue@loop
}
"--license" -> cmd_license = true
"-d", "--daemon" -> cmd_daemon = true
"--test" -> {
last_cmd = "--test"
continue@loop
}
"--anonymize" -> cmd_anonymize = true
"--stats" -> cmd_stats = true
"--list-channels" -> cmd_list_channels = true
else -> throw RuntimeException("Unknown command " + arg)
} }
} }
if (last_cmd != null) { println(values)
CommandLineController.show_error("Command $last_cmd had no parameter set.")
}
} }
operator fun get(name: String): String? = values[name]
fun isSet(name: String): Boolean = values[name]=="true"
} }

View File

@ -29,24 +29,35 @@ import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
fun main(args: Array<String>) { fun main(args: Array<String>) {
CommandLineOptions.parseOptions(args) val clr = CommandLineRunner(args)
CommandLineRunner.setupLogging() clr.setupLogging()
CommandLineRunner.checkVersion() clr.checkVersion()
clr.run()
if (true || CommandLineOptions.cmd_console) {
// Always use the console for now.
CommandLineController()
} else {
GUIController()
}
} }
object CommandLineRunner { class CommandLineRunner(args: Array<String>) {
fun setupLogging() {
val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger
val options = CommandLineOptions(args)
fun run() {
// Always use the console for now.
try {
CommandLineController(options)
} catch (e: Throwable) {
println("An error occured!")
e.printStackTrace()
logger.error("Exception caught!", e)
System.exit(1)
}
}
fun setupLogging() {
if (options.isSet("anonymize")) {
Utils.anonymize = true
}
logger.trace("Setting up Loggers...") logger.trace("Setting up Loggers...")
val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger
val rootContext = rootLogger.getLoggerContext() val rootContext = rootLogger.getLoggerContext()
@ -65,13 +76,13 @@ object CommandLineRunner {
rootLogger.addAppender(appender) rootLogger.addAppender(appender)
rootLogger.setLevel(Level.OFF) rootLogger.setLevel(Level.OFF)
if (CommandLineOptions.cmd_trace) { if (options.isSet("trace")) {
(LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.TRACE) (LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.TRACE)
} else if (CommandLineOptions.cmd_debug) { } else if (options.isSet("debug")) {
(LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.DEBUG) (LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.DEBUG)
} }
if (CommandLineOptions.cmd_trace_telegram) { if (options.isSet("trace_telegram")) {
(LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE) (LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE)
} }
} }

View File

@ -29,7 +29,7 @@ object Config {
val APP_APPVER: String val APP_APPVER: String
val APP_LANG = "en" val APP_LANG = "en"
var FILE_BASE = System.getProperty("user.home") + File.separatorChar + ".telegram_backup" var TARGET_DIR = System.getProperty("user.home") + File.separatorChar + ".telegram_backup"
val FILE_NAME_AUTH_KEY = "auth.dat" val FILE_NAME_AUTH_KEY = "auth.dat"
val FILE_NAME_DC = "dc.dat" val FILE_NAME_DC = "dc.dat"
val FILE_NAME_DB = "database.sqlite" val FILE_NAME_DB = "database.sqlite"

View File

@ -21,7 +21,6 @@ import com.github.badoualy.telegram.tl.core.TLVector
import com.github.badoualy.telegram.api.TelegramClient import com.github.badoualy.telegram.api.TelegramClient
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.Logger import org.slf4j.Logger
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.sql.Statement import java.sql.Statement
@ -47,34 +46,45 @@ import java.text.SimpleDateFormat
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
class Database private constructor(var client: TelegramClient) { class Database constructor(val file_base: String, val user_manager: UserManager) {
private var conn: Connection? = null val conn: Connection
private var stmt: Statement? = null val stmt: Statement
var user_manager: UserManager val logger = LoggerFactory.getLogger(Database::class.java)
fun getTopMessageID(): Int { init {
println("Opening database...")
try { try {
val rs = stmt!!.executeQuery("SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog')") Class.forName("org.sqlite.JDBC")
rs.next() } catch (e: ClassNotFoundException) {
val result = rs.getInt(1) throw RuntimeException("Could not load jdbc-sqlite class.")
rs.close() }
return result
val path = "jdbc:sqlite:${file_base}${Config.FILE_NAME_DB}"
try {
conn = DriverManager.getConnection(path)!!
stmt = conn.createStatement()
} catch (e: SQLException) { } catch (e: SQLException) {
return 0 throw RuntimeException("Could not connect to SQLITE database.")
} }
// Run updates
val updates = DatabaseUpdates(conn, this)
updates.doUpdates()
println("Database is ready.")
} }
fun getTopMessageID(): Int = queryInt("SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog')")
fun getMessageCount(): Int = queryInt("SELECT COUNT(*) FROM messages") fun getMessageCount(): Int = queryInt("SELECT COUNT(*) FROM messages")
fun getChatCount(): Int = queryInt("SELECT COUNT(*) FROM chats") fun getChatCount(): Int = queryInt("SELECT COUNT(*) FROM chats")
fun getUserCount(): Int = queryInt("SELECT COUNT(*) FROM users") fun getUserCount(): Int = queryInt("SELECT COUNT(*) FROM users")
val missingIDs: LinkedList<Int> fun getMissingIDs(): LinkedList<Int> {
get() {
try { try {
val missing = LinkedList<Int>() val missing = LinkedList<Int>()
val max = getTopMessageID() val max = getTopMessageID()
val rs = stmt!!.executeQuery("SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id") val rs = stmt.executeQuery("SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id")
rs.next() rs.next()
var id = rs.getInt(1) var id = rs.getInt(1)
for (i in 1..max) { for (i in 1..max) {
@ -95,7 +105,6 @@ class Database private constructor(var client: TelegramClient) {
e.printStackTrace() e.printStackTrace()
throw RuntimeException("Could not get list of ids.") throw RuntimeException("Could not get list of ids.")
} }
} }
fun getMessagesWithMediaCount() = queryInt("SELECT COUNT(*) FROM messages WHERE has_media=1") fun getMessagesWithMediaCount() = queryInt("SELECT COUNT(*) FROM messages WHERE has_media=1")
@ -105,7 +114,7 @@ class Database private constructor(var client: TelegramClient) {
val list = LinkedList<TLMessage?>() val list = LinkedList<TLMessage?>()
var query = "SELECT data FROM messages WHERE has_media=1 ORDER BY id" var query = "SELECT data FROM messages WHERE has_media=1 ORDER BY id"
if (limit > 0) query += " LIMIT ${limit} OFFSET ${offset}" if (limit > 0) query += " LIMIT ${limit} OFFSET ${offset}"
val rs = stmt!!.executeQuery(query) val rs = stmt.executeQuery(query)
while (rs.next()) { while (rs.next()) {
list.add(bytesToTLMessage(rs.getBytes(1))) list.add(bytesToTLMessage(rs.getBytes(1)))
} }
@ -118,7 +127,7 @@ class Database private constructor(var client: TelegramClient) {
} }
fun getMessagesFromUserCount() = queryInt("SELECT COUNT(*) FROM messages WHERE sender_id=" + user_manager.user!!.getId()) fun getMessagesFromUserCount() = queryInt("SELECT COUNT(*) FROM messages WHERE sender_id=" + user_manager.id)
fun getMessageTypesWithCount(): HashMap<String, Int> = getMessageTypesWithCount(GlobalChat()) fun getMessageTypesWithCount(): HashMap<String, Int> = getMessageTypesWithCount(GlobalChat())
@ -127,7 +136,7 @@ class Database private constructor(var client: TelegramClient) {
fun getMessageSourceTypeWithCount(): HashMap<String, Int> { fun getMessageSourceTypeWithCount(): HashMap<String, Int> {
val map = HashMap<String, Int>() val map = HashMap<String, Int>()
try { try {
val rs = stmt!!.executeQuery("SELECT COUNT(id), source_type FROM messages GROUP BY source_type ORDER BY source_type") val rs = stmt.executeQuery("SELECT COUNT(id), source_type FROM messages GROUP BY source_type ORDER BY source_type")
while (rs.next()) { while (rs.next()) {
val source_type = rs.getString(2) ?: "null" val source_type = rs.getString(2) ?: "null"
map.put("count.messages.source_type.${source_type}", rs.getInt(1)) map.put("count.messages.source_type.${source_type}", rs.getInt(1))
@ -142,7 +151,7 @@ class Database private constructor(var client: TelegramClient) {
fun getMessageApiLayerWithCount(): HashMap<String, Int> { fun getMessageApiLayerWithCount(): HashMap<String, Int> {
val map = HashMap<String, Int>() val map = HashMap<String, Int>()
try { try {
val rs = stmt!!.executeQuery("SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer") val rs = stmt.executeQuery("SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer")
while (rs.next()) { while (rs.next()) {
var layer = rs.getInt(2) var layer = rs.getInt(2)
map.put("count.messages.api_layer.$layer", rs.getInt(1)) map.put("count.messages.api_layer.$layer", rs.getInt(1))
@ -158,25 +167,12 @@ class Database private constructor(var client: TelegramClient) {
fun getMessageTimesMatrix(): Array<IntArray> = getMessageTimesMatrix(GlobalChat()) fun getMessageTimesMatrix(): Array<IntArray> = getMessageTimesMatrix(GlobalChat())
fun getEncoding(): String { fun getEncoding(): String = queryString("PRAGMA encoding")
try {
val rs = stmt!!.executeQuery("PRAGMA encoding")
rs.next()
val result = rs.getString(1)
rs.close()
return result
} catch (e: SQLException) {
logger.debug("SQLException: {}", e)
return "unknown"
}
}
fun getListOfChatsForExport(): LinkedList<Chat> { fun getListOfChatsForExport(): LinkedList<Chat> {
val list = LinkedList<Chat>() val list = LinkedList<Chat>()
try {
val rs = stmt!!.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c " + val rs = stmt.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c " +
"FROM chats, messages WHERE messages.source_type IN('group', 'supergroup', 'channel') AND messages.source_id=chats.id " + "FROM chats, messages WHERE messages.source_type IN('group', 'supergroup', 'channel') AND messages.source_id=chats.id " +
"GROUP BY chats.id ORDER BY c DESC") "GROUP BY chats.id ORDER BY c DESC")
while (rs.next()) { while (rs.next()) {
@ -184,18 +180,12 @@ class Database private constructor(var client: TelegramClient) {
} }
rs.close() rs.close()
return list return list
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Exception above!")
}
} }
fun getListOfDialogsForExport(): LinkedList<Dialog> { fun getListOfDialogsForExport(): LinkedList<Dialog> {
val list = LinkedList<Dialog>() val list = LinkedList<Dialog>()
try { val rs = stmt.executeQuery(
val rs = stmt!!.executeQuery(
"SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " + "SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " +
"FROM users, messages WHERE messages.source_type='dialog' AND messages.source_id=users.id " + "FROM users, messages WHERE messages.source_type='dialog' AND messages.source_id=users.id " +
"GROUP BY users.id ORDER BY c DESC") "GROUP BY users.id ORDER BY c DESC")
@ -204,44 +194,14 @@ class Database private constructor(var client: TelegramClient) {
} }
rs.close() rs.close()
return list return list
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Exception above!")
}
}
init {
this.user_manager = UserManager.getInstance()
System.out.println("Opening database...")
try {
Class.forName("org.sqlite.JDBC")
} catch (e: ClassNotFoundException) {
CommandLineController.show_error("Could not load jdbc-sqlite class.")
}
val path = "jdbc:sqlite:${user_manager.fileBase}${Config.FILE_NAME_DB}"
try {
conn = DriverManager.getConnection(path)
stmt = conn!!.createStatement()
} catch (e: SQLException) {
CommandLineController.show_error("Could not connect to SQLITE database.")
}
// Run updates
val updates = DatabaseUpdates(conn!!, this)
updates.doUpdates()
System.out.println("Database is ready.")
} }
fun backupDatabase(currentVersion: Int) { fun backupDatabase(currentVersion: Int) {
val filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion) val filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion)
System.out.println(" Creating a backup of your database as " + filename) System.out.println(" Creating a backup of your database as " + filename)
try { try {
val src = user_manager.fileBase + Config.FILE_NAME_DB val src = file_base + Config.FILE_NAME_DB
val dst = user_manager.fileBase + filename val dst = file_base + filename
logger.debug("Copying {} to {}", src.anonymize(), dst.anonymize()) logger.debug("Copying {} to {}", src.anonymize(), dst.anonymize())
Files.copy( Files.copy(
File(src).toPath(), File(src).toPath(),
@ -258,8 +218,7 @@ class Database private constructor(var client: TelegramClient) {
fun getTopMessageIDForChannel(id: Int): Int = queryInt("SELECT MAX(message_id) FROM messages WHERE source_id=$id AND source_type IN('channel', 'supergroup')") fun getTopMessageIDForChannel(id: Int): Int = queryInt("SELECT MAX(message_id) FROM messages WHERE source_id=$id AND source_type IN('channel', 'supergroup')")
fun logRun(start_id: Int, end_id: Int, count: Int) { fun logRun(start_id: Int, end_id: Int, count: Int) {
try { val ps = conn.prepareStatement("INSERT INTO runs " +
val ps = conn!!.prepareStatement("INSERT INTO runs " +
"(time, start_id, end_id, count_missing) " + "(time, start_id, end_id, count_missing) " +
"VALUES " + "VALUES " +
"(DateTime('now'), ?, ?, ? )") "(DateTime('now'), ?, ?, ? )")
@ -268,27 +227,36 @@ class Database private constructor(var client: TelegramClient) {
ps.setInt(3, count) ps.setInt(3, count)
ps.execute() ps.execute()
ps.close() ps.close()
} catch (e: SQLException) {
}
} }
fun queryInt(query: String): Int { fun queryInt(query: String): Int {
try { val rs = stmt.executeQuery(query)
val rs = stmt!!.executeQuery(query)
rs.next() rs.next()
val result = rs.getInt(1) val result = rs.getInt(1)
rs.close() rs.close()
return result return result
} catch (e: SQLException) {
throw RuntimeException("Could not get count of messages.")
} }
fun queryString(query: String): String {
val rs = stmt.executeQuery(query)
rs.next()
val result = rs.getString(1)
rs.close()
return result
}
fun queryStringMap(query: String): Map<String, String> {
val map = mutableMapOf<String, String>()
val rs = stmt.executeQuery(query)
while(rs.next()) {
map.put(rs.getString(1), rs.getString(2))
}
rs.close()
return map
} }
@Synchronized @Synchronized
fun saveMessages(all: TLVector<TLAbsMessage>, api_layer: Int, source_type: MessageSource = MessageSource.NORMAL) { fun saveMessages(all: TLVector<TLAbsMessage>, api_layer: Int, source_type: MessageSource = MessageSource.NORMAL, settings: Settings?) {
try {
//"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " + //"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " +
//"VALUES " + //"VALUES " +
//"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); //"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
@ -296,8 +264,8 @@ class Database private constructor(var client: TelegramClient) {
"VALUES " + "VALUES " +
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
//1 2 3 4 5 6 7 8 9 10 11 12 13 14 //1 2 3 4 5 6 7 8 9 10 11 12 13 14
val ps = conn!!.prepareStatement("INSERT OR REPLACE INTO messages " + columns) val ps = conn.prepareStatement("INSERT OR REPLACE INTO messages " + columns)
val ps_insert_or_ignore = conn!!.prepareStatement("INSERT OR IGNORE INTO messages " + columns) val ps_insert_or_ignore = conn.prepareStatement("INSERT OR IGNORE INTO messages " + columns)
for (msg in all) { for (msg in all) {
if (msg is TLMessage) { if (msg is TLMessage) {
@ -309,7 +277,7 @@ class Database private constructor(var client: TelegramClient) {
ps.setInt(4, peer.getChatId()) ps.setInt(4, peer.getChatId())
} else if (peer is TLPeerUser) { } else if (peer is TLPeerUser) {
var id = peer.getUserId() var id = peer.getUserId()
if (id == this.user_manager.user!!.getId()) { if (id == user_manager.id) {
id = msg.getFromId() id = msg.getFromId()
} }
ps.setString(3, "dialog") ps.setString(3, "dialog")
@ -350,7 +318,7 @@ class Database private constructor(var client: TelegramClient) {
} }
ps.setString(7, text) ps.setString(7, text)
ps.setString(8, "" + msg.getDate()) ps.setString(8, "" + msg.getDate())
val f = FileManagerFactory.getFileManager(msg, user_manager, client) val f = FileManagerFactory.getFileManager(msg, user_manager, file_base, settings)
if (f == null) { if (f == null) {
ps.setNull(9, Types.BOOLEAN) ps.setNull(9, Types.BOOLEAN)
ps.setNull(10, Types.VARCHAR) ps.setNull(10, Types.VARCHAR)
@ -377,7 +345,7 @@ class Database private constructor(var client: TelegramClient) {
ps.setInt(4, peer.getChatId()) ps.setInt(4, peer.getChatId())
} else if (peer is TLPeerUser) { } else if (peer is TLPeerUser) {
var id = peer.getUserId() var id = peer.getUserId()
if (id == this.user_manager.user!!.getId()) { if (id == user_manager.id) {
id = msg.getFromId() id = msg.getFromId()
} }
ps.setString(3, "dialog") ps.setString(3, "dialog")
@ -425,32 +393,26 @@ class Database private constructor(var client: TelegramClient) {
throw RuntimeException("Unexpected Message type: " + msg.javaClass) throw RuntimeException("Unexpected Message type: " + msg.javaClass)
} }
} }
conn!!.setAutoCommit(false) conn.setAutoCommit(false)
ps.executeBatch() ps.executeBatch()
ps.clearBatch() ps.clearBatch()
ps_insert_or_ignore.executeBatch() ps_insert_or_ignore.executeBatch()
ps_insert_or_ignore.clearBatch() ps_insert_or_ignore.clearBatch()
conn!!.commit() conn.commit()
conn!!.setAutoCommit(true) conn.setAutoCommit(true)
ps.close() ps.close()
ps_insert_or_ignore.close() ps_insert_or_ignore.close()
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Exception shown above happened.")
}
} }
@Synchronized @Synchronized
fun saveChats(all: TLVector<TLAbsChat>) { fun saveChats(all: TLVector<TLAbsChat>) {
try { val ps_insert_or_replace = conn.prepareStatement(
val ps_insert_or_replace = conn!!.prepareStatement(
"INSERT OR REPLACE INTO chats " + "INSERT OR REPLACE INTO chats " +
"(id, name, type) " + "(id, name, type) " +
"VALUES " + "VALUES " +
"(?, ?, ?)") "(?, ?, ?)")
val ps_insert_or_ignore = conn!!.prepareStatement( val ps_insert_or_ignore = conn.prepareStatement(
"INSERT OR IGNORE INTO chats " + "INSERT OR IGNORE INTO chats " +
"(id, name, type) " + "(id, name, type) " +
"VALUES " + "VALUES " +
@ -483,32 +445,26 @@ class Database private constructor(var client: TelegramClient) {
throw RuntimeException("Unexpected " + abs.javaClass) throw RuntimeException("Unexpected " + abs.javaClass)
} }
} }
conn!!.setAutoCommit(false) conn.setAutoCommit(false)
ps_insert_or_ignore.executeBatch() ps_insert_or_ignore.executeBatch()
ps_insert_or_ignore.clearBatch() ps_insert_or_ignore.clearBatch()
ps_insert_or_replace.executeBatch() ps_insert_or_replace.executeBatch()
ps_insert_or_replace.clearBatch() ps_insert_or_replace.clearBatch()
conn!!.commit() conn.commit()
conn!!.setAutoCommit(true) conn.setAutoCommit(true)
ps_insert_or_ignore.close() ps_insert_or_ignore.close()
ps_insert_or_replace.close() ps_insert_or_replace.close()
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Exception shown above happened.")
}
} }
@Synchronized @Synchronized
fun saveUsers(all: TLVector<TLAbsUser>) { fun saveUsers(all: TLVector<TLAbsUser>) {
try { val ps_insert_or_replace = conn.prepareStatement(
val ps_insert_or_replace = conn!!.prepareStatement(
"INSERT OR REPLACE INTO users " + "INSERT OR REPLACE INTO users " +
"(id, first_name, last_name, username, type, phone) " + "(id, first_name, last_name, username, type, phone) " +
"VALUES " + "VALUES " +
"(?, ?, ?, ?, ?, ?)") "(?, ?, ?, ?, ?, ?)")
val ps_insert_or_ignore = conn!!.prepareStatement( val ps_insert_or_ignore = conn.prepareStatement(
"INSERT OR IGNORE INTO users " + "INSERT OR IGNORE INTO users " +
"(id, first_name, last_name, username, type, phone) " + "(id, first_name, last_name, username, type, phone) " +
"VALUES " + "VALUES " +
@ -535,30 +491,22 @@ class Database private constructor(var client: TelegramClient) {
throw RuntimeException("Unexpected " + abs.javaClass) throw RuntimeException("Unexpected " + abs.javaClass)
} }
} }
conn!!.setAutoCommit(false) conn.setAutoCommit(false)
ps_insert_or_ignore.executeBatch() ps_insert_or_ignore.executeBatch()
ps_insert_or_ignore.clearBatch() ps_insert_or_ignore.clearBatch()
ps_insert_or_replace.executeBatch() ps_insert_or_replace.executeBatch()
ps_insert_or_replace.clearBatch() ps_insert_or_replace.clearBatch()
conn!!.commit() conn.commit()
conn!!.setAutoCommit(true) conn.setAutoCommit(true)
ps_insert_or_ignore.close() ps_insert_or_ignore.close()
ps_insert_or_replace.close() ps_insert_or_replace.close()
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Exception shown above happened.")
}
} }
fun fetchSetting(key: String): String? { fun fetchSettings() = queryStringMap("SELECT key, value FROM settings")
val rs = stmt!!.executeQuery("SELECT value FROM settings WHERE key='${key}'")
rs.next()
return rs.getString(1)
}
fun saveSetting(key: String, value: String?) { fun saveSetting(key: String, value: String?) {
val ps = conn!!.prepareStatement("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)") val ps = conn.prepareStatement("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
ps.setString(1, key) ps.setString(1, key)
if (value==null) { if (value==null) {
ps.setNull(2, Types.VARCHAR) ps.setNull(2, Types.VARCHAR)
@ -566,59 +514,41 @@ class Database private constructor(var client: TelegramClient) {
ps.setString(2, value) ps.setString(2, value)
} }
ps.execute() ps.execute()
ps.close()
} }
fun getIdsFromQuery(query: String): LinkedList<Int> { fun getIdsFromQuery(query: String): LinkedList<Int> {
try {
val list = LinkedList<Int>() val list = LinkedList<Int>()
val rs = stmt!!.executeQuery(query) val rs = stmt.executeQuery(query)
while (rs.next()) { while (rs.next()) {
list.add(rs.getInt(1)) list.add(rs.getInt(1))
} }
rs.close() rs.close()
return list return list
} catch (e: SQLException) {
throw RuntimeException(e)
}
} }
fun getMessageTypesWithCount(c: AbstractChat): HashMap<String, Int> { fun getMessageTypesWithCount(c: AbstractChat): HashMap<String, Int> {
val map = HashMap<String, Int>() val map = HashMap<String, Int>()
try { val rs = stmt.executeQuery("SELECT message_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY message_type")
val rs = stmt!!.executeQuery("SELECT message_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY message_type")
while (rs.next()) { while (rs.next()) {
map.put("count.messages.type." + rs.getString(1), rs.getInt(2)) map.put("count.messages.type." + rs.getString(1), rs.getInt(2))
} }
rs.close() rs.close()
return map return map
} catch (e: Exception) {
throw RuntimeException(e)
}
} }
fun getMessageMediaTypesWithCount(c: AbstractChat): HashMap<String, Int> { fun getMessageMediaTypesWithCount(c: AbstractChat): HashMap<String, Int> {
val map = HashMap<String, Int>() val map = HashMap<String, Int>()
try {
var count = 0 var count = 0
val rs = stmt!!.executeQuery("SELECT media_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY media_type") val rs = stmt.executeQuery("SELECT media_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY media_type")
while (rs.next()) { while (rs.next()) {
var s = rs.getString(1) var type = rs.getString(1) ?: "null"
if (s == null) { if (type != "null") count += rs.getInt(2)
s = "null" map.put("count.messages.media_type.${type}", rs.getInt(2))
} else {
count += rs.getInt(2)
}
map.put("count.messages.media_type.$s", rs.getInt(2))
} }
map.put("count.messages.media_type.any", count) map.put("count.messages.media_type.any", count)
rs.close() rs.close()
return map return map
} catch (e: Exception) {
throw RuntimeException(e)
}
} }
fun getMessageAuthorsWithCount(c: AbstractChat): HashMap<String, Any> { fun getMessageAuthorsWithCount(c: AbstractChat): HashMap<String, Any> {
@ -628,8 +558,7 @@ class Database private constructor(var client: TelegramClient) {
// Set a default value for 'me' to fix the charts for channels - cause I // Set a default value for 'me' to fix the charts for channels - cause I
// possibly didn't send any messages there. // possibly didn't send any messages there.
map.put("authors.count.me", 0) map.put("authors.count.me", 0)
try { val rs = stmt.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) " +
val rs = stmt!!.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) " +
"FROM messages " + "FROM messages " +
"LEFT JOIN users ON users.id=messages.sender_id " + "LEFT JOIN users ON users.id=messages.sender_id " +
"WHERE " + c.query + " GROUP BY sender_id") "WHERE " + c.query + " GROUP BY sender_id")
@ -655,23 +584,13 @@ class Database private constructor(var client: TelegramClient) {
map.put("authors.all", all_data) map.put("authors.all", all_data)
rs.close() rs.close()
return map return map
} catch (e: Exception) {
throw RuntimeException(e)
}
} }
fun getMessageCountForExport(c: AbstractChat): Int { fun getMessageCountForExport(c: AbstractChat): Int = queryInt("SELECT COUNT(*) FROM messages WHERE " + c.query)
val rs = stmt!!.executeQuery("SELECT COUNT(*) FROM messages WHERE " + c.query);
rs.next()
val result = rs.getInt(1)
rs.close()
return result
}
fun getMessageTimesMatrix(c: AbstractChat): Array<IntArray> { fun getMessageTimesMatrix(c: AbstractChat): Array<IntArray> {
val result = Array(7) { IntArray(24) } val result = Array(7) { IntArray(24) }
try { val rs = stmt.executeQuery("SELECT STRFTIME('%w', time, 'unixepoch') as DAY, " +
val rs = stmt!!.executeQuery("SELECT STRFTIME('%w', time, 'unixepoch') as DAY, " +
"STRFTIME('%H', time, 'unixepoch') AS hour, " + "STRFTIME('%H', time, 'unixepoch') AS hour, " +
"COUNT(id) FROM messages WHERE " + c.query + " GROUP BY hour, day " + "COUNT(id) FROM messages WHERE " + c.query + " GROUP BY hour, day " +
"ORDER BY hour, day") "ORDER BY hour, day")
@ -680,14 +599,9 @@ class Database private constructor(var client: TelegramClient) {
} }
rs.close() rs.close()
return result return result
} catch (e: Exception) {
throw RuntimeException(e)
}
} }
fun getMessagesForExport(c: AbstractChat, limit: Int=-1, offset: Int=0): LinkedList<HashMap<String, Any>> { fun getMessagesForExport(c: AbstractChat, limit: Int=-1, offset: Int=0): LinkedList<HashMap<String, Any>> {
try {
var query = "SELECT messages.message_id as message_id, text, time*1000 as time, has_media, " + var query = "SELECT messages.message_id as message_id, text, time*1000 as time, has_media, " +
"media_type, media_file, media_size, users.first_name as user_first_name, users.last_name as user_last_name, " + "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.username as user_username, users.id as user_id, " +
@ -702,7 +616,7 @@ class Database private constructor(var client: TelegramClient) {
query = query + " LIMIT ${limit} OFFSET ${offset}" query = query + " LIMIT ${limit} OFFSET ${offset}"
} }
val rs = stmt!!.executeQuery(query) val rs = stmt.executeQuery(query)
val format_time = SimpleDateFormat("HH:mm:ss") val format_time = SimpleDateFormat("HH:mm:ss")
val format_date = SimpleDateFormat("d MMM yy") val format_date = SimpleDateFormat("d MMM yy")
@ -726,7 +640,7 @@ class Database private constructor(var client: TelegramClient) {
if (rs.getString("media_type") != null) { if (rs.getString("media_type") != null) {
h.put("media_" + rs.getString("media_type"), true) h.put("media_" + rs.getString("media_type"), true)
} }
h.put("from_me", rs.getInt("user_id") == user_manager.user!!.getId()) h.put("from_me", rs.getInt("user_id") == user_manager.id)
h.put("is_new_date", !date.equals(old_date)) h.put("is_new_date", !date.equals(old_date))
h.put("odd_even", if (count % 2 == 0) "even" else "odd") h.put("odd_even", if (count % 2 == 0) "even" else "odd")
h.put("same_user", old_user != 0 && rs.getInt("user_id") == old_user) h.put("same_user", old_user != 0 && rs.getInt("user_id") == old_user)
@ -738,11 +652,6 @@ class Database private constructor(var client: TelegramClient) {
} }
rs.close() rs.close()
return list return list
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Exception above!")
}
} }
@ -752,21 +661,13 @@ class Database private constructor(var client: TelegramClient) {
} }
inner class Dialog(var id: Int, var first_name: String?, var last_name: String?, var username: String?, var count: Int?) : AbstractChat() { inner class Dialog(var id: Int, var first_name: String?, var last_name: String?, var username: String?, var count: Int?) : AbstractChat() {
override val query = "source_type='dialog' AND source_id=" + id
override val query: String override val type = "dialog"
get() = "source_type='dialog' AND source_id=" + id
override val type: String
get() = "dialog"
} }
inner class Chat(var id: Int, var name: String?, var count: Int?) : AbstractChat() { inner class Chat(var id: Int, var name: String?, var count: Int?) : AbstractChat() {
override val query = "source_type IN('group', 'supergroup', 'channel') AND source_id=" + id
override val query: String override val type = "chat"
get() = "source_type IN('group', 'supergroup', 'channel') AND source_id=" + id
override val type: String
get() = "chat"
} }
inner class User(id: Int, first_name: String?, last_name: String?) { inner class User(id: Int, first_name: String?, last_name: String?) {
@ -774,7 +675,7 @@ class Database private constructor(var client: TelegramClient) {
var isMe: Boolean = false var isMe: Boolean = false
init { init {
isMe = id == user_manager.user!!.getId() isMe = id == user_manager.id
val s = StringBuilder() val s = StringBuilder()
if (first_name != null) s.append(first_name + " ") if (first_name != null) s.append(first_name + " ")
if (last_name != null) s.append(last_name) if (last_name != null) s.append(last_name)
@ -783,26 +684,11 @@ class Database private constructor(var client: TelegramClient) {
} }
inner class GlobalChat : AbstractChat() { inner class GlobalChat : AbstractChat() {
override val query: String override val query = "1=1"
get() = "1=1" override val type = "GlobalChat"
override val type: String
get() = "GlobalChat"
} }
companion object { companion object {
private val logger = LoggerFactory.getLogger(Database::class.java)
internal var instance: Database? = null
fun init(c: TelegramClient) {
instance = Database(c)
}
fun getInstance(): Database {
if (instance == null) throw RuntimeException("Database is not initialized but getInstance() was called.")
return instance!!
}
fun bytesToTLMessage(b: ByteArray?): TLMessage? { fun bytesToTLMessage(b: ByteArray?): TLMessage? {
try { try {
if (b == null) return null if (b == null) return null

View File

@ -313,7 +313,7 @@ internal class DB_Update_6(conn: Connection, db: Database) : DatabaseUpdate(conn
} else { } else {
ps.setInt(1, msg.getFwdFrom().getFromId()) ps.setInt(1, msg.getFwdFrom().getFromId())
} }
val f = FileManagerFactory.getFileManager(msg, db.user_manager, db.client) val f = FileManagerFactory.getFileManager(msg, db.user_manager, db.file_base, settings = null)
if (f == null) { if (f == null) {
ps.setNull(2, Types.VARCHAR) ps.setNull(2, Types.VARCHAR)
ps.setNull(3, Types.VARCHAR) ps.setNull(3, Types.VARCHAR)
@ -431,7 +431,7 @@ internal class DB_Update_9(conn: Connection, db: Database) : DatabaseUpdate(conn
} }
} }
rs.close() rs.close()
db.saveMessages(messages, api_layer=53, source_type=MessageSource.SUPERGROUP) db.saveMessages(messages, api_layer=53, source_type=MessageSource.SUPERGROUP, settings=null)
execute("DELETE FROM messages WHERE id IN (" + messages_to_delete.joinToString() + ")") execute("DELETE FROM messages WHERE id IN (" + messages_to_delete.joinToString() + ")")
print(".") print(".")

View File

@ -1,12 +0,0 @@
package de.fabianonline.telegram_backup
class DbSettings() {
private fun fetchValue(name: String): String? = Database.getInstance().fetchSetting(name)
private fun saveValue(name: String, value: String?) = Database.getInstance().saveSetting(name, value)
var pts: String?
get() = fetchValue("pts")
set(x: String?) = saveValue("pts", x)
}

View File

@ -18,7 +18,6 @@ package de.fabianonline.telegram_backup
import de.fabianonline.telegram_backup.UserManager import de.fabianonline.telegram_backup.UserManager
import de.fabianonline.telegram_backup.Database import de.fabianonline.telegram_backup.Database
import de.fabianonline.telegram_backup.StickerConverter
import de.fabianonline.telegram_backup.DownloadProgressInterface import de.fabianonline.telegram_backup.DownloadProgressInterface
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
@ -61,55 +60,21 @@ enum class MessageSource(val descr: String) {
SUPERGROUP("supergroup") SUPERGROUP("supergroup")
} }
class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressInterface) { class DownloadManager(val client: TelegramClient, val prog: DownloadProgressInterface, val db: Database, val user_manager: UserManager, val settings: Settings, val file_base: String) {
internal var user: UserManager? = null
internal var db: Database? = null
internal var prog: DownloadProgressInterface? = null
internal var has_seen_flood_wait_message = false
init {
this.user = UserManager.getInstance()
this.prog = p
this.db = Database.getInstance()
}
@Throws(RpcErrorException::class, IOException::class) @Throws(RpcErrorException::class, IOException::class)
fun downloadMessages(limit: Int?) { fun downloadMessages(limit: Int?) {
var completed: Boolean logger.info("This is downloadMessages with limit {}", limit)
do {
completed = true
try {
_downloadMessages(limit)
} catch (e: RpcErrorException) {
if (e.getCode() == 420) { // FLOOD_WAIT
completed = false
Utils.obeyFloodWaitException(e)
} else {
throw e
}
} catch (e: TimeoutException) {
completed = false
System.out.println("")
System.out.println("Telegram took too long to respond to our request.")
System.out.println("I'm going to wait a minute and then try again.")
try {
TimeUnit.MINUTES.sleep(1)
} catch (e2: InterruptedException) {
}
System.out.println("")
}
} while (!completed)
}
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
fun _downloadMessages(limit: Int?) {
logger.info("This is _downloadMessages with limit {}", limit)
logger.info("Downloading the last dialogs") logger.info("Downloading the last dialogs")
System.out.println("Downloading most recent dialogs... ") System.out.println("Downloading most recent dialogs... ")
var max_message_id = 0 var max_message_id = 0
val chats = getChats() var result: ChatList? = null
Utils.obeyFloodWait() {
result = getChats()
}
val chats = result!!
logger.debug("Got {} dialogs, {} supergoups, {} channels", chats.dialogs.size, chats.supergroups.size, chats.channels.size) logger.debug("Got {} dialogs, {} supergoups, {} channels", chats.dialogs.size, chats.supergroups.size, chats.channels.size)
for (d in chats.dialogs) { for (d in chats.dialogs) {
@ -119,7 +84,7 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
} }
} }
System.out.println("Top message ID is " + max_message_id) System.out.println("Top message ID is " + max_message_id)
var max_database_id = db!!.getTopMessageID() var max_database_id = db.getTopMessageID()
System.out.println("Top message ID in database is " + max_database_id) System.out.println("Top message ID in database is " + max_database_id)
if (limit != null) { if (limit != null) {
System.out.println("Limit is set to " + limit) System.out.println("Limit is set to " + limit)
@ -136,7 +101,7 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
if (max_database_id == max_message_id) { if (max_database_id == max_message_id) {
System.out.println("No new messages to download.") System.out.println("No new messages to download.")
} else if (max_database_id > max_message_id) { } else if (max_database_id > max_message_id) {
throw RuntimeException("max_database_id is bigger then max_message_id. This shouldn't happen. But the telegram api nonetheless does that sometimes. Just ignore this error, wait a few seconds and then try again.") throw RuntimeException("max_database_id is bigger than 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 { } else {
val start_id = max_database_id + 1 val start_id = max_database_id + 1
val end_id = max_message_id val end_id = max_message_id
@ -147,8 +112,8 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
logger.info("Searching for missing messages in the db") logger.info("Searching for missing messages in the db")
System.out.println("Checking message database for completeness...") System.out.println("Checking message database for completeness...")
val db_count = db!!.getMessageCount() val db_count = db.getMessageCount()
val db_max = db!!.getTopMessageID() val db_max = db.getTopMessageID()
logger.debug("db_count: {}", db_count) logger.debug("db_count: {}", db_count)
logger.debug("db_max: {}", db_max) logger.debug("db_max: {}", db_max)
@ -173,24 +138,20 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
} }
*/ */
if (IniSettings.download_channels || IniSettings.download_supergroups) { if (settings.download_channels) {
// TODO Add chat title (and other stuff?) to the database
if (IniSettings.download_channels) {
println("Checking channels...") println("Checking channels...")
for (channel in chats.channels) { if (channel.download) downloadMessagesFromChannel(channel) } for (channel in chats.channels) { if (channel.download) downloadMessagesFromChannel(channel) }
} }
if (IniSettings.download_supergroups) { if (settings.download_supergroups) {
println("Checking supergroups...") println("Checking supergroups...")
for (supergroup in chats.supergroups) { if (supergroup.download) downloadMessagesFromChannel(supergroup) } for (supergroup in chats.supergroups) { if (supergroup.download) downloadMessagesFromChannel(supergroup) }
} }
} }
}
private fun downloadMessagesFromChannel(channel: Channel) { private fun downloadMessagesFromChannel(channel: Channel) {
val obj = channel.obj val obj = channel.obj
val max_known_id = db!!.getTopMessageIDForChannel(channel.id) val max_known_id = db.getTopMessageIDForChannel(channel.id)
if (obj.getTopMessage() > max_known_id) { if (obj.getTopMessage() > max_known_id) {
val ids = makeIdList(max_known_id + 1, obj.getTopMessage()) val ids = makeIdList(max_known_id + 1, obj.getTopMessage())
var channel_name = channel.title var channel_name = channel.title
@ -210,7 +171,7 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
} else { } else {
"${source_type.descr} $source_name" "${source_type.descr} $source_name"
} }
prog!!.onMessageDownloadStart(ids.size, source_string) prog.onMessageDownloadStart(ids.size, source_string)
logger.debug("Entering download loop") logger.debug("Entering download loop")
while (ids.size > 0) { while (ids.size > 0) {
@ -225,105 +186,62 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
logger.trace("vector.size(): {}", vector.size) logger.trace("vector.size(): {}", vector.size)
logger.trace("ids.size(): {}", ids.size) logger.trace("ids.size(): {}", ids.size)
var response: TLAbsMessages var resp: TLAbsMessages? = null
var tries = 0 try {
while (true) { Utils.obeyFloodWait(max_tries=5) {
logger.trace("Trying getMessages(), tries={}", tries) if (channel == null) {
if (tries >= 5) { resp = client.messagesGetMessages(vector)
} else {
resp = client.channelsGetMessages(channel, vector)
}
}
} catch (e: MaxTriesExceededException) {
CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.") CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.")
} }
tries++ val response = resp!!
try {
if (channel == null) {
response = client!!.messagesGetMessages(vector)
} else {
response = client!!.channelsGetMessages(channel, vector)
}
break
} catch (e: RpcErrorException) {
if (e.getCode() == 420) { // FLOOD_WAIT
Utils.obeyFloodWaitException(e, has_seen_flood_wait_message)
has_seen_flood_wait_message = true
} else {
throw e
}
}
}
logger.trace("response.getMessages().size(): {}", response.getMessages().size) logger.trace("response.getMessages().size(): {}", response.getMessages().size)
if (response.getMessages().size != vector.size) { if (response.getMessages().size != vector.size) {
CommandLineController.show_error("Requested ${vector.size} messages, but got ${response.getMessages().size}. That is unexpected. Quitting.") CommandLineController.show_error("Requested ${vector.size} messages, but got ${response.getMessages().size}. That is unexpected. Quitting.")
} }
prog!!.onMessageDownloaded(response.getMessages().size) prog.onMessageDownloaded(response.getMessages().size)
db!!.saveMessages(response.getMessages(), Kotlogram.API_LAYER, source_type=source_type) db.saveMessages(response.getMessages(), Kotlogram.API_LAYER, source_type=source_type, settings=settings)
db!!.saveChats(response.getChats()) db.saveChats(response.getChats())
db!!.saveUsers(response.getUsers()) db.saveUsers(response.getUsers())
logger.trace("Sleeping") logger.trace("Sleeping")
try { try { TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES) } catch (e: InterruptedException) { }
TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES)
} catch (e: InterruptedException) {
}
} }
logger.debug("Finished.") logger.debug("Finished.")
prog!!.onMessageDownloadFinished() prog.onMessageDownloadFinished()
} }
@Throws(RpcErrorException::class, IOException::class) @Throws(RpcErrorException::class, IOException::class)
fun downloadMedia() { fun downloadMedia() {
download_client = client!!.getDownloaderClient() download_client = client.getDownloaderClient()
var completed: Boolean
do {
completed = true
try {
_downloadMedia()
} catch (e: RpcErrorException) {
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.");
logger.warn("TimeoutException caught", e);
try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {}
System.out.println("");
}*/
} while (!completed)
}
@Throws(RpcErrorException::class, IOException::class)
private fun _downloadMedia() {
logger.info("This is _downloadMedia") logger.info("This is _downloadMedia")
logger.info("Checking if there are messages in the DB with a too old API layer") logger.info("Checking if there are messages in the DB with a too old API layer")
val ids = db!!.getIdsFromQuery("SELECT id FROM messages WHERE has_media=1 AND api_layer<" + Kotlogram.API_LAYER) val ids = db.getIdsFromQuery("SELECT id FROM messages WHERE has_media=1 AND api_layer<" + Kotlogram.API_LAYER)
if (ids.size > 0) { if (ids.size > 0) {
System.out.println("You have ${ids.size} messages in your db that need an update. Doing that now.") System.out.println("You have ${ids.size} messages in your db that need an update. Doing that now.")
logger.debug("Found {} messages", ids.size) logger.debug("Found {} messages", ids.size)
downloadMessages(ids, null, source_type=MessageSource.NORMAL) downloadMessages(ids, null, source_type=MessageSource.NORMAL)
} }
val message_count = db.getMessagesWithMediaCount()
prog.onMediaDownloadStart(message_count)
var offset = 0 var offset = 0
val limit = 1000 val limit = 1000
val message_count = this.db!!.getMessagesWithMediaCount()
prog!!.onMediaDownloadStart(message_count)
while (true) { while (true) {
logger.debug("Querying messages with media, limit={}, offset={}", limit, offset) logger.debug("Querying messages with media, limit={}, offset={}", limit, offset)
val messages = this.db!!.getMessagesWithMedia(limit=limit, offset=offset) val messages = db.getMessagesWithMedia(limit, offset)
if (messages.size == 0) break if (messages.size == 0) break
offset += limit offset += limit
logger.debug("Database returned {} messages with media", messages.size) logger.debug("Database returned {} messages with media", messages.size)
prog.onMediaDownloadStart(messages.size)
for (msg in messages) { for (msg in messages) {
if (msg == null) continue if (msg == null) continue
val m = FileManagerFactory.getFileManager(msg, user!!, client!!) val m = FileManagerFactory.getFileManager(msg, user_manager, file_base, settings=settings)
logger.trace("message {}, {}, {}, {}, {}", logger.trace("message {}, {}, {}, {}, {}",
msg.getId(), msg.getId(),
msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", ""), msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", ""),
@ -331,27 +249,27 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
if (m.isEmpty) "empty" else "non-empty", if (m.isEmpty) "empty" else "non-empty",
if (m.downloaded) "downloaded" else "not downloaded") if (m.downloaded) "downloaded" else "not downloaded")
if (m.isEmpty) { if (m.isEmpty) {
prog!!.onMediaDownloadedEmpty() prog.onMediaDownloadedEmpty()
} else if (m.downloaded) { } else if (m.downloaded) {
prog!!.onMediaAlreadyPresent(m) prog.onMediaAlreadyPresent(m)
} else if (IniSettings.max_file_age!=null && (System.currentTimeMillis() / 1000) - msg.date > IniSettings.max_file_age * 24 * 60 * 60) { } else if (settings.max_file_age>0 && (System.currentTimeMillis() / 1000) - msg.date > settings.max_file_age * 24 * 60 * 60) {
prog!!.onMediaTooOld() prog.onMediaTooOld()
} else { } else {
try { try {
val result = m.download() val result = m.download()
if (result) { if (result) {
prog!!.onMediaDownloaded(m) prog.onMediaDownloaded(m)
} else { } else {
prog!!.onMediaSkipped() prog.onMediaSkipped()
} }
} catch (e: TimeoutException) { } catch (e: TimeoutException) {
// do nothing - skip this file // do nothing - skip this file
prog!!.onMediaSkipped() prog.onMediaSkipped()
} }
} }
} }
} }
prog!!.onMediaDownloadFinished() prog.onMediaDownloadFinished()
} }
private fun makeIdList(start: Int, end: Int): MutableList<Int> { private fun makeIdList(start: Int, end: Int): MutableList<Int> {
@ -363,7 +281,7 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
fun getChats(): ChatList { fun getChats(): ChatList {
val cl = ChatList() val cl = ChatList()
logger.trace("Calling messagesGetDialogs") logger.trace("Calling messagesGetDialogs")
val dialogs = client!!.messagesGetDialogs(0, 0, TLInputPeerEmpty(), 100) val dialogs = client.messagesGetDialogs(0, 0, TLInputPeerEmpty(), 100)
logger.trace("Got {} dialogs back", dialogs.getDialogs().size) logger.trace("Got {} dialogs back", dialogs.getDialogs().size)
// Add dialogs // Add dialogs
@ -376,10 +294,10 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
if (tl_peer_channel == null) continue if (tl_peer_channel == null) continue
var download = true var download = true
if (IniSettings.whitelist_channels != null) { if (settings.whitelist_channels.isNotEmpty()) {
download = IniSettings.whitelist_channels!!.contains(tl_channel.getId().toString()) download = settings.whitelist_channels.contains(tl_channel.getId().toString())
} else if (IniSettings.blacklist_channels != null) { } else if (settings.blacklist_channels.isNotEmpty()) {
download = !IniSettings.blacklist_channels!!.contains(tl_channel.getId().toString()) download = !settings.blacklist_channels.contains(tl_channel.getId().toString())
} }
val channel = Channel(id=tl_channel.getId(), access_hash=tl_channel.getAccessHash(), title=tl_channel.getTitle(), obj=tl_peer_channel, download=download) val channel = Channel(id=tl_channel.getId(), access_hash=tl_channel.getAccessHash(), title=tl_channel.getTitle(), obj=tl_peer_channel, download=download)
if (tl_channel.getMegagroup()) { if (tl_channel.getMegagroup()) {
@ -440,38 +358,32 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
} }
logger.trace("offset before the loop is {}", offset) logger.trace("offset before the loop is {}", offset)
fos = FileOutputStream(temp_filename, true) fos = FileOutputStream(temp_filename, true)
var response: TLFile? = null
var try_again: Boolean
do { do {
try_again = false
logger.trace("offset: {} block_size: {} size: {}", offset, size, size) logger.trace("offset: {} block_size: {} size: {}", offset, size, size)
val req = TLRequestUploadGetFile(loc, offset, size) val req = TLRequestUploadGetFile(loc, offset, size)
var resp: TLFile? = null
try { try {
response = download_client!!.executeRpcQuery(req, dcID) as TLFile Utils.obeyFloodWait() {
resp = download_client!!.executeRpcQuery(req, dcID) as TLFile
}
} catch (e: RpcErrorException) { } catch (e: RpcErrorException) {
if (e.getCode() == 420) { // FLOOD_WAIT if (e.getCode() == 400) {
try_again = true // Somehow this file is broken. No idea why. Let's skip it for now.
Utils.obeyFloodWaitException(e)
continue // response is null since we didn't actually receive any data. Skip the rest of this iteration and try again.
} else if (e.getCode() == 400) {
//Somehow this file is broken. No idea why. Let's skip it for now
return false return false
} else { }
throw e throw e
} }
}
val response = resp!!
offset += response.getBytes().getData().size offset += response.getBytes().getData().size
logger.trace("response: {} total size: {}", response.getBytes().getData().size, offset) logger.trace("response: {} total size: {}", response.getBytes().getData().size, offset)
fos.write(response.getBytes().getData()) fos.write(response.getBytes().getData())
fos.flush() fos.flush()
try { try { TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_FILE) } catch (e: InterruptedException) { }
TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_FILE)
} catch (e: InterruptedException) {
}
} while (offset < size && (try_again || response!!.getBytes().getData().size > 0)) } while (offset < size && response.getBytes().getData().size > 0)
fos.close() fos.close()
if (offset < size) { if (offset < size) {
System.out.println("Requested file $target with $size bytes, but got only $offset bytes.") System.out.println("Requested file $target with $size bytes, but got only $offset bytes.")

View File

@ -1,85 +0,0 @@
/* Telegram_Backup
* Copyright (C) 2016 Fabian Schlenz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup
import javax.swing.*
import javax.swing.event.ListSelectionEvent
import javax.swing.event.ListSelectionListener
import java.awt.*
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.util.Vector
class GUIController() {
init {
showAccountChooserDialog()
}
private fun showAccountChooserDialog() {
val accountChooser = JDialog()
accountChooser.setTitle("Choose account")
accountChooser.setSize(400, 200)
val vert = JPanel()
vert.setLayout(BorderLayout())
vert.add(JLabel("Please select the account to use or create a new one."), BorderLayout.NORTH)
val accounts = Utils.getAccounts()
val list = JList<String>(accounts)
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
vert.add(list, BorderLayout.CENTER)
val bottom = JPanel(GridLayout(1, 2))
val btnAddAccount = JButton("Add account")
bottom.add(btnAddAccount)
val btnLogin = JButton("Login")
btnLogin.setEnabled(false)
bottom.add(btnLogin)
vert.add(bottom, BorderLayout.SOUTH)
accountChooser.add(vert)
accountChooser.setVisible(true)
accountChooser.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
}
private fun addAccountDialog() {
val loginDialog = JDialog()
loginDialog.setTitle("Add an account")
loginDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
val sections = JPanel()
sections.setLayout(BoxLayout(sections, BoxLayout.Y_AXIS))
val top = JPanel()
top.setLayout(BoxLayout(top, BoxLayout.Y_AXIS))
top.add(JLabel("Please enter your phone number in international format:"))
top.add(JTextField("+49123773212"))
sections.add(top)
sections.add(Box.createVerticalStrut(5))
sections.add(JSeparator(SwingConstants.HORIZONTAL))
val middle = JPanel()
middle.setLayout(BoxLayout(middle, BoxLayout.Y_AXIS))
middle.add(JLabel("Telegram sent you a code. Enter it here:"))
middle.add(JTextField())
middle.setEnabled(false)
sections.add(middle)
sections.add(Box.createVerticalStrut(5))
sections.add(JSeparator(SwingConstants.HORIZONTAL))
loginDialog.add(sections)
loginDialog.setVisible(true)
}
}

View File

@ -1,86 +0,0 @@
package de.fabianonline.telegram_backup
import java.io.File
import org.slf4j.LoggerFactory
import org.slf4j.Logger
object IniSettings {
val logger = LoggerFactory.getLogger(IniSettings::class.java)
var settings = mutableMapOf<String, MutableList<String>>()
init {
if (UserManager.getInstance().user != null) {
loadIni(UserManager.getInstance().fileBase + "config.ini")
copySampleIni(UserManager.getInstance().fileBase + "config.sample.ini")
}
}
// Dummy function that can be called to force this object to run its init-code.
fun load() { }
private fun loadIni(filename: String) {
val file = File(filename)
logger.trace("Checking ini file {}", filename.anonymize())
if (!file.exists()) return
logger.debug("Loading ini file {}", filename.anonymize())
file.forEachLine { parseLine(it) }
}
private fun parseLine(original_line: String) {
logger.trace("Parsing line: {}", original_line)
var line = original_line.trim().replaceAfter("#", "").removeSuffix("#")
logger.trace("After cleaning: {}", line)
if (line == "") return
val parts: List<String> = line.split("=", limit=2).map{it.trim()}
if (parts.size < 2) throw RuntimeException("Invalid config setting: $line")
val (key, value) = parts
if (value=="") {
settings.remove(key)
} else {
var map = settings.get(key)
if (map == null) {
map = mutableListOf<String>()
settings.put(key, map)
}
map.add(value)
}
}
private fun copySampleIni(filename: String) {
val stream = Config::class.java.getResourceAsStream("/config.sample.ini")
File(filename).outputStream().use { stream.copyTo(it) }
stream.close()
}
fun println() = println(settings)
fun getString(key: String, default: String? = null): String? = settings.get(key)?.last() ?: default
fun getStringList(key: String): List<String>? = settings.get(key)
fun getInt(key: String, default: Int? = null): Int? = try { settings.get(key)?.last()?.toInt() } catch (e: NumberFormatException) { null } ?: default
fun getBoolean(key: String, default: Boolean = false): Boolean {
val value = settings.get(key)?.last()
if (value==null) return default
return value=="true"
}
fun getArray(key: String): List<String> = settings.get(key) ?: listOf<String>()
val gmaps_key: String
get() = getString("gmaps_key", default=Config.SECRET_GMAPS)!!
val pagination: Boolean
get() = getBoolean("pagination", default=true)
val pagination_size: Int
get() = getInt("pagination_size", default=Config.DEFAULT_PAGINATION)!!
val download_media: Boolean
get() = getBoolean("download_media", default=true)
val download_channels: Boolean
get() = getBoolean("download_channels", default=false)
val download_supergroups: Boolean
get() = getBoolean("download_supergroups", default=false)
val whitelist_channels: List<String>?
get() = getStringList("whitelist_channels")
val blacklist_channels: List<String>?
get() = getStringList("blacklist_channels")
val max_file_age = getInt("max_file_age")
}

View File

@ -0,0 +1,96 @@
package de.fabianonline.telegram_backup
import com.github.badoualy.telegram.api.Kotlogram
import com.github.badoualy.telegram.api.TelegramApp
import com.github.badoualy.telegram.api.TelegramClient
import com.github.badoualy.telegram.tl.api.TLUser
import com.github.badoualy.telegram.tl.api.account.TLPassword
import com.github.badoualy.telegram.tl.api.auth.TLSentCode
import com.github.badoualy.telegram.tl.core.TLBytes
import com.github.badoualy.telegram.tl.exception.RpcErrorException
import java.security.MessageDigest
import java.util.*
class LoginManager(val app: TelegramApp, val target_dir: String, val phoneToUse: String?) {
fun run() {
var phone: String
if (phoneToUse == null) {
println("Please enter your phone number in international format.")
println("Example: +4917077651234")
phone = getLine()
} else {
phone = phoneToUse
}
val file_base = CommandLineController.build_file_base(target_dir, phone)
// We now have an account, so we can create an ApiStorage and TelegramClient.
val storage = ApiStorage(file_base)
val client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, null)
val sent_code = send_code_to_phone_number(client, phone)
println("Telegram sent you a code. Please enter it here.")
val code = getLine()
try {
verify_code(client, phone, sent_code, code)
} catch(e: PasswordNeededException) {
println("We also need your account password. Please enter it now. It should not be printed, so it's okay if you see nothing while typing it.")
val pw = getPassword()
verify_password(client, pw)
}
System.out.println("Everything seems fine. Please run this tool again with '--account ${phone} to use this account.")
}
private fun send_code_to_phone_number(client: TelegramClient, phone: String): TLSentCode {
return client.authSendCode(false, phone, true)
}
private fun verify_code(client: TelegramClient, phone: String, sent_code: TLSentCode, code: String): TLUser {
try {
val auth = client.authSignIn(phone, sent_code.getPhoneCodeHash(), code)
return auth.getUser().getAsUser()
} catch (e: RpcErrorException) {
if (e.getCode() == 401 && e.getTag()=="SESSION_PASSWORD_NEEDED") {
throw PasswordNeededException()
} else {
throw e
}
}
}
private fun verify_password(client: TelegramClient, password: String): TLUser {
val pw = password.toByteArray(charset = Charsets.UTF_8)
val salt = (client.accountGetPassword() as TLPassword).getCurrentSalt().getData()
val md = MessageDigest.getInstance("SHA-256")
val salted = ByteArray(2 * salt.size + pw.size)
System.arraycopy(salt, 0, salted, 0, salt.size)
System.arraycopy(pw, 0, salted, salt.size, pw.size)
System.arraycopy(salt, 0, salted, salt.size + pw.size, salt.size)
val hash = md.digest(salted)
val auth = client.authCheckPassword(TLBytes(hash))
return auth.getUser().getAsUser()
}
private fun getLine(): String {
if (System.console() != null) {
return System.console().readLine("> ")
} else {
print("> ")
return Scanner(System.`in`).nextLine()
}
}
private fun getPassword(): String {
if (System.console() != null) {
return String(System.console().readPassword("> "))
} else {
return getLine()
}
}
}
class PasswordNeededException: Exception("A password is needed to be able to login to this account.") {}

View File

@ -0,0 +1,135 @@
package de.fabianonline.telegram_backup
import java.io.File
import java.util.LinkedList
import org.slf4j.LoggerFactory
class Settings(val file_base: String, val database: Database, val cli_settings: CommandLineOptions) {
val logger = LoggerFactory.getLogger(Settings::class.java)
private val db_settings: Map<String, String>
val ini_settings: Map<String, List<String>>
init {
db_settings = database.fetchSettings()
ini_settings = load_ini("config.ini")
copy_sample_ini("config.sample.ini")
}
// Merging CLI and INI settings
val sf = SettingsFactory(ini_settings, cli_settings)
val gmaps_key = sf.getString("gmaps_key", default=Config.SECRET_GMAPS, secret=true)
val pagination = sf.getBoolean("pagination", default=true)
val pagination_size = sf.getInt("pagination_size", default=Config.DEFAULT_PAGINATION)
val download_media = sf.getBoolean("download_media", default=true)
val download_channels = sf.getBoolean("download_channels", default=false)
val download_supergroups = sf.getBoolean("download_supergroups", default=false)
val whitelist_channels = sf.getStringList("whitelist_channels", default=LinkedList<String>())
val blacklist_channels = sf.getStringList("blacklist_channels", default=LinkedList<String>())
val max_file_age = sf.getInt("max_file_age", default=-1)
private fun get_setting_list(name: String): List<String>? {
return ini_settings[name]
}
private fun load_ini(filename: String): Map<String, List<String>> {
val map = mutableMapOf<String, MutableList<String>>()
val file = File(file_base + filename)
logger.trace("Checking ini file {}", filename.anonymize())
if (!file.exists()) return map
logger.debug("Loading ini file {}", filename.anonymize())
file.forEachLine { parseLine(it, map) }
return map
}
private fun parseLine(original_line: String, map: MutableMap<String, MutableList<String>>) {
logger.trace("Parsing line: {}", original_line)
var line = original_line.trim().replaceAfter("#", "").removeSuffix("#")
logger.trace("After cleaning: {}", line)
if (line == "") return
val parts: List<String> = line.split("=", limit=2).map{it.trim()}
if (parts.size < 2) throw RuntimeException("Invalid config setting: $line")
val (key, value) = parts
if (value=="") {
map.remove(key)
} else {
var list = map.get(key)
if (list == null) {
list = mutableListOf<String>()
map.put(key, list)
}
list.add(value)
}
}
private fun copy_sample_ini(filename: String) {
val stream = Config::class.java.getResourceAsStream("/config.sample.ini")
File(filename).outputStream().use { stream.copyTo(it) }
stream.close()
}
fun print() {
println()
Setting.all_settings.forEach { it.print() }
println()
}
}
class SettingsFactory(val ini: Map<String, List<String>>, val cli: CommandLineOptions) {
fun getInt(name: String, default: Int, secret: Boolean = false) = getSetting(name, listOf(default.toString()), secret).get().toInt()
fun getBoolean(name: String, default: Boolean, secret: Boolean = false) = getSetting(name, listOf(default.toString()), secret).get().toBoolean()
fun getString(name: String, default: String, secret: Boolean = false) = getSetting(name, listOf(default), secret).get()
fun getStringList(name: String, default: List<String>, secret: Boolean = false) = getSetting(name, default, secret).getList()
fun getSetting(name: String, default: List<String>, secret: Boolean) = Setting(ini, cli, name, default, secret)
}
class Setting(val ini: Map<String, List<String>>, val cli: CommandLineOptions, val name: String, val default: List<String>, val secret: Boolean) {
val values: List<String>
val source: SettingSource
val logger = LoggerFactory.getLogger(Setting::class.java)
init {
if (getCli(name) != null) {
values = listOf(getCli(name)!!)
source = SettingSource.CLI
} else if (getIni(name) != null) {
values = getIni(name)!!
source = SettingSource.INI
} else {
values = default
source = SettingSource.DEFAULT
}
logger.debug("Setting ${name} loaded. Source: ${source}. Value: ${values.toString().anonymize()}")
all_settings.add(this)
}
fun get(): String = values.last()
fun getList(): List<String> = values
fun getIni(name: String): List<String>? {
return ini[name]
}
fun getCli(name: String): String? {
return cli.get(name)
}
fun print() {
println("%-25s %-10s %s".format(name, source, (if (secret && source==SettingSource.DEFAULT) "[REDACTED]" else values)))
}
companion object {
val all_settings = LinkedList<Setting>()
}
}
enum class SettingSource {
INI,
CLI,
DEFAULT
}

View File

@ -1,52 +0,0 @@
/* Telegram_Backup
* Copyright (C) 2016 Fabian Schlenz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup
import com.github.badoualy.telegram.tl.api.*
import java.lang.StringBuilder
import java.io.File
object StickerConverter {
fun makeFilenameWithPath(attr: TLDocumentAttributeSticker): String {
val file = StringBuilder()
file.append(makePath())
file.append(makeFilename(attr))
return file.toString()
}
fun makeFilename(attr: TLDocumentAttributeSticker): String {
val file = StringBuilder()
if (attr.getStickerset() is TLInputStickerSetShortName) {
file.append((attr.getStickerset() as TLInputStickerSetShortName).getShortName())
} else if (attr.getStickerset() is TLInputStickerSetID) {
file.append((attr.getStickerset() as TLInputStickerSetID).getId())
}
file.append("_")
file.append(attr.getAlt().hashCode())
file.append(".webp")
return file.toString()
}
fun makePath(): String {
val path = Config.FILE_BASE +
File.separatorChar +
Config.FILE_STICKER_BASE +
File.separatorChar
File(path).mkdirs()
return path
}
}

View File

@ -26,48 +26,39 @@ import de.fabianonline.telegram_backup.Database
import de.fabianonline.telegram_backup.UserManager import de.fabianonline.telegram_backup.UserManager
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
import org.slf4j.LoggerFactory
internal class TelegramUpdateHandler : UpdateCallback { internal class TelegramUpdateHandler(val user_manager: UserManager, val db: Database, val file_base: String, val settings: Settings) : UpdateCallback {
private var user: UserManager? = null val logger = LoggerFactory.getLogger(TelegramUpdateHandler::class.java)
private var db: Database? = null
var debug = false
fun activate() {
this.user = UserManager.getInstance()
this.db = Database.getInstance()
}
override fun onUpdates(client: TelegramClient, updates: TLUpdates) { override fun onUpdates(client: TelegramClient, updates: TLUpdates) {
if (db == null) return
if (debug) System.out.println("onUpdates - " + updates.getUpdates().size + " Updates, " + updates.getUsers().size + " Users, " + updates.getChats().size + " Chats") logger.debug("onUpdates - " + updates.getUpdates().size + " Updates, " + updates.getUsers().size + " Users, " + updates.getChats().size + " Chats")
for (update in updates.getUpdates()) { for (update in updates.getUpdates()) {
processUpdate(update, client) processUpdate(update)
if (debug) System.out.println(" " + update.javaClass.getName()) logger.debug(" " + update.javaClass.getName())
} }
db!!.saveUsers(updates.getUsers()) db.saveUsers(updates.getUsers())
db!!.saveChats(updates.getChats()) db.saveChats(updates.getChats())
} }
override fun onUpdatesCombined(client: TelegramClient, updates: TLUpdatesCombined) { override fun onUpdatesCombined(client: TelegramClient, updates: TLUpdatesCombined) {
if (db == null) return logger.debug("onUpdatesCombined")
if (debug) System.out.println("onUpdatesCombined")
for (update in updates.getUpdates()) { for (update in updates.getUpdates()) {
processUpdate(update, client) processUpdate(update)
} }
db!!.saveUsers(updates.getUsers()) db.saveUsers(updates.getUsers())
db!!.saveChats(updates.getChats()) db.saveChats(updates.getChats())
} }
override fun onUpdateShort(client: TelegramClient, update: TLUpdateShort) { override fun onUpdateShort(client: TelegramClient, update: TLUpdateShort) {
if (db == null) return logger.debug("onUpdateShort")
if (debug) System.out.println("onUpdateShort") processUpdate(update.getUpdate())
processUpdate(update.getUpdate(), client) logger.debug(" " + update.getUpdate().javaClass.getName())
if (debug) System.out.println(" " + update.getUpdate().javaClass.getName())
} }
override fun onShortChatMessage(client: TelegramClient, message: TLUpdateShortChatMessage) { override fun onShortChatMessage(client: TelegramClient, message: TLUpdateShortChatMessage) {
if (db == null) return logger.debug("onShortChatMessage - " + message.getMessage())
if (debug) System.out.println("onShortChatMessage - " + message.getMessage())
val msg = TLMessage( val msg = TLMessage(
message.getOut(), message.getOut(),
message.getMentioned(), message.getMentioned(),
@ -85,21 +76,20 @@ internal class TelegramUpdateHandler : UpdateCallback {
message.getEntities(), null, null) message.getEntities(), null, null)
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java) val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
vector.add(msg) vector.add(msg)
db!!.saveMessages(vector, Kotlogram.API_LAYER) db.saveMessages(vector, Kotlogram.API_LAYER, settings=settings)
System.out.print('.') System.out.print('.')
} }
override fun onShortMessage(client: TelegramClient, message: TLUpdateShortMessage) { override fun onShortMessage(client: TelegramClient, message: TLUpdateShortMessage) {
val m = message val m = message
if (db == null) return logger.debug("onShortMessage - " + m.getOut() + " - " + m.getUserId() + " - " + m.getMessage())
if (debug) System.out.println("onShortMessage - " + m.getOut() + " - " + m.getUserId() + " - " + m.getMessage())
val from_id: Int val from_id: Int
val to_id: Int val to_id: Int
if (m.getOut() == true) { if (m.getOut() == true) {
from_id = user!!.user!!.getId() from_id = user_manager.id
to_id = m.getUserId() to_id = m.getUserId()
} else { } else {
to_id = user!!.user!!.getId() to_id = user_manager.id
from_id = m.getUserId() from_id = m.getUserId()
} }
val msg = TLMessage( val msg = TLMessage(
@ -119,29 +109,27 @@ internal class TelegramUpdateHandler : UpdateCallback {
m.getEntities(), null, null) m.getEntities(), null, null)
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java) val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
vector.add(msg) vector.add(msg)
db!!.saveMessages(vector, Kotlogram.API_LAYER) db.saveMessages(vector, Kotlogram.API_LAYER, settings=settings)
System.out.print('.') System.out.print('.')
} }
override fun onShortSentMessage(client: TelegramClient, message: TLUpdateShortSentMessage) { override fun onShortSentMessage(client: TelegramClient, message: TLUpdateShortSentMessage) {
if (db == null) return logger.debug("onShortSentMessage")
System.out.println("onShortSentMessage")
} }
override fun onUpdateTooLong(client: TelegramClient) { override fun onUpdateTooLong(client: TelegramClient) {
if (db == null) return logger.debug("onUpdateTooLong")
System.out.println("onUpdateTooLong")
} }
private fun processUpdate(update: TLAbsUpdate, client: TelegramClient) { private fun processUpdate(update: TLAbsUpdate) {
if (update is TLUpdateNewMessage) { if (update is TLUpdateNewMessage) {
val abs_msg = update.getMessage() val abs_msg = update.getMessage()
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java) val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
vector.add(abs_msg) vector.add(abs_msg)
db!!.saveMessages(vector, Kotlogram.API_LAYER) db.saveMessages(vector, Kotlogram.API_LAYER, settings=settings)
System.out.print('.') System.out.print('.')
if (abs_msg is TLMessage && IniSettings.download_media==true) { if (abs_msg is TLMessage && settings.download_media==true) {
val fm = FileManagerFactory.getFileManager(abs_msg, user!!, client) val fm = FileManagerFactory.getFileManager(abs_msg, user_manager, file_base, settings)
if (fm != null && !fm.isEmpty && !fm.downloaded) { if (fm != null && !fm.isEmpty && !fm.downloaded) {
try { try {
fm.download() fm.download()

View File

@ -10,7 +10,7 @@ import java.sql.ResultSet
import java.io.IOException import java.io.IOException
import java.nio.charset.Charset import java.nio.charset.Charset
internal object TestFeatures { internal class TestFeatures(val db: Database) {
fun test1() { fun test1() {
// Tests entries in a cache4.db in the current working directory for compatibility // Tests entries in a cache4.db in the current working directory for compatibility
try { try {
@ -24,18 +24,13 @@ internal object TestFeatures {
var conn: Connection var conn: Connection
var stmt: Statement? = null var stmt: Statement? = null
try {
conn = DriverManager.getConnection(path) conn = DriverManager.getConnection(path)
stmt = conn.createStatement() stmt = conn.createStatement()
} catch (e: SQLException) {
CommandLineController.show_error("Could not connect to SQLITE database.")
}
var unsupported_constructor = 0 var unsupported_constructor = 0
var success = 0 var success = 0
try { val rs = stmt.executeQuery("SELECT data FROM messages")
val rs = stmt!!.executeQuery("SELECT data FROM messages")
while (rs.next()) { while (rs.next()) {
try { try {
TLApiContext.getInstance().deserializeMessage(rs.getBytes(1)) TLApiContext.getInstance().deserializeMessage(rs.getBytes(1))
@ -44,12 +39,8 @@ internal object TestFeatures {
} catch (e: IOException) { } catch (e: IOException) {
System.out.println("IOException: " + e) System.out.println("IOException: " + e)
} }
success++ success++
} }
} catch (e: SQLException) {
System.out.println("SQL exception: " + e)
}
System.out.println("Success: " + success) System.out.println("Success: " + success)
System.out.println("Unsupported constructor: " + unsupported_constructor) System.out.println("Unsupported constructor: " + unsupported_constructor)
@ -59,7 +50,6 @@ internal object TestFeatures {
// Prints system.encoding and default charset // Prints system.encoding and default charset
System.out.println("Default Charset: " + Charset.defaultCharset()) System.out.println("Default Charset: " + Charset.defaultCharset())
System.out.println("file.encoding: " + System.getProperty("file.encoding")) System.out.println("file.encoding: " + System.getProperty("file.encoding"))
val db = Database.getInstance()
System.out.println("Database encoding: " + db.getEncoding()) System.out.println("Database encoding: " + db.getEncoding())
} }
} }

View File

@ -34,107 +34,34 @@ import java.io.File
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.Logger import org.slf4j.Logger
class UserManager @Throws(IOException::class) class UserManager(val client: TelegramClient) {
private constructor(c: TelegramClient) { val tl_user: TLUser
var user: TLUser? = null val logger = LoggerFactory.getLogger(UserManager::class.java)
var phone: String? = null val phone: String
private var code: String? = null get() = "+" + tl_user.getPhone()
private val client: TelegramClient val id: Int
private var sent_code: TLSentCode? = null get() = tl_user.getId()
private var auth: TLAuthorization? = null
var isPasswordNeeded = false
private set
val loggedIn: Boolean init {
get() = user != null logger.debug("Calling getFullUser")
val full_user = client.usersGetFullUser(TLInputUserSelf())
tl_user = full_user.getUser().getAsUser()
}
val userString: String override fun toString(): String {
get() {
if (this.user == null) return "Not logged in"
val sb = StringBuilder() val sb = StringBuilder()
if (this.user!!.getFirstName() != null) { sb.append(tl_user.getFirstName() ?: "")
sb.append(this.user!!.getFirstName()) if (tl_user.getLastName() != null) {
}
if (this.user!!.getLastName() != null) {
sb.append(" ") sb.append(" ")
sb.append(this.user!!.getLastName()) sb.append(tl_user.getLastName())
} }
if (this.user!!.getUsername() != null) { if (tl_user.getUsername() != null) {
sb.append(" (@") sb.append(" (@")
sb.append(this.user!!.getUsername()) sb.append(tl_user.getUsername())
sb.append(")") sb.append(")")
} }
return sb.toString() return sb.toString()
} }
val fileBase: String
get() = Config.FILE_BASE + File.separatorChar + "+" + this.user!!.getPhone() + File.separatorChar
init {
this.client = c
logger.debug("Calling getFullUser")
try {
val full_user = this.client.usersGetFullUser(TLInputUserSelf())
this.user = full_user.getUser().getAsUser()
} catch (e: RpcErrorException) {
// This may happen. Ignoring it.
logger.debug("Ignoring exception:", e)
}
}
@Throws(RpcErrorException::class, IOException::class)
fun sendCodeToPhoneNumber(number: String) {
this.phone = number
this.sent_code = this.client.authSendCode(false, number, true)
}
@Throws(RpcErrorException::class, IOException::class)
fun verifyCode(code: String) {
this.code = code
try {
this.auth = client.authSignIn(phone, this.sent_code!!.getPhoneCodeHash(), this.code)
this.user = auth!!.getUser().getAsUser()
} catch (e: RpcErrorException) {
if (e.getCode() != 401 || !e.getTag().equals("SESSION_PASSWORD_NEEDED")) throw e
this.isPasswordNeeded = true
}
}
@Throws(RpcErrorException::class, IOException::class)
fun verifyPassword(pw: String) {
val password = pw.toByteArray(charset = Charsets.UTF_8)
val salt = (client.accountGetPassword() as TLPassword).getCurrentSalt().getData()
var md: MessageDigest
try {
md = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
return
}
val salted = ByteArray(2 * salt.size + password.size)
System.arraycopy(salt, 0, salted, 0, salt.size)
System.arraycopy(password, 0, salted, salt.size, password.size)
System.arraycopy(salt, 0, salted, salt.size + password.size, salt.size)
val hash = md.digest(salted)
auth = client.authCheckPassword(TLBytes(hash))
this.user = auth!!.getUser().getAsUser()
}
companion object {
private val logger = LoggerFactory.getLogger(UserManager::class.java)
internal var instance: UserManager? = null
@Throws(IOException::class)
fun init(c: TelegramClient) {
instance = UserManager(c)
}
fun getInstance(): UserManager {
if (instance == null) throw RuntimeException("UserManager is not yet initialized.")
return instance!!
}
}
} }

View File

@ -20,6 +20,7 @@ import com.github.badoualy.telegram.tl.exception.RpcErrorException
import java.io.File import java.io.File
import java.util.Vector import java.util.Vector
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import com.google.gson.* import com.google.gson.*
import java.net.URL import java.net.URL
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
@ -32,11 +33,29 @@ object Utils {
@JvmField public val VERSION_1_NEWER = 1 @JvmField public val VERSION_1_NEWER = 1
@JvmField public val VERSION_2_NEWER = 2 @JvmField public val VERSION_2_NEWER = 2
var hasSeenFloodWaitMessage = false
var anonymize = false
private val logger = LoggerFactory.getLogger(Utils::class.java) as Logger private val logger = LoggerFactory.getLogger(Utils::class.java) as Logger
fun getAccounts(): Vector<String> { fun print_accounts(file_base: String) {
println("List of available accounts:")
val accounts = getAccounts(file_base)
if (accounts.size > 0) {
for (str in accounts) {
println(" " + str.anonymize())
}
println("Use '--account <x>' to use one of those accounts.")
} else {
println("NO ACCOUNTS FOUND")
println("Use '--login' to login to a telegram account.")
}
}
fun getAccounts(file_base: String): Vector<String> {
val accounts = Vector<String>() val accounts = Vector<String>()
val folder = File(Config.FILE_BASE) val folder = File(file_base)
val files = folder.listFiles() val files = folder.listFiles()
if (files != null) if (files != null)
for (f in files) { for (f in files) {
@ -81,29 +100,47 @@ object Utils {
} }
@Throws(RpcErrorException::class) fun obeyFloodWait(max_tries: Int = -1, method: () -> Unit) {
@JvmOverloads internal fun obeyFloodWaitException(e: RpcErrorException?, silent: Boolean = false) { var tries = 0
if (e == null || e.getCode() != 420) return while (true) {
tries++
if (max_tries>0 && tries>max_tries) throw MaxTriesExceededException()
logger.trace("This is try ${tries}.")
try {
method.invoke()
// If we reach this, the method has returned successfully -> we are done
return
} catch(e: RpcErrorException) {
// If we got something else than a FLOOD_WAIT error, we just rethrow it
if (e.getCode() != 420) throw e
val delay: Long = e.getTagInteger()!!.toLong() val delay = e.getTagInteger()!!.toLong()
if (!silent) {
System.out.println("") if (!hasSeenFloodWaitMessage) {
System.out.println( println(
"\n" +
"Telegram complained about us (okay, me) making too many requests in too short time by\n" + "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" + "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" + "asked us to wait for ${delay} seconds.\n" +
"\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" + "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" + "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" + "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.") "the fact that Telegram won't talk to me until then." +
System.out.println("") "\n")
}
try {
TimeUnit.SECONDS.sleep(delay + 1)
} catch (e2: InterruptedException) {
} }
hasSeenFloodWaitMessage = true
try { TimeUnit.SECONDS.sleep(delay + 1) } catch (e: InterruptedException) { }
} catch (e: TimeoutException) {
println(
"\n" +
"Telegram took too long to respond to our request.\n" +
"I'm going to wait a minute and then try again." +
"\n")
try { TimeUnit.MINUTES.sleep(1) } catch (e: InterruptedException) { }
}
}
} }
@JvmStatic @JvmStatic
@ -179,8 +216,10 @@ object Utils {
} }
fun String.anonymize(): String { fun String.anonymize(): String {
return if (!CommandLineOptions.cmd_anonymize) this else this.replace(Regex("[0-9]"), "1").replace(Regex("[A-Z]"), "A").replace(Regex("[a-z]"), "a") + " (ANONYMIZED)" return if (!Utils.anonymize) this else this.replace(Regex("[0-9]"), "1").replace(Regex("[A-Z]"), "A").replace(Regex("[a-z]"), "a") + " (ANONYMIZED)"
} }
fun Any.toJson(): String = Gson().toJson(this) fun Any.toJson(): String = Gson().toJson(this)
fun Any.toPrettyJson(): String = GsonBuilder().setPrettyPrinting().create().toJson(this) fun Any.toPrettyJson(): String = GsonBuilder().setPrettyPrinting().create().toJson(this)
class MaxTriesExceededException(): RuntimeException("Max tries exceeded") {}

View File

@ -37,18 +37,15 @@ import de.fabianonline.telegram_backup.*
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class HTMLExporter { class HTMLExporter(val db: Database, val user: UserManager, val settings: Settings, val file_base: String) {
val db = Database.getInstance()
val user = UserManager.getInstance()
@Throws(IOException::class) @Throws(IOException::class)
fun export() { fun export() {
try { try {
val pagination = if (IniSettings.pagination) IniSettings.pagination_size else -1 val pagination = if (settings.pagination) settings.pagination_size else -1
// Create base dir // Create base dir
logger.debug("Creating base dir") logger.debug("Creating base dir")
val base = user.fileBase + "files" + File.separatorChar val base = file_base + "files" + File.separatorChar
File(base).mkdirs() File(base).mkdirs()
File(base + "dialogs").mkdirs() File(base + "dialogs").mkdirs()

View File

@ -17,33 +17,17 @@
package de.fabianonline.telegram_backup.mediafilemanager package de.fabianonline.telegram_backup.mediafilemanager
import de.fabianonline.telegram_backup.UserManager 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.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.*
import com.github.badoualy.telegram.tl.api.upload.TLFile
import com.github.badoualy.telegram.tl.exception.RpcErrorException import com.github.badoualy.telegram.tl.exception.RpcErrorException
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile import de.fabianonline.telegram_backup.Settings
import java.io.IOException import java.io.IOException
import java.io.File 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 java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils abstract class AbstractMediaFileManager(protected var message: TLMessage, protected var user: UserManager, val file_base: String) {
abstract class AbstractMediaFileManager(protected var message: TLMessage, protected var user: UserManager, protected var client: TelegramClient) {
open var isEmpty = false open var isEmpty = false
abstract val size: Int abstract val size: Int
abstract val extension: String abstract val extension: String
@ -56,7 +40,7 @@ abstract class AbstractMediaFileManager(protected var message: TLMessage, protec
open val targetPath: String open val targetPath: String
get() { get() {
val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar val path = file_base + Config.FILE_FILES_BASE + File.separatorChar
File(path).mkdirs() File(path).mkdirs()
return path return path
} }

View File

@ -17,32 +17,15 @@
package de.fabianonline.telegram_backup.mediafilemanager package de.fabianonline.telegram_backup.mediafilemanager
import de.fabianonline.telegram_backup.UserManager 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.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.*
import com.github.badoualy.telegram.tl.api.upload.TLFile
import com.github.badoualy.telegram.tl.exception.RpcErrorException import com.github.badoualy.telegram.tl.exception.RpcErrorException
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
import java.io.IOException 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 java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils open class DocumentFileManager(msg: TLMessage, user: UserManager, file_base: String) : AbstractMediaFileManager(msg, user, file_base) {
open class DocumentFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
protected var doc: TLDocument? = null protected var doc: TLDocument? = null
override lateinit var extension: String override lateinit var extension: String

View File

@ -18,7 +18,6 @@ package de.fabianonline.telegram_backup.mediafilemanager
import de.fabianonline.telegram_backup.UserManager import de.fabianonline.telegram_backup.UserManager
import de.fabianonline.telegram_backup.Database import de.fabianonline.telegram_backup.Database
import de.fabianonline.telegram_backup.StickerConverter
import de.fabianonline.telegram_backup.DownloadProgressInterface import de.fabianonline.telegram_backup.DownloadProgressInterface
import com.github.badoualy.telegram.api.TelegramClient import com.github.badoualy.telegram.api.TelegramClient
@ -30,6 +29,7 @@ import com.github.badoualy.telegram.tl.api.*
import com.github.badoualy.telegram.tl.api.upload.TLFile import com.github.badoualy.telegram.tl.api.upload.TLFile
import com.github.badoualy.telegram.tl.exception.RpcErrorException import com.github.badoualy.telegram.tl.exception.RpcErrorException
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
import de.fabianonline.telegram_backup.Settings
import java.io.IOException import java.io.IOException
import java.io.File import java.io.File
@ -42,29 +42,29 @@ import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
object FileManagerFactory { object FileManagerFactory {
fun getFileManager(m: TLMessage?, u: UserManager, c: TelegramClient): AbstractMediaFileManager? { fun getFileManager(m: TLMessage?, u: UserManager, file_base: String, settings: Settings?): AbstractMediaFileManager? {
if (m == null) return null if (m == null) return null
val media = m.getMedia() ?: return null val media = m.getMedia() ?: return null
if (media is TLMessageMediaPhoto) { if (media is TLMessageMediaPhoto) {
return PhotoFileManager(m, u, c) return PhotoFileManager(m, u, file_base)
} else if (media is TLMessageMediaDocument) { } else if (media is TLMessageMediaDocument) {
val d = DocumentFileManager(m, u, c) val d = DocumentFileManager(m, u, file_base)
return if (d.isSticker) { return if (d.isSticker) {
StickerFileManager(m, u, c) StickerFileManager(m, u, file_base)
} else d } else d
} else if (media is TLMessageMediaGeo) { } else if (media is TLMessageMediaGeo) {
return GeoFileManager(m, u, c) return GeoFileManager(m, u, file_base, settings)
} else if (media is TLMessageMediaEmpty) { } else if (media is TLMessageMediaEmpty) {
return UnsupportedFileManager(m, u, c, "empty") return UnsupportedFileManager(m, u, file_base, "empty")
} else if (media is TLMessageMediaUnsupported) { } else if (media is TLMessageMediaUnsupported) {
return UnsupportedFileManager(m, u, c, "unsupported") return UnsupportedFileManager(m, u, file_base, "unsupported")
} else if (media is TLMessageMediaWebPage) { } else if (media is TLMessageMediaWebPage) {
return UnsupportedFileManager(m, u, c, "webpage") return UnsupportedFileManager(m, u, file_base, "webpage")
} else if (media is TLMessageMediaContact) { } else if (media is TLMessageMediaContact) {
return UnsupportedFileManager(m, u, c, "contact") return UnsupportedFileManager(m, u, file_base, "contact")
} else if (media is TLMessageMediaVenue) { } else if (media is TLMessageMediaVenue) {
return UnsupportedFileManager(m, u, c, "venue") return UnsupportedFileManager(m, u, file_base, "venue")
} else { } else {
AbstractMediaFileManager.throwUnexpectedObjectError(media) AbstractMediaFileManager.throwUnexpectedObjectError(media)
} }

View File

@ -17,33 +17,16 @@
package de.fabianonline.telegram_backup.mediafilemanager package de.fabianonline.telegram_backup.mediafilemanager
import de.fabianonline.telegram_backup.UserManager 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.DownloadManager
import de.fabianonline.telegram_backup.IniSettings
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.*
import com.github.badoualy.telegram.tl.api.upload.TLFile import de.fabianonline.telegram_backup.Config
import com.github.badoualy.telegram.tl.exception.RpcErrorException import de.fabianonline.telegram_backup.Settings
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
import java.io.IOException import java.io.IOException
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.util.ArrayList
import java.util.LinkedList
import java.net.URL
import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils class GeoFileManager(msg: TLMessage, user: UserManager, file_base: String, val settings: Settings?) : AbstractMediaFileManager(msg, user, file_base) {
class GeoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
protected lateinit var geo: TLGeoPoint protected lateinit var geo: TLGeoPoint
// We don't know the size, so we just guess. // We don't know the size, so we just guess.
@ -77,7 +60,7 @@ class GeoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient)
"center=${geo.getLat()},${geo.getLong()}&" + "center=${geo.getLat()},${geo.getLong()}&" +
"markers=color:red|${geo.getLat()},${geo.getLong()}&" + "markers=color:red|${geo.getLat()},${geo.getLong()}&" +
"zoom=14&size=300x150&scale=2&format=png&" + "zoom=14&size=300x150&scale=2&format=png&" +
"key=" + IniSettings.gmaps_key "key=" + (settings?.gmaps_key ?: Config.SECRET_GMAPS)
return DownloadManager.downloadExternalFile(targetPathAndFilename, url) return DownloadManager.downloadExternalFile(targetPathAndFilename, url)
} }
} }

View File

@ -17,32 +17,15 @@
package de.fabianonline.telegram_backup.mediafilemanager package de.fabianonline.telegram_backup.mediafilemanager
import de.fabianonline.telegram_backup.UserManager 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.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.*
import com.github.badoualy.telegram.tl.api.upload.TLFile
import com.github.badoualy.telegram.tl.exception.RpcErrorException import com.github.badoualy.telegram.tl.exception.RpcErrorException
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
import java.io.IOException 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 java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils class PhotoFileManager(msg: TLMessage, user: UserManager, file_base: String) : AbstractMediaFileManager(msg, user, file_base) {
class PhotoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
private lateinit var photo: TLPhoto private lateinit var photo: TLPhoto
override var size = 0 override var size = 0
private lateinit var photo_size: TLPhotoSize private lateinit var photo_size: TLPhotoSize

View File

@ -18,7 +18,6 @@ package de.fabianonline.telegram_backup.mediafilemanager
import de.fabianonline.telegram_backup.UserManager import de.fabianonline.telegram_backup.UserManager
import de.fabianonline.telegram_backup.Database import de.fabianonline.telegram_backup.Database
import de.fabianonline.telegram_backup.StickerConverter
import de.fabianonline.telegram_backup.DownloadProgressInterface import de.fabianonline.telegram_backup.DownloadProgressInterface
import de.fabianonline.telegram_backup.DownloadManager import de.fabianonline.telegram_backup.DownloadManager
import de.fabianonline.telegram_backup.Config import de.fabianonline.telegram_backup.Config
@ -50,7 +49,7 @@ import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : DocumentFileManager(msg, user, client) { class StickerFileManager(msg: TLMessage, user: UserManager, file_base: String) : DocumentFileManager(msg, user, file_base) {
override val isSticker = true override val isSticker = true
@ -80,7 +79,7 @@ class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClie
override val targetPath: String override val targetPath: String
get() { get() {
val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar val path = file_base + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar
File(path).mkdirs() File(path).mkdirs()
return path return path
} }
@ -94,19 +93,6 @@ class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClie
override val description: String override val description: String
get() = "Sticker" get() = "Sticker"
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
override fun download(): Boolean {
val old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + targetFilename
logger.trace("Old filename exists: {}", File(old_file).exists())
if (File(old_file).exists()) {
Files.copy(Paths.get(old_file), Paths.get(targetPathAndFilename), StandardCopyOption.REPLACE_EXISTING)
return true
}
return super.download()
}
companion object { companion object {
private val logger = LoggerFactory.getLogger(StickerFileManager::class.java) private val logger = LoggerFactory.getLogger(StickerFileManager::class.java)
} }

View File

@ -17,33 +17,10 @@
package de.fabianonline.telegram_backup.mediafilemanager package de.fabianonline.telegram_backup.mediafilemanager
import de.fabianonline.telegram_backup.UserManager 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.*
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 class UnsupportedFileManager(msg: TLMessage, user: UserManager, type: String, file_base: String) : AbstractMediaFileManager(msg, user, file_base) {
import java.io.File
import java.io.FileOutputStream
import java.util.ArrayList
import java.util.LinkedList
import java.net.URL
import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils
class UnsupportedFileManager(msg: TLMessage, user: UserManager, client: TelegramClient, type: String) : AbstractMediaFileManager(msg, user, client) {
override var name = type override var name = type
override val targetFilename = "" override val targetFilename = ""
override val targetPath = "" override val targetPath = ""