WIP: Lots and lots of rewriting.

This commit is contained in:
Fabian Schlenz 2018-03-16 06:59:18 +01:00
parent 2d409352bc
commit eea08a5559
19 changed files with 713 additions and 997 deletions

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 try {
if (this.file_auth_key != null) { return AuthKey(FileUtils.readFileToByteArray(file_auth_key))
try { } catch (e: FileNotFoundException) {
return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key)) return null
} catch (e: IOException) {
if (e !is FileNotFoundException) e.printStackTrace()
}
} }
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 try {
if (this.file_dc != null) { val infos = FileUtils.readFileToString(this.file_dc).split(":")
try { return DataCenter(infos[0], Integer.parseInt(infos[1]))
val infos = FileUtils.readFileToString(this.file_dc).split(":") } catch (e: FileNotFoundException) {
return DataCenter(infos[0], Integer.parseInt(infos[1])) return null
} catch (e: IOException) {
if (e !is FileNotFoundException) e.printStackTrace()
}
} }
return null
} }
override fun deleteAuthKey() { override fun deleteAuthKey() {
if (this.do_save) { try {
try { FileUtils.forceDelete(file_auth_key)
FileUtils.forceDelete(this.file_auth_key) } catch (e: IOException) {
} catch (e: IOException) { logger.warn("Exception in deleteAuthKey(): {}", e)
e.printStackTrace()
}
} }
} }
override fun deleteDc() { override fun deleteDc() {
if (this.do_save) { try {
try { FileUtils.forceDelete(file_dc)
FileUtils.forceDelete(this.file_dc) } catch (e: IOException) {
} catch (e: IOException) { logger.warn("Exception in deleteDc(): {}", e)
e.printStackTrace()
}
} }
} }

View File

@ -29,149 +29,136 @@ 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 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 { init {
val storage: ApiStorage
val app: TelegramApp
val target_dir: String
val file_base: String
val phone_number: String
val handler: TelegramUpdateHandler
val client: TelegramClient
val user_manager: UserManager
val inisettings: IniSettings
val database: Database
logger.info("CommandLineController started. App version {}", Config.APP_APPVER) logger.info("CommandLineController started. App version {}", Config.APP_APPVER)
printHeader() printHeader()
if (CommandLineOptions.cmd_version) { if (options.cmd_version) {
System.exit(0) System.exit(0)
} else if (CommandLineOptions.cmd_help) { } else if (options.cmd_help) {
show_help() show_help()
System.exit(0) System.exit(0)
} else if (CommandLineOptions.cmd_license) { } else if (options.cmd_license) {
show_license() show_license()
System.exit(0) System.exit(0)
} }
// Setup 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)
// Setup file_base // Setup file_base
logger.debug("Target dir from Config: {}", Config.TARGET_DIR.anonymize()) logger.debug("Target dir from Config: {}", Config.TARGET_DIR.anonymize())
target_dir = CommandLineOptions.val_target ?: Config.TARGET_DIR target_dir = options.val_target ?: Config.TARGET_DIR
logger.debug("Target dir after options: {}", target_dir) logger.debug("Target dir after options: {}", target_dir)
println("Base directory for files: ${target_dir.anonymize()}") println("Base directory for files: ${target_dir.anonymize()}")
if (CommandLineOptions.cmd_list_accounts) { if (options.cmd_list_accounts) {
Utils.print_accounts(target_dir) Utils.print_accounts(target_dir)
System.exit(0) System.exit(0)
} }
if (CommandLineOptions.cmd_login) { if (options.cmd_login) {
cmd_login(target_dir, CommandLineOptions.val_account) cmd_login(app, target_dir, options.val_account)
} }
logger.trace("Checking accounts") logger.trace("Checking accounts")
try { phone_number = try { selectAccount(target_dir, options.val_account)
phone_number = selectAccount(target_dir, CommandLineOptions.val_account)
} catch(e: AccountNotFoundException) { } catch(e: AccountNotFoundException) {
show_error("The specified account could not be found.") show_error("The specified account could not be found.")
} catch(e: NoAccountsException) { } catch(e: NoAccountsException) {
println("No accounts found. Starting login process...") println("No accounts found. Starting login process...")
cmd_login(target_dir, CommandLineOptions.val_account) cmd_login(app, target_dir, options.val_account)
} }
// TODO: Create a new TelegramApp if the user set his/her own TelegramApp credentials
file_base = target_dir + File.separatorChar + account_to_use // 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)
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") logger.info("Initializing ApiStorage")
storage = ApiStorage(account) storage = ApiStorage(file_base)
logger.info("Initializing TelegramUpdateHandler") logger.info("Initializing TelegramUpdateHandler")
val handler = TelegramUpdateHandler() 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, handler)
// 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. // Load the ini file.
IniSettings.load() inisettings = IniSettings(file_base)
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() if (options.cmd_stats) {
cmd_stats(file_base, database)
System.exit(0) System.exit(0)
} }
if (CommandLineOptions.val_test != null) {
if (CommandLineOptions.val_test == 1) { if (options.val_test != null) {
if (options.val_test == 1) {
TestFeatures.test1() TestFeatures.test1()
} else if (CommandLineOptions.val_test == 2) { } else if (options.val_test == 2) {
TestFeatures.test2() TestFeatures.test2()
} else { } else {
System.out.println("Unknown test " + CommandLineOptions.val_test) System.out.println("Unknown test " + options.val_test)
} }
System.exit(1) System.exit(1)
} }
logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export)
if (CommandLineOptions.val_export != null) { val export = options.val_export
if (CommandLineOptions.val_export!!.toLowerCase().equals("html")) { logger.debug("options.val_export: {}", export)
(HTMLExporter()).export() if (export != null) {
if (export.toLowerCase().equals("html")) {
HTMLExporter().export()
System.exit(0) System.exit(0)
} else { } else {
show_error("Unknown export format.") show_error("Unknown export format '${export}'.")
} }
} }
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) { println("You are logged in as ${user_manager.toString().anonymize()}")
logger.info("Initializing Download Manager")
val d = DownloadManager(client, CommandLineDownloadProgress(), database, user_manager, inisettings)
if (options.cmd_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 = inisettings.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) {
@ -179,7 +166,7 @@ class CommandLineController {
} }
println() println()
println("Supergroups:") println("Supergroups:")
download = IniSettings.download_supergroups download = inisettings.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) {
@ -188,10 +175,10 @@ 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.val_limit_messages)
d.downloadMessages(CommandLineOptions.val_limit_messages) d.downloadMessages(options.val_limit_messages)
logger.debug("IniSettings.download_media: {}", IniSettings.download_media) logger.debug("IniSettings#download_media: {}", inisettings.download_media)
if (IniSettings.download_media) { if (inisettings.download_media) {
logger.debug("Calling DownloadManager.downloadMedia") logger.debug("Calling DownloadManager.downloadMedia")
d.downloadMedia() d.downloadMedia()
} else { } else {
@ -202,9 +189,9 @@ class CommandLineController {
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. // If we encountered an exception, we definitely don't want to start the daemon mode now.
CommandLineOptions.cmd_daemon = false options.cmd_daemon = false
} finally { } finally {
if (CommandLineOptions.cmd_daemon) { if (options.cmd_daemon) {
handler.activate() handler.activate()
println("DAEMON mode requested - keeping running.") println("DAEMON mode requested - keeping running.")
} else { } else {
@ -225,79 +212,63 @@ class CommandLineController {
} }
private fun selectAccount(file_base: String, requested_account: String?): String { private fun selectAccount(file_base: String, requested_account: String?): String {
val found_account: String? var found_account: String? = null
val accounts = Utils.getAccounts(file_base) val accounts = Utils.getAccounts(file_base)
if (requested_account != null) { if (requested_account != null) {
logger.debug("Account requested: {}", requested_account.anonymize()) logger.debug("Account requested: {}", requested_account.anonymize())
logger.trace("Checking accounts for match.") logger.trace("Checking accounts for match.")
found_account = accounts.find{it == requested_account} found_account = accounts.find{it == requested_account}
if (found_account == null) {
throw AccountNotFoundException()
}
} else if (accounts.size == 0) { } else if (accounts.size == 0) {
throw NoAccountsException() throw NoAccountsException()
} else if (accounts.size == 1) { } else if (accounts.size == 1) {
found_account = accounts.firstElement() found_account = accounts.firstElement()
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) System.exit(1)
} }
if (found_account == null) {
throw AccountNotFoundException()
}
logger.debug("accounts.size: {}", accounts.size) logger.debug("accounts.size: {}", accounts.size)
logger.debug("account: {}", found_account.anonymize()) logger.debug("account: {}", found_account.anonymize())
return found_account!! return found_account
} }
private fun cmd_stats() { 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)
} }
} }
private fun cmd_login(target_dir: String, phoneToUse: String?): Nothing { private fun cmd_login(app: TelegramApp, target_dir: String, phoneToUse: String?): Nothing {
val phone: String LoginManager(app, target_dir, phoneToUse).run()
if (phoneToUse == null) { System.exit(0)
println("Please enter your phone number in international format.") throw RuntimeException("Code never reaches this. This exists just to keep the Kotlin compiler happy.")
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() {
@ -323,15 +294,18 @@ class CommandLineController {
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 AccountNotFoundException() : Exception("Account not found") {}

View File

@ -15,10 +15,10 @@
* 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 var cmd_console = false
public var cmd_help = false var cmd_help = false
public var cmd_login = false var cmd_login = false
var cmd_debug = false var cmd_debug = false
var cmd_trace = false var cmd_trace = false
var cmd_trace_telegram = false var cmd_trace_telegram = false
@ -34,8 +34,8 @@ internal object CommandLineOptions {
var val_target: String? = null var val_target: String? = null
var val_export: String? = null var val_export: String? = null
var val_test: Int? = null var val_test: Int? = null
@JvmStatic
fun parseOptions(args: Array<String>) { init {
var last_cmd: String? = null var last_cmd: String? = null
loop@ for (arg in args) { loop@ for (arg in args) {
if (last_cmd != null) { if (last_cmd != null) {

View File

@ -29,24 +29,30 @@ 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>) {
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() { fun setupLogging() {
val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger
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 +71,13 @@ object CommandLineRunner {
rootLogger.addAppender(appender) rootLogger.addAppender(appender)
rootLogger.setLevel(Level.OFF) rootLogger.setLevel(Level.OFF)
if (CommandLineOptions.cmd_trace) { if (options.cmd_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.cmd_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.cmd_trace_telegram) {
(LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE) (LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE)
} }
} }

File diff suppressed because it is too large Load Diff

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)
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)

View File

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

View File

@ -61,18 +61,9 @@ 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 inisettings: IniSettings) {
internal var user: UserManager? = null
internal var db: Database? = null
internal var prog: DownloadProgressInterface? = null
internal var has_seen_flood_wait_message = false 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 var completed: Boolean
@ -173,18 +164,14 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
} }
*/ */
if (IniSettings.download_channels || IniSettings.download_supergroups) { if (inisettings.download_channels) {
// TODO Add chat title (and other stuff?) to the database println("Checking channels...")
for (channel in chats.channels) { if (channel.download) downloadMessagesFromChannel(channel) }
}
if (IniSettings.download_channels) { if (inisettings.download_supergroups) {
println("Checking channels...") println("Checking supergroups...")
for (channel in chats.channels) { if (channel.download) downloadMessagesFromChannel(channel) } for (supergroup in chats.supergroups) { if (supergroup.download) downloadMessagesFromChannel(supergroup) }
}
if (IniSettings.download_supergroups) {
println("Checking supergroups...")
for (supergroup in chats.supergroups) { if (supergroup.download) downloadMessagesFromChannel(supergroup) }
}
} }
} }
@ -315,7 +302,7 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
prog!!.onMediaDownloadStart(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)
logger.trace("message {}, {}, {}, {}, {}", logger.trace("message {}, {}, {}, {}, {}",
msg.getId(), msg.getId(),
msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", ""), msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", ""),
@ -366,10 +353,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 (inisettings.whitelist_channels != null) {
download = IniSettings.whitelist_channels!!.contains(tl_channel.getId().toString()) download = inisettings.whitelist_channels.contains(tl_channel.getId().toString())
} else if (IniSettings.blacklist_channels != null) { } else if (inisettings.blacklist_channels != null) {
download = !IniSettings.blacklist_channels!!.contains(tl_channel.getId().toString()) download = !inisettings.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()) {

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

@ -4,20 +4,15 @@ import java.io.File
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.Logger import org.slf4j.Logger
object IniSettings { class IniSettings(val file_base: String) {
val logger = LoggerFactory.getLogger(IniSettings::class.java) val logger = LoggerFactory.getLogger(IniSettings::class.java)
var settings = mutableMapOf<String, MutableList<String>>() var settings = mutableMapOf<String, MutableList<String>>()
init { init {
if (UserManager.getInstance().user != null) { loadIni(file_base + "config.ini")
loadIni(UserManager.getInstance().fileBase + "config.ini") copySampleIni(file_base + "config.sample.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) { private fun loadIni(filename: String) {
val file = File(filename) val file = File(filename)
logger.trace("Checking ini file {}", filename.anonymize()) logger.trace("Checking ini file {}", filename.anonymize())
@ -66,20 +61,12 @@ object IniSettings {
} }
fun getArray(key: String): List<String> = settings.get(key) ?: listOf<String>() fun getArray(key: String): List<String> = settings.get(key) ?: listOf<String>()
val gmaps_key: String val gmaps_key = getString("gmaps_key", default=Config.SECRET_GMAPS)!!
get() = getString("gmaps_key", default=Config.SECRET_GMAPS)!! val pagination = getBoolean("pagination", default=true)
val pagination: Boolean val pagination_size = getInt("pagination_size", default=Config.DEFAULT_PAGINATION)!!
get() = getBoolean("pagination", default=true) val download_media = getBoolean("download_media", default=true)
val pagination_size: Int val download_channels = getBoolean("download_channels", default=false)
get() = getInt("pagination_size", default=Config.DEFAULT_PAGINATION)!! val download_supergroups = getBoolean("download_supergroups", default=false)
val download_media: Boolean val whitelist_channels = getStringList("whitelist_channels")
get() = getBoolean("download_media", default=true) val blacklist_channels = getStringList("blacklist_channels")
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")
} }

View File

@ -0,0 +1,87 @@
package de.fabianonline.telegram_backup
import com.github.badoualy.telegram.tl.api.auth.TLSentCode
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 +" + user.user!!.getPhone().anonymize() + " 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

@ -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
get() = user != null
val userString: String
get() {
if (this.user == null) return "Not logged in"
val sb = StringBuilder()
if (this.user!!.getFirstName() != null) {
sb.append(this.user!!.getFirstName())
}
if (this.user!!.getLastName() != null) {
sb.append(" ")
sb.append(this.user!!.getLastName())
}
if (this.user!!.getUsername() != null) {
sb.append(" (@")
sb.append(this.user!!.getUsername())
sb.append(")")
}
return sb.toString()
}
val fileBase: String
get() = Config.FILE_BASE + File.separatorChar + "+" + this.user!!.getPhone() + File.separatorChar
init { init {
this.client = c
logger.debug("Calling getFullUser") logger.debug("Calling getFullUser")
try { val full_user = client.usersGetFullUser(TLInputUserSelf())
val full_user = this.client.usersGetFullUser(TLInputUserSelf()) tl_user = full_user.getUser().getAsUser()
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 toString(): String {
fun sendCodeToPhoneNumber(number: String) { val sb = StringBuilder()
this.phone = number sb.append(tl_user.getFirstName() ?: "")
this.sent_code = this.client.authSendCode(false, number, true) if (tl_user.getLastName() != null) {
} sb.append(" ")
sb.append(tl_user.getLastName())
@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
} }
if (tl_user.getUsername() != null) {
} sb.append(" (@")
sb.append(tl_user.getUsername())
@Throws(RpcErrorException::class, IOException::class) sb.append(")")
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!!
} }
return sb.toString()
} }
} }

View File

@ -43,7 +43,7 @@ import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
abstract class AbstractMediaFileManager(protected var message: TLMessage, protected var user: UserManager, protected var client: TelegramClient) { abstract class AbstractMediaFileManager(protected var message: TLMessage, protected var user: UserManager) {
open var isEmpty = false open var isEmpty = false
abstract val size: Int abstract val size: Int
abstract val extension: String abstract val extension: String

View File

@ -42,7 +42,7 @@ import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
open class DocumentFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) { open class DocumentFileManager(msg: TLMessage, user: UserManager) : AbstractMediaFileManager(msg, user) {
protected var doc: TLDocument? = null protected var doc: TLDocument? = null
override lateinit var extension: String override lateinit var extension: String

View 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): 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)
} else if (media is TLMessageMediaDocument) { } else if (media is TLMessageMediaDocument) {
val d = DocumentFileManager(m, u, c) val d = DocumentFileManager(m, u)
return if (d.isSticker) { return if (d.isSticker) {
StickerFileManager(m, u, c) StickerFileManager(m, u)
} else d } else d
} else if (media is TLMessageMediaGeo) { } else if (media is TLMessageMediaGeo) {
return GeoFileManager(m, u, c) return GeoFileManager(m, u)
} else if (media is TLMessageMediaEmpty) { } else if (media is TLMessageMediaEmpty) {
return UnsupportedFileManager(m, u, c, "empty") return UnsupportedFileManager(m, u, "empty")
} else if (media is TLMessageMediaUnsupported) { } else if (media is TLMessageMediaUnsupported) {
return UnsupportedFileManager(m, u, c, "unsupported") return UnsupportedFileManager(m, u, "unsupported")
} else if (media is TLMessageMediaWebPage) { } else if (media is TLMessageMediaWebPage) {
return UnsupportedFileManager(m, u, c, "webpage") return UnsupportedFileManager(m, u, "webpage")
} else if (media is TLMessageMediaContact) { } else if (media is TLMessageMediaContact) {
return UnsupportedFileManager(m, u, c, "contact") return UnsupportedFileManager(m, u, "contact")
} else if (media is TLMessageMediaVenue) { } else if (media is TLMessageMediaVenue) {
return UnsupportedFileManager(m, u, c, "venue") return UnsupportedFileManager(m, u, "venue")
} else { } else {
AbstractMediaFileManager.throwUnexpectedObjectError(media) AbstractMediaFileManager.throwUnexpectedObjectError(media)
} }

View File

@ -43,7 +43,7 @@ import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
class GeoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) { class GeoFileManager(msg: TLMessage, user: UserManager) : AbstractMediaFileManager(msg, user) {
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.

View File

@ -42,7 +42,7 @@ import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
class PhotoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) { class PhotoFileManager(msg: TLMessage, user: UserManager) : AbstractMediaFileManager(msg, user) {
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

@ -50,7 +50,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) : DocumentFileManager(msg, user) {
override val isSticker = true override val isSticker = true

View File

@ -43,7 +43,7 @@ import java.util.concurrent.TimeoutException
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
class UnsupportedFileManager(msg: TLMessage, user: UserManager, client: TelegramClient, type: String) : AbstractMediaFileManager(msg, user, client) { class UnsupportedFileManager(msg: TLMessage, user: UserManager, type: String) : AbstractMediaFileManager(msg, user) {
override var name = type override var name = type
override val targetFilename = "" override val targetFilename = ""
override val targetPath = "" override val targetPath = ""