Kotlin: Reformatted code.

This commit is contained in:
Fabian Schlenz 2017-12-12 22:04:20 +01:00
parent 7067f98943
commit bb180f95be
25 changed files with 2750 additions and 2795 deletions

View File

@ -28,120 +28,120 @@ import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
internal class ApiStorage(prefix: String?) : TelegramApiStorage { internal class ApiStorage(prefix: String?) : TelegramApiStorage {
private var prefix: String? = null private var prefix: String? = null
private var do_save = false private var do_save = false
private var auth_key: AuthKey? = null private var auth_key: AuthKey? = null
private var dc: DataCenter? = null private var dc: DataCenter? = null
private var file_auth_key: File? = null private var file_auth_key: File? = null
private var file_dc: File? = null private var file_dc: File? = null
init { init {
this.setPrefix(prefix) this.setPrefix(prefix)
} }
fun setPrefix(prefix: String?) { fun setPrefix(prefix: String?) {
this.prefix = prefix this.prefix = prefix
this.do_save = this.prefix != null this.do_save = this.prefix != null
if (this.do_save) { if (this.do_save) {
val base = Config.FILE_BASE + val base = Config.FILE_BASE +
File.separatorChar + File.separatorChar +
this.prefix + this.prefix +
File.separatorChar File.separatorChar
this.file_auth_key = File(base + Config.FILE_NAME_AUTH_KEY) this.file_auth_key = File(base + Config.FILE_NAME_AUTH_KEY)
this.file_dc = File(base + Config.FILE_NAME_DC) this.file_dc = File(base + Config.FILE_NAME_DC)
this._saveAuthKey() this._saveAuthKey()
this._saveDc() this._saveDc()
} else { } else {
this.file_auth_key = null this.file_auth_key = null
this.file_dc = null this.file_dc = null
} }
} }
override fun saveAuthKey(authKey: AuthKey) { override fun saveAuthKey(authKey: AuthKey) {
this.auth_key = authKey this.auth_key = authKey
this._saveAuthKey() this._saveAuthKey()
} }
private fun _saveAuthKey() { private fun _saveAuthKey() {
if (this.do_save && this.auth_key != null) { if (this.do_save && this.auth_key != null) {
try { try {
FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key!!.key) FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key!!.key)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }
override fun loadAuthKey(): AuthKey? { override fun loadAuthKey(): AuthKey? {
if (this.auth_key != null) return this.auth_key if (this.auth_key != null) return this.auth_key
if (this.file_auth_key != null) { if (this.file_auth_key != null) {
try { try {
return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key)) return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key))
} catch (e: IOException) { } catch (e: IOException) {
if (e !is FileNotFoundException) e.printStackTrace() if (e !is FileNotFoundException) e.printStackTrace()
} }
} }
return null return null
} }
override fun saveDc(dataCenter: DataCenter) { override fun saveDc(dataCenter: DataCenter) {
this.dc = dataCenter this.dc = dataCenter
this._saveDc() this._saveDc()
} }
private fun _saveDc() { private fun _saveDc() {
if (this.do_save && this.dc != null) { if (this.do_save && this.dc != null) {
try { try {
FileUtils.write(this.file_dc, this.dc!!.toString()) FileUtils.write(this.file_dc, this.dc!!.toString())
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }
override fun loadDc(): DataCenter? { override fun loadDc(): DataCenter? {
if (this.dc != null) return this.dc if (this.dc != null) return this.dc
if (this.file_dc != null) { 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: IOException) {
if (e !is FileNotFoundException) e.printStackTrace() if (e !is FileNotFoundException) e.printStackTrace()
} }
} }
return null return null
} }
override fun deleteAuthKey() { override fun deleteAuthKey() {
if (this.do_save) { if (this.do_save) {
try { try {
FileUtils.forceDelete(this.file_auth_key) FileUtils.forceDelete(this.file_auth_key)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }
override fun deleteDc() { override fun deleteDc() {
if (this.do_save) { if (this.do_save) {
try { try {
FileUtils.forceDelete(this.file_dc) FileUtils.forceDelete(this.file_dc)
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }
override fun saveSession(session: MTSession?) {} override fun saveSession(session: MTSession?) {}
override fun loadSession(): MTSession? { override fun loadSession(): MTSession? {
return null return null
} }
} }

View File

@ -14,6 +14,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup package de.fabianonline.telegram_backup
import de.fabianonline.telegram_backup.TelegramUpdateHandler import de.fabianonline.telegram_backup.TelegramUpdateHandler
import de.fabianonline.telegram_backup.exporter.HTMLExporter import de.fabianonline.telegram_backup.exporter.HTMLExporter
import com.github.badoualy.telegram.api.Kotlogram import com.github.badoualy.telegram.api.Kotlogram
@ -27,327 +28,282 @@ import java.util.Vector
import java.util.HashMap import java.util.HashMap
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.Logger import org.slf4j.Logger
class CommandLineController { class CommandLineController {
private val storage:ApiStorage private val storage: ApiStorage
var app:TelegramApp var app: TelegramApp
private fun getLine():String { private fun getLine(): String {
if (System.console() != null) { if (System.console() != null) {
return System.console().readLine("> ") return System.console().readLine("> ")
} else { } else {
print("> ") print("> ")
return Scanner(System.`in`).nextLine() return Scanner(System.`in`).nextLine()
} }
} }
private fun getPassword():String { private fun getPassword(): String {
if (System.console() != null) { if (System.console() != null) {
return String(System.console().readPassword("> ")) return String(System.console().readPassword("> "))
} else { } else {
return getLine() return getLine()
} }
} }
init{ init {
logger.info("CommandLineController started. App version {}", Config.APP_APPVER) logger.info("CommandLineController started. App version {}", Config.APP_APPVER)
this.printHeader() this.printHeader()
if (CommandLineOptions.cmd_version) if (CommandLineOptions.cmd_version) {
{ System.exit(0)
System.exit(0) } else if (CommandLineOptions.cmd_help) {
} this.show_help()
else if (CommandLineOptions.cmd_help) System.exit(0)
{ } else if (CommandLineOptions.cmd_license) {
this.show_help() CommandLineController.show_license()
System.exit(0) System.exit(0)
} }
else if (CommandLineOptions.cmd_license) this.setupFileBase()
{ if (CommandLineOptions.cmd_list_accounts) {
CommandLineController.show_license() this.list_accounts()
System.exit(0) System.exit(0)
} }
this.setupFileBase() logger.debug("Initializing TelegramApp")
if (CommandLineOptions.cmd_list_accounts) app = TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG)
{ logger.trace("Checking accounts")
this.list_accounts() val account = this.selectAccount()
System.exit(0) logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login)
} logger.info("Initializing ApiStorage")
logger.debug("Initializing TelegramApp") storage = ApiStorage(account)
app = TelegramApp(Config.APP_ID, Config.APP_HASH, Config.APP_MODEL, Config.APP_SYSVER, Config.APP_APPVER, Config.APP_LANG) logger.info("Initializing TelegramUpdateHandler")
logger.trace("Checking accounts") val handler = TelegramUpdateHandler()
val account = this.selectAccount() logger.info("Creating Client")
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login) val client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler)
logger.info("Initializing ApiStorage") try {
storage = ApiStorage(account) logger.info("Initializing UserManager")
logger.info("Initializing TelegramUpdateHandler") UserManager.init(client)
val handler = TelegramUpdateHandler() val user = UserManager.getInstance()
logger.info("Creating Client") if (!CommandLineOptions.cmd_login && !user.loggedIn) {
val client = Kotlogram.getDefaultClient(app, storage, Kotlogram.PROD_DC4, handler) println("Your authorization data is invalid or missing. You will have to login with Telegram again.")
try CommandLineOptions.cmd_login = true
{ }
logger.info("Initializing UserManager") if (account != null && user.loggedIn) {
UserManager.init(client) if (account != "+" + user.user!!.getPhone()) {
val user = UserManager.getInstance() logger.error("Account: {}, user.user!!.getPhone(): +{}", Utils.anonymize(account), Utils.anonymize(user.user!!.getPhone()))
if (!CommandLineOptions.cmd_login && !user.loggedIn) throw RuntimeException("Account / User mismatch")
{ }
println("Your authorization data is invalid or missing. You will have to login with Telegram again.") }
CommandLineOptions.cmd_login = true logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login)
} if (CommandLineOptions.cmd_login) {
if (account != null && user.loggedIn) cmd_login(account)
{ System.exit(0)
if (account != "+" + user.user!!.getPhone()) }
{ // If we reach this point, we can assume that there is an account and a database can be loaded / created.
logger.error("Account: {}, user.user!!.getPhone(): +{}", Utils.anonymize(account), Utils.anonymize(user.user!!.getPhone())) Database.init(client)
throw RuntimeException("Account / User mismatch") if (CommandLineOptions.cmd_stats) {
} cmd_stats()
} System.exit(0)
logger.debug("CommandLineOptions.cmd_login: {}", CommandLineOptions.cmd_login) }
if (CommandLineOptions.cmd_login) if (CommandLineOptions.val_test != null) {
{ if (CommandLineOptions.val_test == 1) {
cmd_login(account) TestFeatures.test1()
System.exit(0) } else if (CommandLineOptions.val_test == 2) {
} TestFeatures.test2()
// If we reach this point, we can assume that there is an account and a database can be loaded / created. } else {
Database.init(client) System.out.println("Unknown test " + CommandLineOptions.val_test)
if (CommandLineOptions.cmd_stats) }
{ System.exit(1)
cmd_stats() }
System.exit(0) logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export)
} if (CommandLineOptions.val_export != null) {
if (CommandLineOptions.val_test != null) if (CommandLineOptions.val_export!!.toLowerCase().equals("html")) {
{ (HTMLExporter()).export()
if (CommandLineOptions.val_test == 1) System.exit(0)
{ } else {
TestFeatures.test1() show_error("Unknown export format.")
} }
else if (CommandLineOptions.val_test == 2) }
{ if (user.loggedIn) {
TestFeatures.test2() System.out.println("You are logged in as " + Utils.anonymize(user.userString))
} } else {
else println("You are not logged in.")
{ System.exit(1)
System.out.println("Unknown test " + CommandLineOptions.val_test) }
} logger.info("Initializing Download Manager")
System.exit(1) val d = DownloadManager(client, CommandLineDownloadProgress())
} logger.debug("Calling DownloadManager.downloadMessages with limit {}", CommandLineOptions.val_limit_messages)
logger.debug("CommandLineOptions.val_export: {}", CommandLineOptions.val_export) d.downloadMessages(CommandLineOptions.val_limit_messages)
if (CommandLineOptions.val_export != null) logger.debug("CommandLineOptions.cmd_no_media: {}", CommandLineOptions.cmd_no_media)
{ if (!CommandLineOptions.cmd_no_media) {
if (CommandLineOptions.val_export!!.toLowerCase().equals("html")) logger.debug("Calling DownloadManager.downloadMedia")
{ d.downloadMedia()
(HTMLExporter()).export() } else {
System.exit(0) println("Skipping media download because --no-media is set.")
} }
else } catch (e: Exception) {
{ e.printStackTrace()
show_error("Unknown export format.") logger.error("Exception caught!", e)
} } finally {
} if (CommandLineOptions.cmd_daemon) {
if (user.loggedIn) handler.activate()
{ println("DAEMON mode requested - keeping running.")
System.out.println("You are logged in as " + Utils.anonymize(user.userString)) } else {
} client.close()
else println()
{ println("----- EXIT -----")
println("You are not logged in.") System.exit(0)
System.exit(1) }
} }
logger.info("Initializing Download Manager") }
val d = DownloadManager(client, CommandLineDownloadProgress())
logger.debug("Calling DownloadManager.downloadMessages with limit {}", CommandLineOptions.val_limit_messages) private fun printHeader() {
d.downloadMessages(CommandLineOptions.val_limit_messages) System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz")
logger.debug("CommandLineOptions.cmd_no_media: {}", CommandLineOptions.cmd_no_media) println()
if (!CommandLineOptions.cmd_no_media) 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.")
logger.debug("Calling DownloadManager.downloadMedia") println()
d.downloadMedia() }
}
else private fun setupFileBase() {
{ logger.debug("Target dir at startup: {}", Utils.anonymize(Config.FILE_BASE))
println("Skipping media download because --no-media is set.") if (CommandLineOptions.val_target != null) {
} Config.FILE_BASE = CommandLineOptions.val_target!!
} }
catch (e:Exception) { logger.debug("Target dir after options: {}", Utils.anonymize(Config.FILE_BASE))
e.printStackTrace() System.out.println("Base directory for files: " + Utils.anonymize(Config.FILE_BASE))
logger.error("Exception caught!", e) }
}
finally private fun selectAccount(): String? {
{ var account = "none"
if (CommandLineOptions.cmd_daemon) val accounts = Utils.getAccounts()
{ if (CommandLineOptions.cmd_login) {
handler.activate() logger.debug("Login requested, doing nothing.")
println("DAEMON mode requested - keeping running.") // do nothing
} } else if (CommandLineOptions.val_account != null) {
else logger.debug("Account requested: {}", Utils.anonymize(CommandLineOptions.val_account!!))
{ logger.trace("Checking accounts for match.")
client.close() var found = false
println() for (acc in accounts) {
println("----- EXIT -----") logger.trace("Checking {}", Utils.anonymize(acc))
System.exit(0) if (acc == CommandLineOptions.val_account) {
} found = true
} logger.trace("Matches.")
} break
private fun printHeader() { }
System.out.println("Telegram_Backup version " + Config.APP_APPVER + ", Copyright (C) 2016, 2017 Fabian Schlenz") }
println() if (!found) {
println("Telegram_Backup comes with ABSOLUTELY NO WARRANTY. This is free software, and you are") show_error("Couldn't find account '" + Utils.anonymize(CommandLineOptions.val_account!!) + "'. Maybe you want to use '--login' first?")
println("welcome to redistribute it under certain conditions; run it with '--license' for details.") }
println() account = CommandLineOptions.val_account!!
} } else if (accounts.size == 0) {
private fun setupFileBase() { println("No accounts found. Starting login process...")
logger.debug("Target dir at startup: {}", Utils.anonymize(Config.FILE_BASE)) CommandLineOptions.cmd_login = true
if (CommandLineOptions.val_target != null) return null
{ } else if (accounts.size == 1) {
Config.FILE_BASE = CommandLineOptions.val_target!! account = accounts.firstElement()
} System.out.println("Using only available account: " + Utils.anonymize(account))
logger.debug("Target dir after options: {}", Utils.anonymize(Config.FILE_BASE)) } else {
System.out.println("Base directory for files: " + Utils.anonymize(Config.FILE_BASE)) show_error(("You didn't specify which account to use.\n" +
} "Use '--account <x>' to use account <x>.\n" +
private fun selectAccount():String? { "Use '--list-accounts' to see all available accounts."))
var account = "none" System.exit(1)
val accounts = Utils.getAccounts() }
if (CommandLineOptions.cmd_login) logger.debug("accounts.size: {}", accounts.size)
{ logger.debug("account: {}", Utils.anonymize(account))
logger.debug("Login requested, doing nothing.") return account
// do nothing }
}
else if (CommandLineOptions.val_account != null) private fun cmd_stats() {
{ println()
logger.debug("Account requested: {}", Utils.anonymize(CommandLineOptions.val_account!!)) println("Stats:")
logger.trace("Checking accounts for match.") val format = "%40s: %d%n"
var found = false System.out.format(format, "Number of accounts", Utils.getAccounts().size)
for (acc in accounts) System.out.format(format, "Number of messages", Database.getInstance().getMessageCount())
{ System.out.format(format, "Number of chats", Database.getInstance().getChatCount())
logger.trace("Checking {}", Utils.anonymize(acc)) System.out.format(format, "Number of users", Database.getInstance().getUserCount())
if (acc == CommandLineOptions.val_account) System.out.format(format, "Top message ID", Database.getInstance().getTopMessageID())
{ println()
found = true println("Media Types:")
logger.trace("Matches.") for ((key, value) in Database.getInstance().getMessageMediaTypesWithCount()) {
break System.out.format(format, key, value)
} }
} println()
if (!found) println("Api layers of messages:")
{ for ((key, value) in Database.getInstance().getMessageApiLayerWithCount()) {
show_error("Couldn't find account '" + Utils.anonymize(CommandLineOptions.val_account!!) + "'. Maybe you want to use '--login' first?") System.out.format(format, key, value)
} }
account = CommandLineOptions.val_account!! }
}
else if (accounts.size == 0) @Throws(RpcErrorException::class, IOException::class)
{ private fun cmd_login(phoneToUse: String?) {
println("No accounts found. Starting login process...") val user = UserManager.getInstance()
CommandLineOptions.cmd_login = true val phone: String
return null if (phoneToUse == null) {
} println("Please enter your phone number in international format.")
else if (accounts.size == 1) println("Example: +4917077651234")
{ phone = getLine()
account = accounts.firstElement() } else {
System.out.println("Using only available account: " + Utils.anonymize(account)) phone = phoneToUse
} }
else user.sendCodeToPhoneNumber(phone)
{ println("Telegram sent you a code. Please enter it here.")
show_error(("You didn't specify which account to use.\n" + val code = getLine()
"Use '--account <x>' to use account <x>.\n" + user.verifyCode(code)
"Use '--list-accounts' to see all available accounts.")) if (user.isPasswordNeeded) {
System.exit(1) 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()
logger.debug("accounts.size: {}", accounts.size) user.verifyPassword(pw)
logger.debug("account: {}", Utils.anonymize(account)) }
return account storage.setPrefix("+" + user.user!!.getPhone())
} System.out.println("Everything seems fine. Please run this tool again with '--account +" + Utils.anonymize(user.user!!.getPhone()) + " to use this account.")
private fun cmd_stats() { }
println()
println("Stats:") private fun show_help() {
val format = "%40s: %d%n" println("Valid options are:")
System.out.format(format, "Number of accounts", Utils.getAccounts().size) println(" -h, --help Shows this help.")
System.out.format(format, "Number of messages", Database.getInstance().getMessageCount()) println(" -a, --account <x> Use account <x>.")
System.out.format(format, "Number of chats", Database.getInstance().getChatCount()) println(" -l, --login Login to an existing telegram account.")
System.out.format(format, "Number of users", Database.getInstance().getUserCount()) println(" --debug Shows some debug information.")
System.out.format(format, "Top message ID", Database.getInstance().getTopMessageID()) println(" --trace Shows lots of debug information. Overrides --debug.")
println() println(" --trace-telegram Shows lots of debug messages from the library used to access Telegram.")
println("Media Types:") println(" -A, --list-accounts List all existing accounts ")
for ((key, value) in Database.getInstance().getMessageMediaTypesWithCount()) println(" --limit-messages <x> Downloads at most the most recent <x> messages.")
{ println(" --no-media Do not download media files.")
System.out.format(format, key, value) println(" -t, --target <x> Target directory for the files.")
} println(" -e, --export <format> Export the database. Valid formats are:")
println() println(" html - Creates HTML files.")
println("Api layers of messages:") println(" --license Displays the license of this program.")
for ((key, value) in Database.getInstance().getMessageApiLayerWithCount()) println(" -d, --daemon Keep running and automatically save new messages.")
{ println(" --anonymize (Try to) Remove all sensitive information from output. Useful for requesting support.")
System.out.format(format, key, value) println(" --stats Print some usage statistics.")
} println(" --with-channels Backup channels as well.")
} println(" --with-supergroups Backup supergroups as well.")
@Throws(RpcErrorException::class, IOException::class) }
private fun cmd_login(phoneToUse:String?) {
val user = UserManager.getInstance() private fun list_accounts() {
val phone: String println("List of available accounts:")
if (phoneToUse == null) val accounts = Utils.getAccounts()
{ if (accounts.size > 0) {
println("Please enter your phone number in international format.") for (str in accounts) {
println("Example: +4917077651234") System.out.println(" " + Utils.anonymize(str))
phone = getLine() }
} else { println("Use '--account <x>' to use one of those accounts.")
phone = phoneToUse } else {
println("NO ACCOUNTS FOUND")
println("Use '--login' to login to a telegram account.")
}
}
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.")
}
} }
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 +" + Utils.anonymize(user.user!!.getPhone()) + " 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(" --no-media Do not download media files.")
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 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(" --with-channels Backup channels as well.")
println(" --with-supergroups Backup supergroups as well.")
}
private fun list_accounts() {
println("List of available accounts:")
val accounts = Utils.getAccounts()
if (accounts.size > 0)
{
for (str in accounts)
{
System.out.println(" " + Utils.anonymize(str))
}
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 {
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.")
}
}
} }

View File

@ -20,68 +20,68 @@ import de.fabianonline.telegram_backup.DownloadProgressInterface
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
internal class CommandLineDownloadProgress : DownloadProgressInterface { internal class CommandLineDownloadProgress : DownloadProgressInterface {
private var mediaCount = 0 private var mediaCount = 0
private var i = 0 private var i = 0
override fun onMessageDownloadStart(count: Int, source: String?) { override fun onMessageDownloadStart(count: Int, source: String?) {
i = 0 i = 0
if (source == null) { if (source == null) {
System.out.println("Downloading $count messages.") System.out.println("Downloading $count messages.")
} else { } else {
System.out.println("Downloading " + count + " messages from " + Utils.anonymize(source)) System.out.println("Downloading " + count + " messages from " + Utils.anonymize(source))
} }
} }
override fun onMessageDownloaded(number: Int) { override fun onMessageDownloaded(number: Int) {
i += number i += number
print("..." + i) print("..." + i)
} }
override fun onMessageDownloadFinished() { override fun onMessageDownloadFinished() {
println(" done.") println(" done.")
} }
override fun onMediaDownloadStart(count: Int) { override fun onMediaDownloadStart(count: Int) {
i = 0 i = 0
mediaCount = count mediaCount = count
println("Checking and downloading media.") println("Checking and downloading media.")
println("Legend:") println("Legend:")
println("'V' - Video 'P' - Photo 'D' - Document") println("'V' - Video 'P' - Photo 'D' - Document")
println("'S' - Sticker 'A' - Audio 'G' - Geolocation") println("'S' - Sticker 'A' - Audio 'G' - Geolocation")
println("'.' - Previously downloaded file 'e' - Empty file") println("'.' - Previously downloaded file 'e' - Empty file")
println("' ' - Ignored media type (weblinks or contacts, for example)") println("' ' - Ignored media type (weblinks or contacts, for example)")
println("'x' - File skipped because of timeout errors") println("'x' - File skipped because of timeout errors")
println("" + count + " Files to check / download") println("" + count + " Files to check / download")
} }
override fun onMediaDownloaded(file_manager: AbstractMediaFileManager) { override fun onMediaDownloaded(file_manager: AbstractMediaFileManager) {
show(file_manager.letter.toUpperCase()) show(file_manager.letter.toUpperCase())
} }
override fun onMediaDownloadedEmpty() { override fun onMediaDownloadedEmpty() {
show("e") show("e")
} }
override fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager) { override fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager) {
show(".") show(".")
} }
override fun onMediaSkipped() { override fun onMediaSkipped() {
show("x") show("x")
} }
override fun onMediaDownloadFinished() { override fun onMediaDownloadFinished() {
showNewLine() showNewLine()
println("Done.") println("Done.")
} }
private fun show(letter: String) { private fun show(letter: String) {
print(letter) print(letter)
i++ i++
if (i % 100 == 0) showNewLine() if (i % 100 == 0) showNewLine()
} }
private fun showNewLine() { private fun showNewLine() {
println(" - $i/$mediaCount") println(" - $i/$mediaCount")
} }
} }

View File

@ -14,85 +14,84 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup package de.fabianonline.telegram_backup
internal object CommandLineOptions { internal object CommandLineOptions {
public var cmd_console = false public var cmd_console = false
public var cmd_help = false public var cmd_help = false
public var cmd_login = false public 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
var cmd_list_accounts = false var cmd_list_accounts = false
var cmd_version = false var cmd_version = false
var cmd_license = false var cmd_license = false
var cmd_daemon = false var cmd_daemon = false
var cmd_no_media = false var cmd_no_media = false
var cmd_anonymize = false var cmd_anonymize = false
var cmd_stats = false var cmd_stats = false
var cmd_channels = false var cmd_channels = false
var cmd_supergroups = false var cmd_supergroups = false
var val_account:String? = null var val_account: String? = null
var val_limit_messages:Int? = null var val_limit_messages: Int? = null
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>) { @JvmStatic
var last_cmd:String? = null fun parseOptions(args: Array<String>) {
loop@ for (arg in args) var last_cmd: String? = null
{ loop@ for (arg in args) {
if (last_cmd != null) if (last_cmd != null) {
{ when (last_cmd) {
when (last_cmd) { "--account" -> val_account = arg
"--account" -> val_account = arg "--limit-messages" -> val_limit_messages = Integer.parseInt(arg)
"--limit-messages" -> val_limit_messages = Integer.parseInt(arg) "--target" -> val_target = arg
"--target" -> val_target = arg "--export" -> val_export = arg
"--export" -> val_export = arg "--test" -> val_test = Integer.parseInt(arg)
"--test" -> val_test = Integer.parseInt(arg) }
} last_cmd = null
last_cmd = null continue
continue }
} when (arg) {
when (arg) { "-a", "--account" -> {
"-a", "--account" -> { last_cmd = "--account"
last_cmd = "--account" continue@loop
continue@loop }
} "-h", "--help" -> cmd_help = true
"-h", "--help" -> cmd_help = true "-l", "--login" -> cmd_login = true
"-l", "--login" -> cmd_login = true "--debug" -> cmd_debug = true
"--debug" -> cmd_debug = true "--trace" -> cmd_trace = true
"--trace" -> cmd_trace = true "--trace-telegram" -> cmd_trace_telegram = true
"--trace-telegram" -> cmd_trace_telegram = true "-A", "--list-accounts" -> cmd_list_accounts = true
"-A", "--list-accounts" -> cmd_list_accounts = true "--limit-messages" -> {
"--limit-messages" -> { last_cmd = arg
last_cmd = arg continue@loop
continue@loop }
} "--console" -> cmd_console = true
"--console" -> cmd_console = true "-t", "--target" -> {
"-t", "--target" -> { last_cmd = "--target"
last_cmd = "--target" continue@loop
continue@loop }
} "-V", "--version" -> cmd_version = true
"-V", "--version" -> cmd_version = true "-e", "--export" -> {
"-e", "--export" -> { last_cmd = "--export"
last_cmd = "--export" continue@loop
continue@loop }
} "--license" -> cmd_license = true
"--license" -> cmd_license = true "-d", "--daemon" -> cmd_daemon = true
"-d", "--daemon" -> cmd_daemon = true "--no-media" -> cmd_no_media = true
"--no-media" -> cmd_no_media = true "--test" -> {
"--test" -> { last_cmd = "--test"
last_cmd = "--test" continue@loop
continue@loop }
} "--anonymize" -> cmd_anonymize = true
"--anonymize" -> cmd_anonymize = true "--stats" -> cmd_stats = true
"--stats" -> cmd_stats = true "--with-channels" -> cmd_channels = true
"--with-channels" -> cmd_channels = true "--with-supergroups" -> cmd_supergroups = true
"--with-supergroups" -> cmd_supergroups = true else -> throw RuntimeException("Unknown command " + arg)
else -> throw RuntimeException("Unknown command " + arg) }
} }
} if (last_cmd != null) {
if (last_cmd != null) CommandLineController.show_error("Command $last_cmd had no parameter set.")
{ }
CommandLineController.show_error("Command $last_cmd had no parameter set.") }
}
}
} }

View File

@ -27,67 +27,67 @@ import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.ConsoleAppender 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) CommandLineOptions.parseOptions(args)
CommandLineRunner.setupLogging() CommandLineRunner.setupLogging()
CommandLineRunner.checkVersion() CommandLineRunner.checkVersion()
if (true || CommandLineOptions.cmd_console) { if (true || CommandLineOptions.cmd_console) {
// Always use the console for now. // Always use the console for now.
CommandLineController() CommandLineController()
} else { } else {
GUIController() GUIController()
} }
} }
object CommandLineRunner { object CommandLineRunner {
fun setupLogging() { fun setupLogging() {
val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger 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()
rootContext.reset() rootContext.reset()
val encoder = PatternLayoutEncoder() val encoder = PatternLayoutEncoder()
encoder.setContext(rootContext) encoder.setContext(rootContext)
encoder.setPattern("%d{HH:mm:ss.SSS} %-5level %-35.-35(%logger{0}.%method): %message%n") encoder.setPattern("%d{HH:mm:ss.SSS} %-5level %-35.-35(%logger{0}.%method): %message%n")
encoder.start() encoder.start()
val appender = ConsoleAppender<ILoggingEvent>() val appender = ConsoleAppender<ILoggingEvent>()
appender.setContext(rootContext) appender.setContext(rootContext)
appender.setEncoder(encoder) appender.setEncoder(encoder)
appender.start() appender.start()
rootLogger.addAppender(appender) rootLogger.addAppender(appender)
rootLogger.setLevel(Level.OFF) rootLogger.setLevel(Level.OFF)
if (CommandLineOptions.cmd_trace) { if (CommandLineOptions.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 (CommandLineOptions.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 (CommandLineOptions.cmd_trace_telegram) {
(LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE) (LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE)
} }
} }
fun checkVersion(): Boolean { fun checkVersion(): Boolean {
val v = Utils.getNewestVersion() val v = Utils.getNewestVersion()
if (v != null && v.isNewer) { if (v != null && v.isNewer) {
System.out.println("A newer version is vailable!") System.out.println("A newer version is vailable!")
System.out.println("You are using: " + Config.APP_APPVER) System.out.println("You are using: " + Config.APP_APPVER)
System.out.println("Available: " + v.version) System.out.println("Available: " + v.version)
System.out.println("Get it here: " + v.url) System.out.println("Get it here: " + v.url)
System.out.println() System.out.println()
System.out.println("Changes in this version:") System.out.println("Changes in this version:")
System.out.println(v.body) System.out.println(v.body)
System.out.println() System.out.println()
return false return false
} }
return true return true
} }
} }

View File

@ -22,39 +22,39 @@ import java.io.FileInputStream
import java.util.Properties import java.util.Properties
object Config { object Config {
val APP_ID = 32860 val APP_ID = 32860
val APP_HASH = "16e4ff955cd0adfc058f95ca564f562d" val APP_HASH = "16e4ff955cd0adfc058f95ca564f562d"
val APP_MODEL = "Desktop" val APP_MODEL = "Desktop"
val APP_SYSVER = "1.0" val APP_SYSVER = "1.0"
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 FILE_BASE = 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"
val FILE_NAME_DB_BACKUP = "database.version_%d.backup.sqlite" val FILE_NAME_DB_BACKUP = "database.version_%d.backup.sqlite"
val FILE_FILES_BASE = "files" val FILE_FILES_BASE = "files"
val FILE_STICKER_BASE = "stickers" val FILE_STICKER_BASE = "stickers"
var DELAY_AFTER_GET_MESSAGES: Long = 400 var DELAY_AFTER_GET_MESSAGES: Long = 400
var DELAY_AFTER_GET_FILE: Long = 100 var DELAY_AFTER_GET_FILE: Long = 100
var GET_MESSAGES_BATCH_SIZE = 200 var GET_MESSAGES_BATCH_SIZE = 200
var RENAMING_MAX_TRIES = 5 var RENAMING_MAX_TRIES = 5
var RENAMING_DELAY: Long = 1000 var RENAMING_DELAY: Long = 1000
val SECRET_GMAPS = "AIzaSyBEtUDhCQKEH6i2Mn1GAiQ9M_tLN0vxHIs" val SECRET_GMAPS = "AIzaSyBEtUDhCQKEH6i2Mn1GAiQ9M_tLN0vxHIs"
init { init {
val p = Properties() val p = Properties()
try { try {
p.load(Config::class.java.getResourceAsStream("/build.properties")) p.load(Config::class.java.getResourceAsStream("/build.properties"))
APP_APPVER = p.getProperty("version") APP_APPVER = p.getProperty("version")
} catch (e: IOException) { } catch (e: IOException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -17,355 +17,355 @@ import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
class DatabaseUpdates(protected var conn: Connection, protected var db: Database) { class DatabaseUpdates(protected var conn: Connection, protected var db: Database) {
private val maxPossibleVersion: Int private val maxPossibleVersion: Int
get() = updates.size get() = updates.size
init { init {
logger.debug("Registering Database Updates...") logger.debug("Registering Database Updates...")
register(DB_Update_1(conn, db)) register(DB_Update_1(conn, db))
register(DB_Update_2(conn, db)) register(DB_Update_2(conn, db))
register(DB_Update_3(conn, db)) register(DB_Update_3(conn, db))
register(DB_Update_4(conn, db)) register(DB_Update_4(conn, db))
register(DB_Update_5(conn, db)) register(DB_Update_5(conn, db))
register(DB_Update_6(conn, db)) register(DB_Update_6(conn, db))
register(DB_Update_7(conn, db)) register(DB_Update_7(conn, db))
register(DB_Update_8(conn, db)) register(DB_Update_8(conn, db))
} }
fun doUpdates() { fun doUpdates() {
try { try {
val stmt = conn.createStatement() val stmt = conn.createStatement()
var rs: ResultSet var rs: ResultSet
logger.debug("DatabaseUpdate.doUpdates running") logger.debug("DatabaseUpdate.doUpdates running")
logger.debug("Getting current database version") logger.debug("Getting current database version")
val version: Int val version: Int
logger.debug("Checking if table database_versions exists") logger.debug("Checking if table database_versions exists")
rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'") rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'")
rs.next() rs.next()
if (rs.getInt(1) == 0) { if (rs.getInt(1) == 0) {
logger.debug("Table does not exist") logger.debug("Table does not exist")
version = 0 version = 0
} else { } else {
logger.debug("Table exists. Checking max version") logger.debug("Table exists. Checking max version")
rs.close() rs.close()
rs = stmt.executeQuery("SELECT MAX(version) FROM database_versions") rs = stmt.executeQuery("SELECT MAX(version) FROM database_versions")
rs.next() rs.next()
version = rs.getInt(1) version = rs.getInt(1)
} }
rs.close() rs.close()
logger.debug("version: {}", version) logger.debug("version: {}", version)
System.out.println("Database version: " + version) System.out.println("Database version: " + version)
logger.debug("Max available database version is {}", maxPossibleVersion) logger.debug("Max available database version is {}", maxPossibleVersion)
if (version < maxPossibleVersion) { if (version < maxPossibleVersion) {
logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion) logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion)
var backup = false var backup = false
for (i in version + 1..maxPossibleVersion) { for (i in version + 1..maxPossibleVersion) {
if (getUpdateToVersion(i).needsBackup) { if (getUpdateToVersion(i).needsBackup) {
logger.debug("Update to version {} needs a backup", i) logger.debug("Update to version {} needs a backup", i)
backup = true backup = true
} }
} }
if (backup) { if (backup) {
if (version > 0) { if (version > 0) {
logger.debug("Performing backup") logger.debug("Performing backup")
db.backupDatabase(version) db.backupDatabase(version)
} else { } else {
logger.debug("NOT performing a backup, because we are creating a fresh database and don't need a backup of that.") logger.debug("NOT performing a backup, because we are creating a fresh database and don't need a backup of that.")
} }
} }
logger.debug("Applying updates") logger.debug("Applying updates")
try { try {
for (i in version + 1..maxPossibleVersion) { for (i in version + 1..maxPossibleVersion) {
getUpdateToVersion(i).doUpdate() getUpdateToVersion(i).doUpdate()
} }
} catch (e: SQLException) { } catch (e: SQLException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} else { } else {
logger.debug("No update necessary.") logger.debug("No update necessary.")
} }
} catch (e: SQLException) { } catch (e: SQLException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
private fun getUpdateToVersion(i: Int): DatabaseUpdate { private fun getUpdateToVersion(i: Int): DatabaseUpdate {
return updates.get(i - 1) return updates.get(i - 1)
} }
private fun register(d: DatabaseUpdate) { private fun register(d: DatabaseUpdate) {
logger.debug("Registering {} as update to version {}", d.javaClass, d.version) logger.debug("Registering {} as update to version {}", d.javaClass, d.version)
if (d.version != updates.size + 1) { if (d.version != updates.size + 1) {
throw RuntimeException("Tried to register DB update to version ${d.version}, but would need update to version ${updates.size + 1}") throw RuntimeException("Tried to register DB update to version ${d.version}, but would need update to version ${updates.size + 1}")
} }
updates.add(d) updates.add(d)
} }
companion object { companion object {
private val logger = LoggerFactory.getLogger(DatabaseUpdates::class.java) private val logger = LoggerFactory.getLogger(DatabaseUpdates::class.java)
private val updates = LinkedList<DatabaseUpdate>() private val updates = LinkedList<DatabaseUpdate>()
} }
} }
internal abstract class DatabaseUpdate(protected var conn: Connection, protected var db: Database) { internal abstract class DatabaseUpdate(protected var conn: Connection, protected var db: Database) {
protected var stmt: Statement protected var stmt: Statement
abstract val version: Int abstract val version: Int
init { init {
try { try {
stmt = conn.createStatement() stmt = conn.createStatement()
} catch (e: SQLException) { } catch (e: SQLException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
@Throws(SQLException::class) @Throws(SQLException::class)
fun doUpdate() { fun doUpdate() {
logger.debug("Applying update to version {}", version) logger.debug("Applying update to version {}", version)
System.out.println(" Updating to version $version...") System.out.println(" Updating to version $version...")
_doUpdate() _doUpdate()
logger.debug("Saving current database version to the db") logger.debug("Saving current database version to the db")
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES ($version)") stmt.executeUpdate("INSERT INTO database_versions (version) VALUES ($version)")
} }
@Throws(SQLException::class) @Throws(SQLException::class)
protected abstract fun _doUpdate() protected abstract fun _doUpdate()
open val needsBackup = false open val needsBackup = false
@Throws(SQLException::class) @Throws(SQLException::class)
protected fun execute(sql: String) { protected fun execute(sql: String) {
logger.debug("Executing: {}", sql) logger.debug("Executing: {}", sql)
stmt.executeUpdate(sql) stmt.executeUpdate(sql)
} }
companion object { companion object {
protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java) protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java)
} }
} }
internal class DB_Update_1(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_1(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 1 get() = 1
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
stmt.executeUpdate("CREATE TABLE messages (" stmt.executeUpdate("CREATE TABLE messages ("
+ "id INTEGER PRIMARY KEY ASC, " + "id INTEGER PRIMARY KEY ASC, "
+ "dialog_id INTEGER, " + "dialog_id INTEGER, "
+ "to_id INTEGER, " + "to_id INTEGER, "
+ "from_id INTEGER, " + "from_id INTEGER, "
+ "from_type TEXT, " + "from_type TEXT, "
+ "text TEXT, " + "text TEXT, "
+ "time TEXT, " + "time TEXT, "
+ "has_media BOOLEAN, " + "has_media BOOLEAN, "
+ "sticker TEXT, " + "sticker TEXT, "
+ "data BLOB," + "data BLOB,"
+ "type TEXT)") + "type TEXT)")
stmt.executeUpdate("CREATE TABLE dialogs (" stmt.executeUpdate("CREATE TABLE dialogs ("
+ "id INTEGER PRIMARY KEY ASC, " + "id INTEGER PRIMARY KEY ASC, "
+ "name TEXT, " + "name TEXT, "
+ "type TEXT)") + "type TEXT)")
stmt.executeUpdate("CREATE TABLE people (" stmt.executeUpdate("CREATE TABLE people ("
+ "id INTEGER PRIMARY KEY ASC, " + "id INTEGER PRIMARY KEY ASC, "
+ "first_name TEXT, " + "first_name TEXT, "
+ "last_name TEXT, " + "last_name TEXT, "
+ "username TEXT, " + "username TEXT, "
+ "type TEXT)") + "type TEXT)")
stmt.executeUpdate("CREATE TABLE database_versions (" + "version INTEGER)") stmt.executeUpdate("CREATE TABLE database_versions (" + "version INTEGER)")
} }
} }
internal class DB_Update_2(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_2(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 2 get() = 2
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'") stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'")
stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT") stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT")
} }
} }
internal class DB_Update_3(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_3(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 3 get() = 3
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'") stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'")
} }
} }
internal class DB_Update_4(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_4(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 4 get() = 4
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
stmt.executeUpdate("CREATE TABLE messages_new (id INTEGER PRIMARY KEY ASC, dialog_id INTEGER, to_id INTEGER, from_id INTEGER, from_type TEXT, text TEXT, time INTEGER, has_media BOOLEAN, sticker TEXT, data BLOB, type TEXT);") stmt.executeUpdate("CREATE TABLE messages_new (id INTEGER PRIMARY KEY ASC, dialog_id INTEGER, to_id INTEGER, from_id INTEGER, from_type TEXT, text TEXT, time INTEGER, has_media BOOLEAN, sticker TEXT, data BLOB, type TEXT);")
stmt.executeUpdate("INSERT INTO messages_new SELECT * FROM messages") stmt.executeUpdate("INSERT INTO messages_new SELECT * FROM messages")
stmt.executeUpdate("DROP TABLE messages") stmt.executeUpdate("DROP TABLE messages")
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO 'messages'") stmt.executeUpdate("ALTER TABLE messages_new RENAME TO 'messages'")
} }
} }
internal class DB_Update_5(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_5(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 5 get() = 5
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)") stmt.executeUpdate("CREATE TABLE runs (id INTEGER PRIMARY KEY ASC, time INTEGER, start_id INTEGER, end_id INTEGER, count_missing INTEGER)")
} }
} }
internal class DB_Update_6(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_6(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 6 get() = 6
override val needsBackup = true override val needsBackup = true
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
stmt.executeUpdate( stmt.executeUpdate(
"CREATE TABLE messages_new (\n" + "CREATE TABLE messages_new (\n" +
" id INTEGER PRIMARY KEY ASC,\n" + " id INTEGER PRIMARY KEY ASC,\n" +
" message_type TEXT,\n" + " message_type TEXT,\n" +
" dialog_id INTEGER,\n" + " dialog_id INTEGER,\n" +
" chat_id INTEGER,\n" + " chat_id INTEGER,\n" +
" sender_id INTEGER,\n" + " sender_id INTEGER,\n" +
" fwd_from_id INTEGER,\n" + " fwd_from_id INTEGER,\n" +
" text TEXT,\n" + " text TEXT,\n" +
" time INTEGER,\n" + " time INTEGER,\n" +
" has_media BOOLEAN,\n" + " has_media BOOLEAN,\n" +
" media_type TEXT,\n" + " media_type TEXT,\n" +
" media_file TEXT,\n" + " media_file TEXT,\n" +
" media_size INTEGER,\n" + " media_size INTEGER,\n" +
" media_json TEXT,\n" + " media_json TEXT,\n" +
" markup_json TEXT,\n" + " markup_json TEXT,\n" +
" data BLOB)") " data BLOB)")
val mappings = LinkedHashMap<String, String>() val mappings = LinkedHashMap<String, String>()
mappings.put("id", "id") mappings.put("id", "id")
mappings.put("message_type", "type") mappings.put("message_type", "type")
mappings.put("dialog_id", "CASE from_type WHEN 'user' THEN dialog_id ELSE NULL END") mappings.put("dialog_id", "CASE from_type WHEN 'user' THEN dialog_id ELSE NULL END")
mappings.put("chat_id", "CASE from_type WHEN 'chat' THEN dialog_id ELSE NULL END") mappings.put("chat_id", "CASE from_type WHEN 'chat' THEN dialog_id ELSE NULL END")
mappings.put("sender_id", "from_id") mappings.put("sender_id", "from_id")
mappings.put("text", "text") mappings.put("text", "text")
mappings.put("time", "time") mappings.put("time", "time")
mappings.put("has_media", "has_media") mappings.put("has_media", "has_media")
mappings.put("data", "data") mappings.put("data", "data")
val query = StringBuilder("INSERT INTO messages_new\n(") val query = StringBuilder("INSERT INTO messages_new\n(")
var first: Boolean var first: Boolean
first = true first = true
for (s in mappings.keys) { for (s in mappings.keys) {
if (!first) query.append(", ") if (!first) query.append(", ")
query.append(s) query.append(s)
first = false first = false
} }
query.append(")\nSELECT \n") query.append(")\nSELECT \n")
first = true first = true
for (s in mappings.values) { for (s in mappings.values) {
if (!first) query.append(", ") if (!first) query.append(", ")
query.append(s) query.append(s)
first = false first = false
} }
query.append("\nFROM messages") query.append("\nFROM messages")
stmt.executeUpdate(query.toString()) stmt.executeUpdate(query.toString())
System.out.println(" Updating the data (this might take some time)...") System.out.println(" Updating the data (this might take some time)...")
val rs = stmt.executeQuery("SELECT id, data FROM messages_new") val rs = stmt.executeQuery("SELECT id, data FROM messages_new")
val ps = conn.prepareStatement("UPDATE messages_new SET fwd_from_id=?, media_type=?, media_file=?, media_size=? WHERE id=?") val ps = conn.prepareStatement("UPDATE messages_new SET fwd_from_id=?, media_type=?, media_file=?, media_size=? WHERE id=?")
while (rs.next()) { while (rs.next()) {
ps.setInt(5, rs.getInt(1)) ps.setInt(5, rs.getInt(1))
val msg = Database.bytesToTLMessage(rs.getBytes(2)) val msg = Database.bytesToTLMessage(rs.getBytes(2))
if (msg == null || msg.getFwdFrom() == null) { if (msg == null || msg.getFwdFrom() == null) {
ps.setNull(1, Types.INTEGER) ps.setNull(1, Types.INTEGER)
} 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.client)
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)
ps.setNull(4, Types.INTEGER) ps.setNull(4, Types.INTEGER)
} else { } else {
ps.setString(2, f.name) ps.setString(2, f.name)
ps.setString(3, f.targetFilename) ps.setString(3, f.targetFilename)
ps.setInt(4, f.size) ps.setInt(4, f.size)
} }
ps.addBatch() ps.addBatch()
} }
rs.close() rs.close()
conn.setAutoCommit(false) conn.setAutoCommit(false)
ps.executeBatch() ps.executeBatch()
conn.commit() conn.commit()
conn.setAutoCommit(true) conn.setAutoCommit(true)
stmt.executeUpdate("DROP TABLE messages") stmt.executeUpdate("DROP TABLE messages")
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO messages") stmt.executeUpdate("ALTER TABLE messages_new RENAME TO messages")
} }
} }
internal class DB_Update_7(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_7(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 7 get() = 7
override val needsBackup = true override val needsBackup = true
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER") stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER")
stmt.executeUpdate("UPDATE messages SET api_layer=51") stmt.executeUpdate("UPDATE messages SET api_layer=51")
} }
} }
internal class DB_Update_8(conn: Connection, db: Database) : DatabaseUpdate(conn, db) { internal class DB_Update_8(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
override val version: Int override val version: Int
get() = 8 get() = 8
override val needsBackup = true override val needsBackup = true
@Throws(SQLException::class) @Throws(SQLException::class)
override fun _doUpdate() { override fun _doUpdate() {
execute("ALTER TABLE messages ADD COLUMN source_type TEXT") execute("ALTER TABLE messages ADD COLUMN source_type TEXT")
execute("ALTER TABLE messages ADD COLUMN source_id INTEGER") execute("ALTER TABLE messages ADD COLUMN source_id INTEGER")
execute("update messages set source_type='dialog', source_id=dialog_id where dialog_id is not null") execute("update messages set source_type='dialog', source_id=dialog_id where dialog_id is not null")
execute("update messages set source_type='group', source_id=chat_id where chat_id is not null") execute("update messages set source_type='group', source_id=chat_id where chat_id is not null")
execute("CREATE TABLE messages_new (" + execute("CREATE TABLE messages_new (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT," + "id INTEGER PRIMARY KEY AUTOINCREMENT," +
"message_id INTEGER," + "message_id INTEGER," +
"message_type TEXT," + "message_type TEXT," +
"source_type TEXT," + "source_type TEXT," +
"source_id INTEGER," + "source_id INTEGER," +
"sender_id INTEGER," + "sender_id INTEGER," +
"fwd_from_id INTEGER," + "fwd_from_id INTEGER," +
"text TEXT," + "text TEXT," +
"time INTEGER," + "time INTEGER," +
"has_media BOOLEAN," + "has_media BOOLEAN," +
"media_type TEXT," + "media_type TEXT," +
"media_file TEXT," + "media_file TEXT," +
"media_size INTEGER," + "media_size INTEGER," +
"media_json TEXT," + "media_json TEXT," +
"markup_json TEXT," + "markup_json TEXT," +
"data BLOB," + "data BLOB," +
"api_layer INTEGER)") "api_layer INTEGER)")
execute("INSERT INTO messages_new" + execute("INSERT INTO messages_new" +
"(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," +
"media_file, media_size, media_json, markup_json, data, api_layer)" + "media_file, media_size, media_json, markup_json, data, api_layer)" +
"SELECT " + "SELECT " +
"id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," + "id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type," +
"media_file, media_size, media_json, markup_json, data, api_layer FROM messages") "media_file, media_size, media_json, markup_json, data, api_layer FROM messages")
execute("DROP TABLE messages") execute("DROP TABLE messages")
execute("ALTER TABLE messages_new RENAME TO 'messages'") execute("ALTER TABLE messages_new RENAME TO 'messages'")
execute("CREATE UNIQUE INDEX unique_messages ON messages (source_type, source_id, message_id)") execute("CREATE UNIQUE INDEX unique_messages ON messages (source_type, source_id, message_id)")
} }
} }

View File

@ -53,102 +53,102 @@ import java.nio.file.StandardCopyOption
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressInterface) { class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressInterface) {
internal var user: UserManager? = null internal var user: UserManager? = null
internal var db: Database? = null internal var db: Database? = null
internal var prog: DownloadProgressInterface? = null internal var prog: DownloadProgressInterface? = null
internal var has_seen_flood_wait_message = false internal var has_seen_flood_wait_message = false
init { init {
this.user = UserManager.getInstance() this.user = UserManager.getInstance()
this.prog = p this.prog = p
this.db = Database.getInstance() 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
do { do {
completed = true completed = true
try { try {
_downloadMessages(limit) _downloadMessages(limit)
} catch (e: RpcErrorException) { } catch (e: RpcErrorException) {
if (e.getCode() == 420) { // FLOOD_WAIT if (e.getCode() == 420) { // FLOOD_WAIT
completed = false completed = false
Utils.obeyFloodWaitException(e) Utils.obeyFloodWaitException(e)
} else { } else {
throw e throw e
} }
} catch (e: TimeoutException) { } catch (e: TimeoutException) {
completed = false completed = false
System.out.println("") System.out.println("")
System.out.println("Telegram took too long to respond to our request.") 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.") System.out.println("I'm going to wait a minute and then try again.")
try { try {
TimeUnit.MINUTES.sleep(1) TimeUnit.MINUTES.sleep(1)
} catch (e2: InterruptedException) { } catch (e2: InterruptedException) {
} }
System.out.println("") System.out.println("")
} }
} while (!completed) } while (!completed)
} }
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
fun _downloadMessages(limit: Int?) { fun _downloadMessages(limit: Int?) {
logger.info("This is _downloadMessages with limit {}", limit) logger.info("This is _downloadMessages with limit {}", limit)
val dialog_limit = 100 val dialog_limit = 100
logger.info("Downloading the last {} dialogs", dialog_limit) logger.info("Downloading the last {} dialogs", dialog_limit)
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 dialogs = client!!.messagesGetDialogs( val dialogs = client!!.messagesGetDialogs(
0, 0,
0, 0,
TLInputPeerEmpty(), TLInputPeerEmpty(),
dialog_limit) dialog_limit)
logger.debug("Got {} dialogs", dialogs.getDialogs().size) logger.debug("Got {} dialogs", dialogs.getDialogs().size)
for (d in dialogs.getDialogs()) { for (d in dialogs.getDialogs()) {
if (d.getTopMessage() > max_message_id && d.getPeer() !is TLPeerChannel) { if (d.getTopMessage() > max_message_id && d.getPeer() !is TLPeerChannel) {
logger.trace("Updating top message id: {} => {}. Dialog type: {}", max_message_id, d.getTopMessage(), d.getPeer().javaClass) logger.trace("Updating top message id: {} => {}. Dialog type: {}", max_message_id, d.getTopMessage(), d.getPeer().javaClass)
max_message_id = d.getTopMessage() max_message_id = d.getTopMessage()
} }
} }
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)
max_database_id = Math.max(max_database_id, max_message_id - limit) max_database_id = Math.max(max_database_id, max_message_id - limit)
System.out.println("New top message id 'in database' is " + max_database_id) System.out.println("New top message id 'in database' is " + max_database_id)
} }
if (max_message_id - max_database_id > 1000000) { if (max_message_id - max_database_id > 1000000) {
System.out.println("Would have to load more than 1 million messages which is not supported by telegram. Capping the list.") System.out.println("Would have to load more than 1 million messages which is not supported by telegram. Capping the list.")
logger.debug("max_message_id={}, max_database_id={}, difference={}", max_message_id, max_database_id, max_message_id - max_database_id) logger.debug("max_message_id={}, max_database_id={}, difference={}", max_message_id, max_database_id, max_message_id - max_database_id)
max_database_id = Math.max(0, max_message_id - 1000000) max_database_id = Math.max(0, max_message_id - 1000000)
logger.debug("new max_database_id: {}", max_database_id) logger.debug("new max_database_id: {}", max_database_id)
} }
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 then max_message_id. This shouldn't happen. But the telegram api nonetheless does that sometimes. Just ignore this error, wait a few seconds and then try again.")
} else { } 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
val ids = makeIdList(start_id, end_id) val ids = makeIdList(start_id, end_id)
downloadMessages(ids, null, null) downloadMessages(ids, null, null)
} }
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)
/*if (db_count != db_max) { /*if (db_count != db_max) {
if (limit != null) { if (limit != null) {
System.out.println("You are missing messages in your database. But since you're using '--limit-messages', I won't download these now."); System.out.println("You are missing messages in your database. But since you're using '--limit-messages', I won't download these now.");
} else { } else {
@ -169,138 +169,138 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
} }
*/ */
if (CommandLineOptions.cmd_channels || CommandLineOptions.cmd_supergroups) { if (CommandLineOptions.cmd_channels || CommandLineOptions.cmd_supergroups) {
System.out.println("Processing channels and/or supergroups...") System.out.println("Processing channels and/or supergroups...")
System.out.println("Please note that only channels/supergroups in the last 100 active chats are processed.") System.out.println("Please note that only channels/supergroups in the last 100 active chats are processed.")
val channel_access_hashes = HashMap<Int, Long>() val channel_access_hashes = HashMap<Int, Long>()
val channel_names = HashMap<Int, String>() val channel_names = HashMap<Int, String>()
val channels = LinkedList<Int>() val channels = LinkedList<Int>()
val supergroups = LinkedList<Int>() val supergroups = LinkedList<Int>()
// TODO Add chat title (and other stuff?) to the database // TODO Add chat title (and other stuff?) to the database
for (c in dialogs.getChats()) { for (c in dialogs.getChats()) {
if (c is TLChannel) { if (c is TLChannel) {
channel_access_hashes.put(c.getId(), c.getAccessHash()) channel_access_hashes.put(c.getId(), c.getAccessHash())
channel_names.put(c.getId(), c.getTitle()) channel_names.put(c.getId(), c.getTitle())
if (c.getMegagroup()) { if (c.getMegagroup()) {
supergroups.add(c.getId()) supergroups.add(c.getId())
} else { } else {
channels.add(c.getId()) channels.add(c.getId())
} }
// Channel: TLChannel // Channel: TLChannel
// Supergroup: getMegagroup()==true // Supergroup: getMegagroup()==true
} }
} }
for (d in dialogs.getDialogs()) { for (d in dialogs.getDialogs()) {
if (d.getPeer() is TLPeerChannel) { if (d.getPeer() is TLPeerChannel) {
val channel_id = (d.getPeer() as TLPeerChannel).getChannelId() val channel_id = (d.getPeer() as TLPeerChannel).getChannelId()
// If this is a channel and we don't want to download channels OR // If this is a channel and we don't want to download channels OR
// it is a supergroups and we don't want to download supergroups, then // it is a supergroups and we don't want to download supergroups, then
if (channels.contains(channel_id) && !CommandLineOptions.cmd_channels || supergroups.contains(channel_id) && !CommandLineOptions.cmd_supergroups) { if (channels.contains(channel_id) && !CommandLineOptions.cmd_channels || supergroups.contains(channel_id) && !CommandLineOptions.cmd_supergroups) {
// Skip this chat. // Skip this chat.
continue continue
} }
val max_known_id = db!!.getTopMessageIDForChannel(channel_id) val max_known_id = db!!.getTopMessageIDForChannel(channel_id)
if (d.getTopMessage() > max_known_id) { if (d.getTopMessage() > max_known_id) {
val ids = makeIdList(max_known_id + 1, d.getTopMessage()) val ids = makeIdList(max_known_id + 1, d.getTopMessage())
val access_hash = channel_access_hashes.get(channel_id) ?: throw RuntimeException("AccessHash for Channel missing.") val access_hash = channel_access_hashes.get(channel_id) ?: throw RuntimeException("AccessHash for Channel missing.")
var channel_name = channel_names.get(channel_id) var channel_name = channel_names.get(channel_id)
if (channel_name == null) { if (channel_name == null) {
channel_name = "?" channel_name = "?"
} }
val channel = TLInputChannel(channel_id, access_hash) val channel = TLInputChannel(channel_id, access_hash)
downloadMessages(ids, channel, "channel $channel_name") downloadMessages(ids, channel, "channel $channel_name")
} }
} }
} }
} }
} }
@Throws(RpcErrorException::class, IOException::class) @Throws(RpcErrorException::class, IOException::class)
private fun downloadMessages(ids: MutableList<Int>, channel: TLInputChannel?, source_string: String?) { private fun downloadMessages(ids: MutableList<Int>, channel: TLInputChannel?, source_string: String?) {
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) {
logger.trace("Loop") logger.trace("Loop")
val vector = TLIntVector() val vector = TLIntVector()
val download_count = Config.GET_MESSAGES_BATCH_SIZE val download_count = Config.GET_MESSAGES_BATCH_SIZE
logger.trace("download_count: {}", download_count) logger.trace("download_count: {}", download_count)
for (i in 0 until download_count) { for (i in 0 until download_count) {
if (ids.size == 0) break if (ids.size == 0) break
vector.add(ids.removeAt(0)) vector.add(ids.removeAt(0))
} }
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 response: TLAbsMessages
var tries = 0 var tries = 0
while (true) { while (true) {
logger.trace("Trying getMessages(), tries={}", tries) logger.trace("Trying getMessages(), tries={}", tries)
if (tries >= 5) { if (tries >= 5) {
CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.") CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.")
} }
tries++ tries++
try { try {
if (channel == null) { if (channel == null) {
response = client!!.messagesGetMessages(vector) response = client!!.messagesGetMessages(vector)
} else { } else {
response = client!!.channelsGetMessages(channel, vector) response = client!!.channelsGetMessages(channel, vector)
} }
break break
} catch (e: RpcErrorException) { } catch (e: RpcErrorException) {
if (e.getCode() == 420) { // FLOOD_WAIT if (e.getCode() == 420) { // FLOOD_WAIT
Utils.obeyFloodWaitException(e, has_seen_flood_wait_message) Utils.obeyFloodWaitException(e, has_seen_flood_wait_message)
has_seen_flood_wait_message = true has_seen_flood_wait_message = true
} else { } else {
throw e 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) db!!.saveMessages(response.getMessages(), Kotlogram.API_LAYER)
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) TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES)
} catch (e: InterruptedException) { } 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 var completed: Boolean
do { do {
completed = true completed = true
try { try {
_downloadMedia() _downloadMedia()
} catch (e: RpcErrorException) { } catch (e: RpcErrorException) {
if (e.getTag().startsWith("420: FLOOD_WAIT_")) { if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
completed = false completed = false
Utils.obeyFloodWaitException(e) Utils.obeyFloodWaitException(e)
} else { } else {
throw e throw e
} }
} }
/*catch (TimeoutException e) { /*catch (TimeoutException e) {
completed = false; completed = false;
System.out.println(""); System.out.println("");
System.out.println("Telegram took too long to respond to our request."); System.out.println("Telegram took too long to respond to our request.");
@ -309,181 +309,181 @@ class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressI
try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {} try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {}
System.out.println(""); System.out.println("");
}*/ }*/
} while (!completed) } while (!completed)
} }
@Throws(RpcErrorException::class, IOException::class) @Throws(RpcErrorException::class, IOException::class)
private fun _downloadMedia() { 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, null) downloadMessages(ids, null, null)
} }
val messages = this.db!!.getMessagesWithMedia() val messages = this.db!!.getMessagesWithMedia()
logger.debug("Database returned {} messages with media", messages.size) logger.debug("Database returned {} messages with media", messages.size)
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!!, client!!)
logger.trace("message {}, {}, {}, {}, {}", logger.trace("message {}, {}, {}, {}, {}",
msg.getId(), msg.getId(),
msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", ""), msg.getMedia().javaClass.getSimpleName().replace("TLMessageMedia", ""),
m!!.javaClass.getSimpleName(), m!!.javaClass.getSimpleName(),
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 { } else {
try { try {
m.download() m.download()
prog!!.onMediaDownloaded(m) prog!!.onMediaDownloaded(m)
} 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> {
val a = LinkedList<Int>() val a = LinkedList<Int>()
for (i in start..end) a.add(i) for (i in start..end) a.add(i)
return a return a
} }
companion object { companion object {
internal var download_client: TelegramClient? = null internal var download_client: TelegramClient? = null
internal var last_download_succeeded = true internal var last_download_succeeded = true
internal val logger = LoggerFactory.getLogger(DownloadManager::class.java) internal val logger = LoggerFactory.getLogger(DownloadManager::class.java)
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
fun downloadFile(targetFilename: String, size: Int, dcId: Int, volumeId: Long, localId: Int, secret: Long) { fun downloadFile(targetFilename: String, size: Int, dcId: Int, volumeId: Long, localId: Int, secret: Long) {
val loc = TLInputFileLocation(volumeId, localId, secret) val loc = TLInputFileLocation(volumeId, localId, secret)
downloadFileFromDc(targetFilename, loc, dcId, size) downloadFileFromDc(targetFilename, loc, dcId, size)
} }
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
fun downloadFile(targetFilename: String, size: Int, dcId: Int, id: Long, accessHash: Long) { fun downloadFile(targetFilename: String, size: Int, dcId: Int, id: Long, accessHash: Long) {
val loc = TLInputDocumentFileLocation(id, accessHash) val loc = TLInputDocumentFileLocation(id, accessHash)
downloadFileFromDc(targetFilename, loc, dcId, size) downloadFileFromDc(targetFilename, loc, dcId, size)
} }
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
private fun downloadFileFromDc(target: String, loc: TLAbsInputFileLocation, dcID: Int, size: Int): Boolean { private fun downloadFileFromDc(target: String, loc: TLAbsInputFileLocation, dcID: Int, size: Int): Boolean {
var fos: FileOutputStream? = null var fos: FileOutputStream? = null
try { try {
val temp_filename = target + ".downloading" val temp_filename = target + ".downloading"
logger.debug("Downloading file {}", target) logger.debug("Downloading file {}", target)
logger.trace("Temporary filename: {}", temp_filename) logger.trace("Temporary filename: {}", temp_filename)
var offset = 0 var offset = 0
if (File(temp_filename).isFile()) { if (File(temp_filename).isFile()) {
logger.info("Temporary filename already exists; continuing this file") logger.info("Temporary filename already exists; continuing this file")
offset = File(temp_filename).length().toInt() offset = File(temp_filename).length().toInt()
if (offset >= size) { if (offset >= size) {
logger.warn("Temporary file size is >= the target size. Assuming corrupt file & deleting it") logger.warn("Temporary file size is >= the target size. Assuming corrupt file & deleting it")
File(temp_filename).delete() File(temp_filename).delete()
offset = 0 offset = 0
} }
} }
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 response: TLFile? = null
var try_again: Boolean var try_again: Boolean
do { do {
try_again = false 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)
try { try {
response = download_client!!.executeRpcQuery(req, dcID) as TLFile response = download_client!!.executeRpcQuery(req, dcID) as TLFile
} catch (e: RpcErrorException) { } catch (e: RpcErrorException) {
if (e.getTag().startsWith("420: FLOOD_WAIT_")) { if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
try_again = true try_again = true
Utils.obeyFloodWaitException(e) Utils.obeyFloodWaitException(e)
} else if (e.getCode() == 400) { } else if (e.getCode() == 400) {
//Somehow this file is broken. No idea why. Let's skip it for now //Somehow this file is broken. No idea why. Let's skip it for now
return false return false
} else { } else {
throw e throw e
} }
} }
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) TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_FILE)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
} }
} while (offset < size && (response!!.getBytes().getData().size > 0 || try_again)) } while (offset < size && (response!!.getBytes().getData().size > 0 || try_again))
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.")
File(temp_filename).delete() File(temp_filename).delete()
System.exit(1) System.exit(1)
} }
logger.trace("Renaming {} to {}", temp_filename, target) logger.trace("Renaming {} to {}", temp_filename, target)
var rename_tries = 0 var rename_tries = 0
var last_exception: IOException? = null var last_exception: IOException? = null
while (rename_tries <= Config.RENAMING_MAX_TRIES) { while (rename_tries <= Config.RENAMING_MAX_TRIES) {
rename_tries++ rename_tries++
try { try {
Files.move(File(temp_filename).toPath(), File(target).toPath(), StandardCopyOption.REPLACE_EXISTING) Files.move(File(temp_filename).toPath(), File(target).toPath(), StandardCopyOption.REPLACE_EXISTING)
last_exception = null last_exception = null
break break
} catch (e: IOException) { } catch (e: IOException) {
logger.debug("Exception during move. rename_tries: {}. Exception: {}", rename_tries, e) logger.debug("Exception during move. rename_tries: {}. Exception: {}", rename_tries, e)
last_exception = e last_exception = e
try { try {
TimeUnit.MILLISECONDS.sleep(Config.RENAMING_DELAY) TimeUnit.MILLISECONDS.sleep(Config.RENAMING_DELAY)
} catch (e2: InterruptedException) { } catch (e2: InterruptedException) {
} }
} }
} }
if (last_exception != null) { if (last_exception != null) {
throw last_exception throw last_exception
} }
last_download_succeeded = true last_download_succeeded = true
return true return true
} catch (ex: java.io.IOException) { } catch (ex: java.io.IOException) {
if (fos != null) fos.close() if (fos != null) fos.close()
System.out.println("IOException happened while downloading " + target) System.out.println("IOException happened while downloading " + target)
throw ex throw ex
} catch (ex: RpcErrorException) { } catch (ex: RpcErrorException) {
if (fos != null) fos.close() if (fos != null) fos.close()
if (ex.getCode() == 500) { if (ex.getCode() == 500) {
if (!last_download_succeeded) { if (!last_download_succeeded) {
System.out.println("Got an Internal Server Error from Telegram. Since the file downloaded before also happened to get this error, we will stop downloading now. Please try again later.") System.out.println("Got an Internal Server Error from Telegram. Since the file downloaded before also happened to get this error, we will stop downloading now. Please try again later.")
throw ex throw ex
} }
last_download_succeeded = false last_download_succeeded = false
System.out.println("Got an Internal Server Error from Telegram. Skipping this file for now. Next run of telegram_backup will continue to download this file.") System.out.println("Got an Internal Server Error from Telegram. Skipping this file for now. Next run of telegram_backup will continue to download this file.")
logger.warn(ex.toString()) logger.warn(ex.toString())
return false return false
} }
System.out.println("RpcErrorException happened while downloading " + target) System.out.println("RpcErrorException happened while downloading " + target)
throw ex throw ex
} }
} }
@Throws(IOException::class) @Throws(IOException::class)
fun downloadExternalFile(target: String, url: String): Boolean { fun downloadExternalFile(target: String, url: String): Boolean {
FileUtils.copyURLToFile(URL(url), File(target), 5000, 5000) FileUtils.copyURLToFile(URL(url), File(target), 5000, 5000)
return true return true
} }
} }
} }

View File

@ -19,14 +19,14 @@ package de.fabianonline.telegram_backup
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
interface DownloadProgressInterface { interface DownloadProgressInterface {
fun onMessageDownloadStart(count: Int, source: String?) fun onMessageDownloadStart(count: Int, source: String?)
fun onMessageDownloaded(number: Int) fun onMessageDownloaded(number: Int)
fun onMessageDownloadFinished() fun onMessageDownloadFinished()
fun onMediaDownloadStart(count: Int) fun onMediaDownloadStart(count: Int)
fun onMediaDownloaded(file_manager: AbstractMediaFileManager) fun onMediaDownloaded(file_manager: AbstractMediaFileManager)
fun onMediaDownloadedEmpty() fun onMediaDownloadedEmpty()
fun onMediaSkipped() fun onMediaSkipped()
fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager) fun onMediaAlreadyPresent(file_manager: AbstractMediaFileManager)
fun onMediaDownloadFinished() fun onMediaDownloadFinished()
} }

View File

@ -25,61 +25,61 @@ import java.awt.event.ActionListener
import java.util.Vector import java.util.Vector
class GUIController() { class GUIController() {
init { init {
showAccountChooserDialog() 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() private fun showAccountChooserDialog() {
sections.setLayout(BoxLayout(sections, BoxLayout.Y_AXIS)) 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)
}
val top = JPanel() private fun addAccountDialog() {
top.setLayout(BoxLayout(top, BoxLayout.Y_AXIS)) val loginDialog = JDialog()
top.add(JLabel("Please enter your phone number in international format:")) loginDialog.setTitle("Add an account")
top.add(JTextField("+49123773212")) loginDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
sections.add(top) val sections = JPanel()
sections.add(Box.createVerticalStrut(5)) sections.setLayout(BoxLayout(sections, BoxLayout.Y_AXIS))
sections.add(JSeparator(SwingConstants.HORIZONTAL))
val middle = JPanel() val top = JPanel()
middle.setLayout(BoxLayout(middle, BoxLayout.Y_AXIS)) top.setLayout(BoxLayout(top, BoxLayout.Y_AXIS))
middle.add(JLabel("Telegram sent you a code. Enter it here:")) top.add(JLabel("Please enter your phone number in international format:"))
middle.add(JTextField()) top.add(JTextField("+49123773212"))
middle.setEnabled(false)
sections.add(middle) sections.add(top)
sections.add(Box.createVerticalStrut(5)) sections.add(Box.createVerticalStrut(5))
sections.add(JSeparator(SwingConstants.HORIZONTAL)) sections.add(JSeparator(SwingConstants.HORIZONTAL))
loginDialog.add(sections) val middle = JPanel()
loginDialog.setVisible(true) 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

@ -21,32 +21,32 @@ import java.lang.StringBuilder
import java.io.File import java.io.File
object StickerConverter { object StickerConverter {
fun makeFilenameWithPath(attr: TLDocumentAttributeSticker): String { fun makeFilenameWithPath(attr: TLDocumentAttributeSticker): String {
val file = StringBuilder() val file = StringBuilder()
file.append(makePath()) file.append(makePath())
file.append(makeFilename(attr)) file.append(makeFilename(attr))
return file.toString() return file.toString()
} }
fun makeFilename(attr: TLDocumentAttributeSticker): String { fun makeFilename(attr: TLDocumentAttributeSticker): String {
val file = StringBuilder() val file = StringBuilder()
if (attr.getStickerset() is TLInputStickerSetShortName) { if (attr.getStickerset() is TLInputStickerSetShortName) {
file.append((attr.getStickerset() as TLInputStickerSetShortName).getShortName()) file.append((attr.getStickerset() as TLInputStickerSetShortName).getShortName())
} else if (attr.getStickerset() is TLInputStickerSetID) { } else if (attr.getStickerset() is TLInputStickerSetID) {
file.append((attr.getStickerset() as TLInputStickerSetID).getId()) file.append((attr.getStickerset() as TLInputStickerSetID).getId())
} }
file.append("_") file.append("_")
file.append(attr.getAlt().hashCode()) file.append(attr.getAlt().hashCode())
file.append(".webp") file.append(".webp")
return file.toString() return file.toString()
} }
fun makePath(): String { fun makePath(): String {
val path = Config.FILE_BASE + val path = Config.FILE_BASE +
File.separatorChar + File.separatorChar +
Config.FILE_STICKER_BASE + Config.FILE_STICKER_BASE +
File.separatorChar File.separatorChar
File(path).mkdirs() File(path).mkdirs()
return path return path
} }
} }

View File

@ -27,20 +27,20 @@ import java.io.InputStream
import com.github.badoualy.telegram.tl.StreamUtils.readTLObject import com.github.badoualy.telegram.tl.StreamUtils.readTLObject
class TLRequestAccountGetPasswordWithCurrentSalt : TLMethod<TLPassword>() { class TLRequestAccountGetPasswordWithCurrentSalt : TLMethod<TLPassword>() {
private val _constructor = "account.getPassword#548a30f5" private val _constructor = "account.getPassword#548a30f5"
override fun getConstructorId(): Int = CONSTRUCTOR_ID override fun getConstructorId(): Int = CONSTRUCTOR_ID
@Throws(IOException::class) @Throws(IOException::class)
override fun deserializeResponse(stream: InputStream, context: TLContext): TLPassword { override fun deserializeResponse(stream: InputStream, context: TLContext): TLPassword {
val response = (readTLObject(stream, context) ?: throw IOException("Unable to parse response")) as? TLPassword ?: throw IOException("Incorrect response type, expected getClass().getCanonicalName(), found response.getClass().getCanonicalName()") val response = (readTLObject(stream, context) ?: throw IOException("Unable to parse response")) as? TLPassword ?: throw IOException("Incorrect response type, expected getClass().getCanonicalName(), found response.getClass().getCanonicalName()")
return response return response
} }
override fun toString(): String { override fun toString(): String {
return _constructor return _constructor
} }
companion object { companion object {
val CONSTRUCTOR_ID = 0x548a30f5 val CONSTRUCTOR_ID = 0x548a30f5
} }
} }

View File

@ -28,133 +28,133 @@ import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
internal class TelegramUpdateHandler : UpdateCallback { internal class TelegramUpdateHandler : UpdateCallback {
private var user: UserManager? = null private var user: UserManager? = null
private var db: Database? = null private var db: Database? = null
var debug = false var debug = false
fun activate() { fun activate() {
this.user = UserManager.getInstance() this.user = UserManager.getInstance()
this.db = Database.getInstance() this.db = Database.getInstance()
} }
override fun onUpdates(client: TelegramClient, updates: TLUpdates) { override fun onUpdates(client: TelegramClient, updates: TLUpdates) {
if (db == null) return if (db == null) return
if (debug) System.out.println("onUpdates - " + updates.getUpdates().size + " Updates, " + updates.getUsers().size + " Users, " + updates.getChats().size + " Chats") if (debug) System.out.println("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, client)
if (debug) System.out.println(" " + update.javaClass.getName()) if (debug) System.out.println(" " + 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 if (db == null) return
if (debug) System.out.println("onUpdatesCombined") if (debug) System.out.println("onUpdatesCombined")
for (update in updates.getUpdates()) { for (update in updates.getUpdates()) {
processUpdate(update, client) processUpdate(update, client)
} }
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 if (db == null) return
if (debug) System.out.println("onUpdateShort") if (debug) System.out.println("onUpdateShort")
processUpdate(update.getUpdate(), client) processUpdate(update.getUpdate(), client)
if (debug) System.out.println(" " + 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 if (db == null) return
if (debug) System.out.println("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(),
message.getMediaUnread(), message.getMediaUnread(),
message.getSilent(), message.getSilent(),
false, false,
message.getId(), message.getId(),
message.getFromId(), message.getFromId(),
TLPeerChat(message.getChatId()), TLPeerChat(message.getChatId()),
message.getFwdFrom(), message.getFwdFrom(),
message.getViaBotId(), message.getViaBotId(),
message.getReplyToMsgId(), message.getReplyToMsgId(),
message.getDate(), message.getDate(),
message.getMessage(), null, null, message.getMessage(), null, null,
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)
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 if (db == null) return
if (debug) System.out.println("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!!.user!!.getId()
to_id = m.getUserId() to_id = m.getUserId()
} else { } else {
to_id = user!!.user!!.getId() to_id = user!!.user!!.getId()
from_id = m.getUserId() from_id = m.getUserId()
} }
val msg = TLMessage( val msg = TLMessage(
m.getOut(), m.getOut(),
m.getMentioned(), m.getMentioned(),
m.getMediaUnread(), m.getMediaUnread(),
m.getSilent(), m.getSilent(),
false, false,
m.getId(), m.getId(),
from_id, from_id,
TLPeerUser(to_id), TLPeerUser(to_id),
m.getFwdFrom(), m.getFwdFrom(),
m.getViaBotId(), m.getViaBotId(),
m.getReplyToMsgId(), m.getReplyToMsgId(),
m.getDate(), m.getDate(),
m.getMessage(), null, null, m.getMessage(), null, null,
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)
System.out.print('.') System.out.print('.')
} }
override fun onShortSentMessage(client: TelegramClient, message: TLUpdateShortSentMessage) { override fun onShortSentMessage(client: TelegramClient, message: TLUpdateShortSentMessage) {
if (db == null) return if (db == null) return
System.out.println("onShortSentMessage") System.out.println("onShortSentMessage")
} }
override fun onUpdateTooLong(client: TelegramClient) { override fun onUpdateTooLong(client: TelegramClient) {
if (db == null) return if (db == null) return
System.out.println("onUpdateTooLong") System.out.println("onUpdateTooLong")
} }
private fun processUpdate(update: TLAbsUpdate, client: TelegramClient) { private fun processUpdate(update: TLAbsUpdate, client: TelegramClient) {
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)
System.out.print('.') System.out.print('.')
if (abs_msg is TLMessage) { if (abs_msg is TLMessage) {
val fm = FileManagerFactory.getFileManager(abs_msg, user!!, client) val fm = FileManagerFactory.getFileManager(abs_msg, user!!, client)
if (fm != null && !fm.isEmpty && !fm.downloaded) { if (fm != null && !fm.isEmpty && !fm.downloaded) {
try { try {
fm.download() fm.download()
} catch (e: Exception) { } catch (e: Exception) {
System.out.println("We got an exception while downloading media, but we're going to ignore it.") System.out.println("We got an exception while downloading media, but we're going to ignore it.")
System.out.println("Here it is anyway:") System.out.println("Here it is anyway:")
e.printStackTrace() e.printStackTrace()
} }
} }
} }
} else { } else {
// ignore everything else... // ignore everything else...
} }
} }
} }

View File

@ -11,55 +11,55 @@ import java.io.IOException
import java.nio.charset.Charset import java.nio.charset.Charset
internal object TestFeatures { internal object TestFeatures {
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 {
Class.forName("org.sqlite.JDBC") Class.forName("org.sqlite.JDBC")
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
CommandLineController.show_error("Could not load jdbc-sqlite class.") CommandLineController.show_error("Could not load jdbc-sqlite class.")
} }
val path = "jdbc:sqlite:cache4.db" val path = "jdbc:sqlite:cache4.db"
var conn: Connection var conn: Connection
var stmt: Statement? = null var stmt: Statement? = null
try { try {
conn = DriverManager.getConnection(path) conn = DriverManager.getConnection(path)
stmt = conn.createStatement() stmt = conn.createStatement()
} catch (e: SQLException) { } catch (e: SQLException) {
CommandLineController.show_error("Could not connect to SQLITE database.") CommandLineController.show_error("Could not connect to SQLITE database.")
} }
var unsupported_constructor = 0 var unsupported_constructor = 0
var success = 0 var success = 0
try { 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))
} catch (e: com.github.badoualy.telegram.tl.exception.UnsupportedConstructorException) { } catch (e: com.github.badoualy.telegram.tl.exception.UnsupportedConstructorException) {
unsupported_constructor++ unsupported_constructor++
} catch (e: IOException) { } catch (e: IOException) {
System.out.println("IOException: " + e) System.out.println("IOException: " + e)
} }
success++ success++
} }
} catch (e: SQLException) { } catch (e: SQLException) {
System.out.println("SQL exception: " + e) 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)
} }
fun test2() { fun test2() {
// 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() val db = Database.getInstance()
System.out.println("Database encoding: " + db.getEncoding()) System.out.println("Database encoding: " + db.getEncoding())
} }
} }

View File

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

View File

@ -28,156 +28,156 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
object Utils { object Utils {
@JvmField public val VERSIONS_EQUAL = 0 @JvmField public val VERSIONS_EQUAL = 0
@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
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 getAccounts(): Vector<String> {
val accounts = Vector<String>() val accounts = Vector<String>()
val folder = File(Config.FILE_BASE) val folder = File(Config.FILE_BASE)
val files = folder.listFiles() val files = folder.listFiles()
if (files != null) if (files != null)
for (f in files) { for (f in files) {
if (f.isDirectory() && f.getName().startsWith("+")) { if (f.isDirectory() && f.getName().startsWith("+")) {
accounts.add(f.getName()) accounts.add(f.getName())
} }
} }
return accounts return accounts
} }
fun getNewestVersion(): Version? { fun getNewestVersion(): Version? {
try { try {
val data_url = "https://api.github.com/repos/fabianonline/telegram_backup/releases" val data_url = "https://api.github.com/repos/fabianonline/telegram_backup/releases"
logger.debug("Requesting current release info from {}", data_url) logger.debug("Requesting current release info from {}", data_url)
val json = IOUtils.toString(URL(data_url)) val json = IOUtils.toString(URL(data_url))
val parser = JsonParser() val parser = JsonParser()
val root_elm = parser.parse(json) val root_elm = parser.parse(json)
if (root_elm.isJsonArray()) { if (root_elm.isJsonArray()) {
val root = root_elm.getAsJsonArray() val root = root_elm.getAsJsonArray()
var newest_version: JsonObject? = null var newest_version: JsonObject? = null
for (e in root) for (e in root)
if (e.isJsonObject()) { if (e.isJsonObject()) {
val version = e.getAsJsonObject() val version = e.getAsJsonObject()
if (version.getAsJsonPrimitive("prerelease").getAsBoolean() == false) { if (version.getAsJsonPrimitive("prerelease").getAsBoolean() == false) {
newest_version = version newest_version = version
break break
} }
} }
if (newest_version == null) return null if (newest_version == null) return null
val new_v = newest_version.getAsJsonPrimitive("tag_name").getAsString() val new_v = newest_version.getAsJsonPrimitive("tag_name").getAsString()
logger.debug("Found current release version {}", new_v) logger.debug("Found current release version {}", new_v)
val cur_v = Config.APP_APPVER val cur_v = Config.APP_APPVER
val result = compareVersions(cur_v, new_v) val result = compareVersions(cur_v, new_v)
return Version(new_v, newest_version.getAsJsonPrimitive("html_url").getAsString(), newest_version.getAsJsonPrimitive("body").getAsString(), result == VERSION_2_NEWER) return Version(new_v, newest_version.getAsJsonPrimitive("html_url").getAsString(), newest_version.getAsJsonPrimitive("body").getAsString(), result == VERSION_2_NEWER)
} }
return null return null
} catch (e: Exception) { } catch (e: Exception) {
return null return null
} }
} }
@Throws(RpcErrorException::class) @Throws(RpcErrorException::class)
@JvmOverloads internal fun obeyFloodWaitException(e: RpcErrorException?, silent: Boolean = false) { @JvmOverloads internal fun obeyFloodWaitException(e: RpcErrorException?, silent: Boolean = false) {
if (e == null || e.getCode() != 420) return if (e == null || e.getCode() != 420) return
val delay: Long = e.getTagInteger()!!.toLong() val delay: Long = e.getTagInteger()!!.toLong()
if (!silent) { if (!silent) {
System.out.println("") System.out.println("")
System.out.println( System.out.println(
"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("") System.out.println("")
} }
try { try {
TimeUnit.SECONDS.sleep(delay + 1) TimeUnit.SECONDS.sleep(delay + 1)
} catch (e2: InterruptedException) { } catch (e2: InterruptedException) {
} }
} }
@JvmStatic @JvmStatic
fun compareVersions(v1: String, v2: String): Int { fun compareVersions(v1: String, v2: String): Int {
logger.debug("Comparing versions {} and {}.", v1, v2) logger.debug("Comparing versions {} and {}.", v1, v2)
if (v1.equals(v2)) return VERSIONS_EQUAL if (v1.equals(v2)) return VERSIONS_EQUAL
val v1_p = v1.split("-", limit=2) val v1_p = v1.split("-", limit = 2)
val v2_p = v2.split("-", limit=2) val v2_p = v2.split("-", limit = 2)
logger.trace("Parts to compare without suffixes: {} and {}.", v1_p[0], v2_p[0]) logger.trace("Parts to compare without suffixes: {} and {}.", v1_p[0], v2_p[0])
val v1_p2 = v1_p[0].split(".") val v1_p2 = v1_p[0].split(".")
val v2_p2 = v2_p[0].split(".") val v2_p2 = v2_p[0].split(".")
logger.trace("Length of the parts without suffixes: {} and {}.", v1_p2.size, v2_p2.size) logger.trace("Length of the parts without suffixes: {} and {}.", v1_p2.size, v2_p2.size)
var i: Int var i: Int
i = 0 i = 0
while (i < v1_p2.size && i < v2_p2.size) { while (i < v1_p2.size && i < v2_p2.size) {
val i_1 = Integer.parseInt(v1_p2[i]) val i_1 = Integer.parseInt(v1_p2[i])
val i_2 = Integer.parseInt(v2_p2[i]) val i_2 = Integer.parseInt(v2_p2[i])
logger.trace("Comparing parts: {} and {}.", i_1, i_2) logger.trace("Comparing parts: {} and {}.", i_1, i_2)
if (i_1 > i_2) { if (i_1 > i_2) {
logger.debug("v1 is newer") logger.debug("v1 is newer")
return VERSION_1_NEWER return VERSION_1_NEWER
} else if (i_2 > i_1) { } else if (i_2 > i_1) {
logger.debug("v2 is newer") logger.debug("v2 is newer")
return VERSION_2_NEWER return VERSION_2_NEWER
} }
i++ i++
} }
logger.trace("At least one of the versions has run out of parts.") logger.trace("At least one of the versions has run out of parts.")
if (v1_p2.size > v2_p2.size) { if (v1_p2.size > v2_p2.size) {
logger.debug("v1 is longer, so it is newer") logger.debug("v1 is longer, so it is newer")
return VERSION_1_NEWER return VERSION_1_NEWER
} else if (v2_p2.size > v1_p2.size) { } else if (v2_p2.size > v1_p2.size) {
logger.debug("v2 is longer, so it is newer") logger.debug("v2 is longer, so it is newer")
return VERSION_2_NEWER return VERSION_2_NEWER
} }
// startsWith // startsWith
if (v1_p.size > 1 && v2_p.size == 1) { if (v1_p.size > 1 && v2_p.size == 1) {
logger.debug("v1 has a suffix, v2 not.") logger.debug("v1 has a suffix, v2 not.")
if (v1_p[1].startsWith("pre")) { if (v1_p[1].startsWith("pre")) {
logger.debug("v1 is a pre version, so v1 is newer") logger.debug("v1 is a pre version, so v1 is newer")
return VERSION_2_NEWER return VERSION_2_NEWER
} else { } else {
return VERSION_1_NEWER return VERSION_1_NEWER
} }
} else if (v1_p.size == 1 && v2_p.size > 1) { } else if (v1_p.size == 1 && v2_p.size > 1) {
logger.debug("v1 has no suffix, but v2 has") logger.debug("v1 has no suffix, but v2 has")
if (v2_p[1].startsWith("pre")) { if (v2_p[1].startsWith("pre")) {
logger.debug("v2 is a pre version, so v1 is better") logger.debug("v2 is a pre version, so v1 is better")
return VERSION_1_NEWER return VERSION_1_NEWER
} else { } else {
return VERSION_2_NEWER return VERSION_2_NEWER
} }
} else if (v1_p.size > 1 && v2_p.size > 1) { } else if (v1_p.size > 1 && v2_p.size > 1) {
logger.debug("Both have a suffix") logger.debug("Both have a suffix")
if (v1_p[1].startsWith("pre") && !v2_p[1].startsWith("pre")) { if (v1_p[1].startsWith("pre") && !v2_p[1].startsWith("pre")) {
logger.debug("v1 is a 'pre' version, v2 not.") logger.debug("v1 is a 'pre' version, v2 not.")
return VERSION_2_NEWER return VERSION_2_NEWER
} else if (!v1_p[1].startsWith("pre") && v2_p[1].startsWith("pre")) { } else if (!v1_p[1].startsWith("pre") && v2_p[1].startsWith("pre")) {
logger.debug("v2 is a 'pre' version, v2 not.") logger.debug("v2 is a 'pre' version, v2 not.")
return VERSION_1_NEWER return VERSION_1_NEWER
} }
return VERSIONS_EQUAL return VERSIONS_EQUAL
} }
logger.debug("We couldn't find a real difference, so we're assuming the versions are equal-ish.") logger.debug("We couldn't find a real difference, so we're assuming the versions are equal-ish.")
return VERSIONS_EQUAL return VERSIONS_EQUAL
} }
fun anonymize(str: String): String { fun anonymize(str: String): String {
return if (!CommandLineOptions.cmd_anonymize) str else str.replace("[0-9]".toRegex(), "1").replace("[A-Z]".toRegex(), "A").replace("[a-z]".toRegex(), "a") + " (ANONYMIZED)" return if (!CommandLineOptions.cmd_anonymize) str else str.replace("[0-9]".toRegex(), "1").replace("[A-Z]".toRegex(), "A").replace("[a-z]".toRegex(), "a") + " (ANONYMIZED)"
} }
} }

View File

@ -42,145 +42,145 @@ import org.slf4j.LoggerFactory
class HTMLExporter { class HTMLExporter {
@Throws(IOException::class) @Throws(IOException::class)
fun export() { fun export() {
try { try {
val user = UserManager.getInstance() val user = UserManager.getInstance()
val db = Database.getInstance() val db = Database.getInstance()
// 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 = user.fileBase + "files" + File.separatorChar
File(base).mkdirs() File(base).mkdirs()
File(base + "dialogs").mkdirs() File(base + "dialogs").mkdirs()
logger.debug("Fetching dialogs") logger.debug("Fetching dialogs")
val dialogs = db.getListOfDialogsForExport() val dialogs = db.getListOfDialogsForExport()
logger.trace("Got {} dialogs", dialogs.size) logger.trace("Got {} dialogs", dialogs.size)
logger.debug("Fetching chats") logger.debug("Fetching chats")
val chats = db.getListOfChatsForExport() val chats = db.getListOfChatsForExport()
logger.trace("Got {} chats", chats.size) logger.trace("Got {} chats", chats.size)
logger.debug("Generating index.html") logger.debug("Generating index.html")
val scope = HashMap<String, Any>() val scope = HashMap<String, Any>()
scope.put("user", user) scope.put("user", user)
scope.put("dialogs", dialogs) scope.put("dialogs", dialogs)
scope.put("chats", chats) scope.put("chats", chats)
// Collect stats data // Collect stats data
scope.put("count.chats", chats.size) scope.put("count.chats", chats.size)
scope.put("count.dialogs", dialogs.size) scope.put("count.dialogs", dialogs.size)
var count_messages_chats = 0 var count_messages_chats = 0
var count_messages_dialogs = 0 var count_messages_dialogs = 0
for (c in chats) count_messages_chats += c.count ?: 0 for (c in chats) count_messages_chats += c.count ?: 0
for (d in dialogs) count_messages_dialogs += d.count ?: 0 for (d in dialogs) count_messages_dialogs += d.count ?: 0
scope.put("count.messages", count_messages_chats + count_messages_dialogs) scope.put("count.messages", count_messages_chats + count_messages_dialogs)
scope.put("count.messages.chats", count_messages_chats) scope.put("count.messages.chats", count_messages_chats)
scope.put("count.messages.dialogs", count_messages_dialogs) scope.put("count.messages.dialogs", count_messages_dialogs)
scope.put("count.messages.from_me", db.getMessagesFromUserCount()) scope.put("count.messages.from_me", db.getMessagesFromUserCount())
scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix())) scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix()))
scope.putAll(db.getMessageAuthorsWithCount()) scope.putAll(db.getMessageAuthorsWithCount())
scope.putAll(db.getMessageTypesWithCount()) scope.putAll(db.getMessageTypesWithCount())
scope.putAll(db.getMessageMediaTypesWithCount()) scope.putAll(db.getMessageMediaTypesWithCount())
val mf = DefaultMustacheFactory() val mf = DefaultMustacheFactory()
var mustache = mf.compile("templates/html/index.mustache") var mustache = mf.compile("templates/html/index.mustache")
var w = getWriter(base + "index.html") var w = getWriter(base + "index.html")
mustache.execute(w, scope) mustache.execute(w, scope)
w.close() w.close()
mustache = mf.compile("templates/html/chat.mustache") mustache = mf.compile("templates/html/chat.mustache")
var i = 0 var i = 0
logger.debug("Generating {} dialog pages", dialogs.size) logger.debug("Generating {} dialog pages", dialogs.size)
for (d in dialogs) { for (d in dialogs) {
i++ i++
logger.trace("Dialog {}/{}: {}", i, dialogs.size, Utils.anonymize("" + d.id)) logger.trace("Dialog {}/{}: {}", i, dialogs.size, Utils.anonymize("" + d.id))
val messages = db.getMessagesForExport(d) val messages = db.getMessagesForExport(d)
scope.clear() scope.clear()
scope.put("user", user) scope.put("user", user)
scope.put("dialog", d) scope.put("dialog", d)
scope.put("messages", messages) scope.put("messages", messages)
scope.putAll(db.getMessageAuthorsWithCount(d)) scope.putAll(db.getMessageAuthorsWithCount(d))
scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(d))) scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(d)))
scope.putAll(db.getMessageTypesWithCount(d)) scope.putAll(db.getMessageTypesWithCount(d))
scope.putAll(db.getMessageMediaTypesWithCount(d)) scope.putAll(db.getMessageMediaTypesWithCount(d))
w = getWriter(base + "dialogs" + File.separatorChar + "user_" + d.id + ".html") w = getWriter(base + "dialogs" + File.separatorChar + "user_" + d.id + ".html")
mustache.execute(w, scope) mustache.execute(w, scope)
w.close() w.close()
} }
i = 0 i = 0
logger.debug("Generating {} chat pages", chats.size) logger.debug("Generating {} chat pages", chats.size)
for (c in chats) { for (c in chats) {
i++ i++
logger.trace("Chat {}/{}: {}", i, chats.size, Utils.anonymize("" + c.id)) logger.trace("Chat {}/{}: {}", i, chats.size, Utils.anonymize("" + c.id))
val messages = db.getMessagesForExport(c) val messages = db.getMessagesForExport(c)
scope.clear() scope.clear()
scope.put("user", user) scope.put("user", user)
scope.put("chat", c) scope.put("chat", c)
scope.put("messages", messages) scope.put("messages", messages)
scope.putAll(db.getMessageAuthorsWithCount(c)) scope.putAll(db.getMessageAuthorsWithCount(c))
scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(c))) scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(c)))
scope.putAll(db.getMessageTypesWithCount(c)) scope.putAll(db.getMessageTypesWithCount(c))
scope.putAll(db.getMessageMediaTypesWithCount(c)) scope.putAll(db.getMessageMediaTypesWithCount(c))
w = getWriter(base + "dialogs" + File.separatorChar + "chat_" + c.id + ".html") w = getWriter(base + "dialogs" + File.separatorChar + "chat_" + c.id + ".html")
mustache.execute(w, scope) mustache.execute(w, scope)
w.close() w.close()
} }
logger.debug("Generating additional files") logger.debug("Generating additional files")
// Copy CSS // Copy CSS
val cssFile = javaClass.getResource("/templates/html/style.css") val cssFile = javaClass.getResource("/templates/html/style.css")
val dest = File(base + "style.css") val dest = File(base + "style.css")
FileUtils.copyURLToFile(cssFile, dest) FileUtils.copyURLToFile(cssFile, dest)
logger.debug("Done exporting.") logger.debug("Done exporting.")
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
logger.error("Caught an exception!", e) logger.error("Caught an exception!", e)
throw e throw e
} }
} }
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
private fun getWriter(filename: String): OutputStreamWriter { private fun getWriter(filename: String): OutputStreamWriter {
logger.trace("Creating writer for file {}", Utils.anonymize(filename)) logger.trace("Creating writer for file {}", Utils.anonymize(filename))
return OutputStreamWriter(FileOutputStream(filename), Charset.forName("UTF-8").newEncoder()) return OutputStreamWriter(FileOutputStream(filename), Charset.forName("UTF-8").newEncoder())
} }
private fun intArrayToString(data: Array<IntArray>): String { private fun intArrayToString(data: Array<IntArray>): String {
val sb = StringBuilder() val sb = StringBuilder()
sb.append("[") sb.append("[")
for (x in data.indices) { for (x in data.indices) {
for (y in 0 until data[x].size) { for (y in 0 until data[x].size) {
if (x > 0 || y > 0) sb.append(",") if (x > 0 || y > 0) sb.append(",")
sb.append("[" + x + "," + y + "," + data[x][y] + "]") sb.append("[" + x + "," + y + "," + data[x][y] + "]")
} }
} }
sb.append("]") sb.append("]")
return sb.toString() return sb.toString()
} }
private fun mapToString(map: Map<String, Int>): String { private fun mapToString(map: Map<String, Int>): String {
val sb = StringBuilder("[") val sb = StringBuilder("[")
for ((key, value) in map) { for ((key, value) in map) {
sb.append("['$key', $value],") sb.append("['$key', $value],")
} }
sb.append("]") sb.append("]")
return sb.toString() return sb.toString()
} }
companion object { companion object {
private val logger = LoggerFactory.getLogger(HTMLExporter::class.java) private val logger = LoggerFactory.getLogger(HTMLExporter::class.java)
} }
} }

View File

@ -44,25 +44,25 @@ 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, 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
open val downloaded: Boolean
get() = File(targetPathAndFilename).isFile()
val downloading: Boolean
get() = File("${targetPathAndFilename}.downloading").isFile()
open val downloaded: Boolean
get() = File(targetPathAndFilename).isFile()
val downloading: Boolean
get() = File("${targetPathAndFilename}.downloading").isFile()
open val targetPath: String open val targetPath: String
get() { get() {
val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar val path = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar
File(path).mkdirs() File(path).mkdirs()
return path return path
} }
open val targetFilename: String open val targetFilename: String
get() { get() {
val message_id = message.getId() val message_id = message.getId()
var to = message.getToId() var to = message.getToId()
if (to is TLPeerChannel) { if (to is TLPeerChannel) {
@ -71,30 +71,30 @@ abstract class AbstractMediaFileManager(protected var message: TLMessage, protec
} else return "${message_id}.$extension" } else return "${message_id}.$extension"
} }
open val targetPathAndFilename: String open val targetPathAndFilename: String
get() = targetPath + targetFilename get() = targetPath + targetFilename
abstract val letter: String abstract val letter: String
abstract val name: String abstract val name: String
abstract val description: String abstract val description: String
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
abstract fun download() abstract fun download()
protected fun extensionFromMimetype(mime: String): String { protected fun extensionFromMimetype(mime: String): String {
when (mime) { when (mime) {
"text/plain" -> return "txt" "text/plain" -> return "txt"
} }
val i = mime.lastIndexOf('/') val i = mime.lastIndexOf('/')
val ext = mime.substring(i + 1).toLowerCase() val ext = mime.substring(i + 1).toLowerCase()
return if (ext === "unknown") "dat" else ext return if (ext === "unknown") "dat" else ext
} }
companion object { companion object {
fun throwUnexpectedObjectError(o: Any) { fun throwUnexpectedObjectError(o: Any) {
throw RuntimeException("Unexpected " + o.javaClass.getName()) throw RuntimeException("Unexpected " + o.javaClass.getName())
} }
} }
} }

View File

@ -43,63 +43,63 @@ 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, 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
open val isSticker: Boolean open val isSticker: Boolean
get() { get() {
if (this.isEmpty || doc == null) return false if (this.isEmpty || doc == null) return false
return doc!!.getAttributes()?.filter{it is TLDocumentAttributeSticker}?.isNotEmpty() ?: false return doc!!.getAttributes()?.filter { it is TLDocumentAttributeSticker }?.isNotEmpty() ?: false
} }
override val size: Int override val size: Int
get() = if (doc != null) doc!!.getSize() else 0 get() = if (doc != null) doc!!.getSize() else 0
open override val letter: String = "d" open override val letter: String = "d"
open override val name: String = "document" open override val name: String = "document"
open override val description: String = "Document" open override val description: String = "Document"
init { init {
val d = (msg.getMedia() as TLMessageMediaDocument).getDocument() val d = (msg.getMedia() as TLMessageMediaDocument).getDocument()
if (d is TLDocument) { if (d is TLDocument) {
this.doc = d this.doc = d
} else if (d is TLDocumentEmpty) { } else if (d is TLDocumentEmpty) {
this.isEmpty = true this.isEmpty = true
} else { } else {
throwUnexpectedObjectError(d) throwUnexpectedObjectError(d)
} }
extension = processExtension() extension = processExtension()
} }
private fun processExtension(): String { private fun processExtension(): String {
if (doc == null) return "empty" if (doc == null) return "empty"
var ext: String? = null var ext: String? = null
var original_filename: String? = null var original_filename: String? = null
if (doc!!.getAttributes() != null) if (doc!!.getAttributes() != null)
for (attr in doc!!.getAttributes()) { for (attr in doc!!.getAttributes()) {
if (attr is TLDocumentAttributeFilename) { if (attr is TLDocumentAttributeFilename) {
original_filename = attr.getFileName() original_filename = attr.getFileName()
} }
} }
if (original_filename != null) { if (original_filename != null) {
val i = original_filename.lastIndexOf('.') val i = original_filename.lastIndexOf('.')
if (i > 0) ext = original_filename.substring(i + 1) if (i > 0) ext = original_filename.substring(i + 1)
} }
if (ext == null) { if (ext == null) {
ext = extensionFromMimetype(doc!!.getMimeType()) ext = extensionFromMimetype(doc!!.getMimeType())
} }
// Sometimes, extensions contain a trailing double quote. Remove this. Fixes #12. // Sometimes, extensions contain a trailing double quote. Remove this. Fixes #12.
ext = ext.replace("\"", "") ext = ext.replace("\"", "")
return ext return ext
} }
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
override fun download() { override fun download() {
if (doc != null) { if (doc != null) {
DownloadManager.downloadFile(targetPathAndFilename, size, doc!!.getDcId(), doc!!.getId(), doc!!.getAccessHash()) DownloadManager.downloadFile(targetPathAndFilename, size, doc!!.getDcId(), doc!!.getId(), doc!!.getAccessHash())
} }
} }
} }

View File

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

View File

@ -44,39 +44,39 @@ 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, 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.
override val size: Int override val size: Int
get() { get() {
val f = File(targetPathAndFilename) val f = File(targetPathAndFilename)
return if (f.isFile()) f.length().toInt() else 100000 return if (f.isFile()) f.length().toInt() else 100000
} }
override val extension: String override val extension: String
get() = "png" get() = "png"
override val letter = "g" override val letter = "g"
override val name = "geo" override val name = "geo"
override val description = "Geolocation" override val description = "Geolocation"
init { init {
val g = (msg.getMedia() as TLMessageMediaGeo).getGeo() val g = (msg.getMedia() as TLMessageMediaGeo).getGeo()
if (g is TLGeoPoint) { if (g is TLGeoPoint) {
this.geo = g this.geo = g
} else if (g is TLGeoPointEmpty) { } else if (g is TLGeoPointEmpty) {
this.isEmpty = true this.isEmpty = true
} else { } else {
throwUnexpectedObjectError(g) throwUnexpectedObjectError(g)
} }
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun download() { override fun download() {
val url = "https://maps.googleapis.com/maps/api/staticmap?" + val url = "https://maps.googleapis.com/maps/api/staticmap?" +
"center=" + geo.getLat() + "," + geo.getLong() + "&" + "center=" + geo.getLat() + "," + geo.getLong() + "&" +
"zoom=14&size=300x150&scale=2&format=png&" + "zoom=14&size=300x150&scale=2&format=png&" +
"key=" + Config.SECRET_GMAPS "key=" + Config.SECRET_GMAPS
DownloadManager.downloadExternalFile(targetPathAndFilename, url) DownloadManager.downloadExternalFile(targetPathAndFilename, url)
} }
} }

View File

@ -43,43 +43,43 @@ 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, 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
override val extension = "jpg" override val extension = "jpg"
override val letter = "p" override val letter = "p"
override val name = "photo" override val name = "photo"
override val description = "Photo" override val description = "Photo"
init { init {
val p = (msg.getMedia() as TLMessageMediaPhoto).getPhoto() val p = (msg.getMedia() as TLMessageMediaPhoto).getPhoto()
if (p is TLPhoto) { if (p is TLPhoto) {
this.photo = p this.photo = p
var biggest: TLPhotoSize? = null var biggest: TLPhotoSize? = null
for (s in photo.getSizes()) for (s in photo.getSizes())
if (s is TLPhotoSize) { if (s is TLPhotoSize) {
if (biggest == null || s.getW() > biggest.getW() && s.getH() > biggest.getH()) { if (biggest == null || s.getW() > biggest.getW() && s.getH() > biggest.getH()) {
biggest = s biggest = s
} }
} }
if (biggest == null) { if (biggest == null) {
throw RuntimeException("Could not find a size for a photo.") throw RuntimeException("Could not find a size for a photo.")
} }
this.photo_size = biggest this.photo_size = biggest
this.size = biggest.getSize() this.size = biggest.getSize()
} else if (p is TLPhotoEmpty) { } else if (p is TLPhotoEmpty) {
this.isEmpty = true this.isEmpty = true
} else { } else {
throwUnexpectedObjectError(p) throwUnexpectedObjectError(p)
} }
} }
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
override fun download() { override fun download() {
if (isEmpty) return if (isEmpty) return
val loc = photo_size.getLocation() as TLFileLocation val loc = photo_size.getLocation() as TLFileLocation
DownloadManager.downloadFile(targetPathAndFilename, size, loc.getDcId(), loc.getVolumeId(), loc.getLocalId(), loc.getSecret()) DownloadManager.downloadFile(targetPathAndFilename, size, loc.getDcId(), loc.getVolumeId(), loc.getLocalId(), loc.getSecret())
} }
} }

View File

@ -52,62 +52,62 @@ import org.apache.commons.io.FileUtils
class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : DocumentFileManager(msg, user, client) { class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : DocumentFileManager(msg, user, client) {
override val isSticker = true override val isSticker = true
private val filenameBase: String private val filenameBase: String
get() { get() {
var sticker: TLDocumentAttributeSticker? = null var sticker: TLDocumentAttributeSticker? = null
for (attr in doc!!.getAttributes()) { for (attr in doc!!.getAttributes()) {
if (attr is TLDocumentAttributeSticker) { if (attr is TLDocumentAttributeSticker) {
sticker = attr sticker = attr
} }
} }
val file = StringBuilder() val file = StringBuilder()
val set = sticker!!.getStickerset() val set = sticker!!.getStickerset()
if (set is TLInputStickerSetShortName) { if (set is TLInputStickerSetShortName) {
file.append(set.getShortName()) file.append(set.getShortName())
} else if (set is TLInputStickerSetID) { } else if (set is TLInputStickerSetID) {
file.append(set.getId()) file.append(set.getId())
} }
file.append("_") file.append("_")
file.append(sticker.getAlt().hashCode()) file.append(sticker.getAlt().hashCode())
return file.toString() return file.toString()
} }
override val targetFilename: String override val targetFilename: String
get() = filenameBase + "." + extension get() = filenameBase + "." + extension
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 = user.fileBase + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar
File(path).mkdirs() File(path).mkdirs()
return path return path
} }
override var extension = "webp" override var extension = "webp"
override val letter: String override val letter: String
get() = "s" get() = "s"
override val name: String override val name: String
get() = "sticker" get() = "sticker"
override val description: String override val description: String
get() = "Sticker" get() = "Sticker"
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class) @Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
override fun download() { override fun download() {
val old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + targetFilename val old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + targetFilename
logger.trace("Old filename exists: {}", File(old_file).exists()) logger.trace("Old filename exists: {}", File(old_file).exists())
if (File(old_file).exists()) { if (File(old_file).exists()) {
Files.copy(Paths.get(old_file), Paths.get(targetPathAndFilename), StandardCopyOption.REPLACE_EXISTING) Files.copy(Paths.get(old_file), Paths.get(targetPathAndFilename), StandardCopyOption.REPLACE_EXISTING)
return return
} }
super.download() super.download()
} }
companion object { companion object {
private val logger = LoggerFactory.getLogger(StickerFileManager::class.java) private val logger = LoggerFactory.getLogger(StickerFileManager::class.java)
} }
} }

View File

@ -44,15 +44,15 @@ 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, 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 = ""
override val extension = "" override val extension = ""
override val size = 0 override val size = 0
override var isEmpty = false override var isEmpty = false
override val downloaded = false override val downloaded = false
override val letter = " " override val letter = " "
override val description = "Unsupported / non-downloadable Media" override val description = "Unsupported / non-downloadable Media"
override fun download() {} override fun download() {}
} }