telegram_backup/src/main/kotlin/de/fabianonline/telegram_backup/CommandLineController.kt

337 lines
12 KiB
Kotlin

/* Telegram_Backup
* Copyright (C) 2016 Fabian Schlenz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup
import de.fabianonline.telegram_backup.TelegramUpdateHandler
import de.fabianonline.telegram_backup.exporter.HTMLExporter
import com.github.badoualy.telegram.api.Kotlogram
import com.github.badoualy.telegram.api.TelegramApp
import com.github.badoualy.telegram.api.TelegramClient
import com.github.badoualy.telegram.tl.exception.RpcErrorException
import java.io.File
import java.io.IOException
import java.util.Scanner
import java.util.Vector
import java.util.HashMap
import org.slf4j.LoggerFactory
import org.slf4j.Logger
class CommandLineController {
private val storage: ApiStorage
val app: TelegramApp
val target_dir: String
val file_base: String
val phone_number: String
val account_file_base: String
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 {
logger.info("CommandLineController started. App version {}", Config.APP_APPVER)
printHeader()
if (CommandLineOptions.cmd_version) {
System.exit(0)
} else if (CommandLineOptions.cmd_help) {
show_help()
System.exit(0)
} else if (CommandLineOptions.cmd_license) {
show_license()
System.exit(0)
}
// Setup file_base
logger.debug("Target dir from Config: {}", Config.TARGET_DIR.anonymize())
target_dir = CommandLineOptions.val_target ?: Config.TARGET_DIR
logger.debug("Target dir after options: {}", target_dir)
println("Base directory for files: ${target_dir.anonymize()}")
if (CommandLineOptions.cmd_list_accounts) {
Utils.print_accounts(target_dir)
System.exit(0)
}
logger.trace("Checking accounts")
try {
phone_number = selectAccount(target_dir, CommandLineOptions.val_account)
} catch(e: AccountNotFoundException) {
show_error("The specified account could not be found.")
} catch(e: NoAccountsException) {
println("No accounts found. Starting login process...")
CommandLineOptions.cmd_login = true
}
file_base = target_dir + File.separatorChar + account_to_use
account = Account(file_base, account_to_use)
logger.debug("Initializing TelegramApp")
app = TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG)
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login)
logger.info("Initializing ApiStorage")
storage = ApiStorage(account)
logger.info("Initializing TelegramUpdateHandler")
val handler = TelegramUpdateHandler()
logger.info("Creating Client")
val client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler)
try {
logger.info("Initializing UserManager")
UserManager.init(client)
val user = UserManager.getInstance()
if (!CommandLineOptions.cmd_login && !user.loggedIn) {
println("Your authorization data is invalid or missing. You will have to login with Telegram again.")
CommandLineOptions.cmd_login = true
}
if (account != null && user.loggedIn) {
if (account != "+" + user.user!!.getPhone()) {
logger.error("Account: {}, user.user!!.getPhone(): +{}", account.anonymize(), user.user!!.getPhone().anonymize())
throw RuntimeException("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.
Database.init(client)
if (CommandLineOptions.cmd_stats) {
cmd_stats()
System.exit(0)
}
if (CommandLineOptions.val_test != null) {
if (CommandLineOptions.val_test == 1) {
TestFeatures.test1()
} else if (CommandLineOptions.val_test == 2) {
TestFeatures.test2()
} 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) {
val chats = d.getChats()
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 "")}
var download: Boolean
println("Channels:")
download = IniSettings.download_channels
if (!download) println("Download of channels is disabled - see download_channels in config.ini")
print_header(download)
for (c in chats.channels) {
println(format(c, download))
}
println()
println("Supergroups:")
download = IniSettings.download_supergroups
if (!download) println("Download of supergroups is disabled - see download_supergroups in config.ini")
print_header(download)
for (c in chats.supergroups) {
println(format(c, download))
}
System.exit(0)
}
logger.debug("Calling DownloadManager.downloadMessages with limit {}", CommandLineOptions.val_limit_messages)
d.downloadMessages(CommandLineOptions.val_limit_messages)
logger.debug("IniSettings.download_media: {}", IniSettings.download_media)
if (IniSettings.download_media) {
logger.debug("Calling DownloadManager.downloadMedia")
d.downloadMedia()
} else {
println("Skipping media download because download_media is set to false.")
}
} catch (e: Throwable) {
println("An error occured!")
e.printStackTrace()
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 {
if (CommandLineOptions.cmd_daemon) {
handler.activate()
println("DAEMON mode requested - keeping running.")
} else {
client.close()
println()
println("----- EXIT -----")
System.exit(0)
}
}
}
private fun printHeader() {
System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz")
println()
println("Telegram_Backup comes with ABSOLUTELY NO WARRANTY. This is free software, and you are")
println("welcome to redistribute it under certain conditions; run it with '--license' for details.")
println()
}
private fun selectAccount(file_base: String, requested_account: String?): String {
val found_account: String?
val accounts = Utils.getAccounts(file_base)
if (requested_account != null) {
logger.debug("Account requested: {}", requested_account.anonymize())
logger.trace("Checking accounts for match.")
found_account = accounts.find{it == requested_account}
if (found_account == null) {
throw AccountNotFoundException()
}
} else if (accounts.size == 0) {
throw NoAccountsException()
} else if (accounts.size == 1) {
found_account = accounts.firstElement()
println("Using only available account: " + account.anonymize())
} else {
show_error(("You didn't specify which account to use.\n" +
"Use '--account <x>' to use account <x>.\n" +
"Use '--list-accounts' to see all available accounts."))
System.exit(1)
}
logger.debug("accounts.size: {}", accounts.size)
logger.debug("account: {}", found_account.anonymize())
return found_account!!
}
private fun cmd_stats() {
println()
println("Stats:")
val format = "%40s: %d%n"
System.out.format(format, "Number of accounts", Utils.getAccounts().size)
System.out.format(format, "Number of messages", Database.getInstance().getMessageCount())
System.out.format(format, "Number of chats", Database.getInstance().getChatCount())
System.out.format(format, "Number of users", Database.getInstance().getUserCount())
System.out.format(format, "Top message ID", Database.getInstance().getTopMessageID())
println()
println("Media Types:")
for ((key, value) in Database.getInstance().getMessageMediaTypesWithCount()) {
System.out.format(format, key, value)
}
println()
println("Api layers of messages:")
for ((key, value) in Database.getInstance().getMessageApiLayerWithCount()) {
System.out.format(format, key, value)
}
println()
println("Message source types:")
for ((key, value) in Database.getInstance().getMessageSourceTypeWithCount()) {
System.out.format(format, key, value)
}
}
@Throws(RpcErrorException::class, IOException::class)
private fun cmd_login(phoneToUse: String?) {
val user = UserManager.getInstance()
val phone: String
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() {
println("Valid options are:")
println(" -h, --help Shows this help.")
println(" -a, --account <x> Use account <x>.")
println(" -l, --login Login to an existing telegram account.")
println(" --debug Shows some debug information.")
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(" -A, --list-accounts List all existing accounts ")
println(" --limit-messages <x> Downloads at most the most recent <x> messages.")
println(" -t, --target <x> Target directory for the files.")
println(" -e, --export <format> Export the database. Valid formats are:")
println(" html - Creates HTML files.")
println(" --license Displays the license of this program.")
println(" -d, --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(" --stats Print some usage statistics.")
println(" --list-channels Lists all channels together with their ID")
}
companion object {
private val logger = LoggerFactory.getLogger(CommandLineController::class.java)
public fun show_error(error: String) {
logger.error(error)
println("ERROR: " + error)
System.exit(1)
}
fun show_license() {
println("TODO: Print the GPL.")
}
}
class AccountNotFoundException() : Exception("Account not found") {}
class NoAccountsException() : Exception("No accounts found") {}
}