mirror of
https://github.com/fabianonline/telegram_backup.git
synced 2025-07-01 12:56:25 +00:00
Complete conversion.
This commit is contained in:
147
src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt
Normal file
147
src/main/kotlin/de/fabianonline/telegram_backup/ApiStorage.kt
Normal file
@ -0,0 +1,147 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramApiStorage
|
||||
import com.github.badoualy.telegram.mtproto.model.DataCenter
|
||||
import com.github.badoualy.telegram.mtproto.auth.AuthKey
|
||||
import com.github.badoualy.telegram.mtproto.model.MTSession
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
internal class ApiStorage(prefix: String) : TelegramApiStorage {
|
||||
private var prefix: String? = null
|
||||
private var do_save = false
|
||||
private var auth_key: AuthKey? = null
|
||||
private var dc: DataCenter? = null
|
||||
private var file_auth_key: File? = null
|
||||
private var file_dc: File? = null
|
||||
|
||||
init {
|
||||
this.setPrefix(prefix)
|
||||
}
|
||||
|
||||
fun setPrefix(prefix: String) {
|
||||
this.prefix = prefix
|
||||
this.do_save = this.prefix != null
|
||||
if (this.do_save) {
|
||||
val base = Config.FILE_BASE +
|
||||
File.separatorChar +
|
||||
this.prefix +
|
||||
File.separatorChar
|
||||
this.file_auth_key = File(base + Config.FILE_NAME_AUTH_KEY)
|
||||
this.file_dc = File(base + Config.FILE_NAME_DC)
|
||||
this._saveAuthKey()
|
||||
this._saveDc()
|
||||
} else {
|
||||
this.file_auth_key = null
|
||||
this.file_dc = null
|
||||
}
|
||||
}
|
||||
|
||||
fun saveAuthKey(authKey: AuthKey) {
|
||||
this.auth_key = authKey
|
||||
this._saveAuthKey()
|
||||
}
|
||||
|
||||
private fun _saveAuthKey() {
|
||||
if (this.do_save && this.auth_key != null) {
|
||||
try {
|
||||
FileUtils.writeByteArrayToFile(this.file_auth_key, this.auth_key!!.getKey())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun loadAuthKey(): AuthKey? {
|
||||
if (this.auth_key != null) return this.auth_key
|
||||
if (this.file_auth_key != null) {
|
||||
try {
|
||||
return AuthKey(FileUtils.readFileToByteArray(this.file_auth_key))
|
||||
} catch (e: IOException) {
|
||||
if (e !is FileNotFoundException) e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun saveDc(dc: DataCenter) {
|
||||
this.dc = dc
|
||||
this._saveDc()
|
||||
}
|
||||
|
||||
private fun _saveDc() {
|
||||
if (this.do_save && this.dc != null) {
|
||||
try {
|
||||
FileUtils.write(this.file_dc, this.dc!!.toString())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDc(): DataCenter? {
|
||||
if (this.dc != null) return this.dc
|
||||
if (this.file_dc != null) {
|
||||
try {
|
||||
val infos = FileUtils.readFileToString(this.file_dc).split(":")
|
||||
return DataCenter(infos[0], Integer.parseInt(infos[1]))
|
||||
} catch (e: IOException) {
|
||||
if (e !is FileNotFoundException) e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun deleteAuthKey() {
|
||||
if (this.do_save) {
|
||||
try {
|
||||
FileUtils.forceDelete(this.file_auth_key)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteDc() {
|
||||
if (this.do_save) {
|
||||
try {
|
||||
FileUtils.forceDelete(this.file_dc)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun saveSession(session: MTSession) {}
|
||||
|
||||
fun loadSession(): MTSession? {
|
||||
return null
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,87 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||
|
||||
internal class CommandLineDownloadProgress : DownloadProgressInterface {
|
||||
private var mediaCount = 0
|
||||
private var i = 0
|
||||
|
||||
fun onMessageDownloadStart(count: Int, source: String?) {
|
||||
i = 0
|
||||
if (source == null) {
|
||||
System.out.println("Downloading $count messages.")
|
||||
} else {
|
||||
System.out.println("Downloading " + count + " messages from " + Utils.anonymize(source))
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageDownloaded(number: Int) {
|
||||
i += number
|
||||
System.out.print("..." + i)
|
||||
}
|
||||
|
||||
fun onMessageDownloadFinished() {
|
||||
System.out.println(" done.")
|
||||
}
|
||||
|
||||
fun onMediaDownloadStart(count: Int) {
|
||||
i = 0
|
||||
mediaCount = count
|
||||
System.out.println("Checking and downloading media.")
|
||||
System.out.println("Legend:")
|
||||
System.out.println("'V' - Video 'P' - Photo 'D' - Document")
|
||||
System.out.println("'S' - Sticker 'A' - Audio 'G' - Geolocation")
|
||||
System.out.println("'.' - Previously downloaded file 'e' - Empty file")
|
||||
System.out.println("' ' - Ignored media type (weblinks or contacts, for example)")
|
||||
System.out.println("'x' - File skipped because of timeout errors")
|
||||
System.out.println("" + count + " Files to check / download")
|
||||
}
|
||||
|
||||
fun onMediaDownloaded(fm: AbstractMediaFileManager) {
|
||||
show(fm.getLetter().toUpperCase())
|
||||
}
|
||||
|
||||
fun onMediaDownloadedEmpty() {
|
||||
show("e")
|
||||
}
|
||||
|
||||
fun onMediaAlreadyPresent(fm: AbstractMediaFileManager) {
|
||||
show(".")
|
||||
}
|
||||
|
||||
fun onMediaSkipped() {
|
||||
show("x")
|
||||
}
|
||||
|
||||
fun onMediaDownloadFinished() {
|
||||
showNewLine()
|
||||
System.out.println("Done.")
|
||||
}
|
||||
|
||||
private fun show(letter: String) {
|
||||
System.out.print(letter)
|
||||
i++
|
||||
if (i % 100 == 0) showNewLine()
|
||||
}
|
||||
|
||||
private fun showNewLine() {
|
||||
System.out.println(" - $i/$mediaCount")
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import de.fabianonline.telegram_backup.CommandLineController
|
||||
import de.fabianonline.telegram_backup.Utils
|
||||
import de.fabianonline.telegram_backup.Version
|
||||
import org.slf4j.LoggerFactory
|
||||
import ch.qos.logback.classic.Logger
|
||||
import ch.qos.logback.classic.LoggerContext
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||
import ch.qos.logback.core.ConsoleAppender
|
||||
import ch.qos.logback.classic.Level
|
||||
|
||||
object CommandLineRunner {
|
||||
fun main(args: Array<String>) {
|
||||
CommandLineOptions.parseOptions(args)
|
||||
|
||||
setupLogging()
|
||||
checkVersion()
|
||||
|
||||
|
||||
|
||||
if (true || CommandLineOptions.cmd_console) {
|
||||
// Always use the console for now.
|
||||
CommandLineController()
|
||||
} else {
|
||||
GUIController()
|
||||
}
|
||||
}
|
||||
|
||||
fun setupLogging() {
|
||||
val logger = LoggerFactory.getLogger(CommandLineRunner::class.java) as Logger
|
||||
val rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as Logger
|
||||
val rootContext = rootLogger.getLoggerContext()
|
||||
rootContext.reset()
|
||||
|
||||
val encoder = PatternLayoutEncoder()
|
||||
encoder.setContext(rootContext)
|
||||
encoder.setPattern("%d{HH:mm:ss.SSS} %-5level %-35.-35(%logger{0}.%method): %message%n")
|
||||
encoder.start()
|
||||
|
||||
val appender = ConsoleAppender<ILoggingEvent>()
|
||||
appender.setContext(rootContext)
|
||||
appender.setEncoder(encoder)
|
||||
appender.start()
|
||||
|
||||
rootLogger.addAppender(appender)
|
||||
rootLogger.setLevel(Level.OFF)
|
||||
|
||||
if (CommandLineOptions.cmd_trace) {
|
||||
(LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.TRACE)
|
||||
} else if (CommandLineOptions.cmd_debug) {
|
||||
(LoggerFactory.getLogger("de.fabianonline.telegram_backup") as Logger).setLevel(Level.DEBUG)
|
||||
}
|
||||
|
||||
if (CommandLineOptions.cmd_trace_telegram) {
|
||||
(LoggerFactory.getLogger("com.github.badoualy") as Logger).setLevel(Level.TRACE)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkVersion(): Boolean {
|
||||
val v = Utils.getNewestVersion()
|
||||
if (v != null && v!!.isNewer) {
|
||||
System.out.println("A newer version is vailable!")
|
||||
System.out.println("You are using: " + Config.APP_APPVER)
|
||||
System.out.println("Available: " + v!!.version)
|
||||
System.out.println("Get it here: " + v!!.url)
|
||||
System.out.println()
|
||||
System.out.println("Changes in this version:")
|
||||
System.out.println(v!!.body)
|
||||
System.out.println()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
60
src/main/kotlin/de/fabianonline/telegram_backup/Config.kt
Normal file
60
src/main/kotlin/de/fabianonline/telegram_backup/Config.kt
Normal file
@ -0,0 +1,60 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
object Config {
|
||||
val APP_ID = 32860
|
||||
val APP_HASH = "16e4ff955cd0adfc058f95ca564f562d"
|
||||
val APP_MODEL = "Desktop"
|
||||
val APP_SYSVER = "1.0"
|
||||
val APP_APPVER: String
|
||||
val APP_LANG = "en"
|
||||
|
||||
var FILE_BASE = System.getProperty("user.home") + File.separatorChar + ".telegram_backup"
|
||||
val FILE_NAME_AUTH_KEY = "auth.dat"
|
||||
val FILE_NAME_DC = "dc.dat"
|
||||
val FILE_NAME_DB = "database.sqlite"
|
||||
val FILE_NAME_DB_BACKUP = "database.version_%d.backup.sqlite"
|
||||
val FILE_FILES_BASE = "files"
|
||||
val FILE_STICKER_BASE = "stickers"
|
||||
|
||||
var DELAY_AFTER_GET_MESSAGES = 400
|
||||
var DELAY_AFTER_GET_FILE = 100
|
||||
var GET_MESSAGES_BATCH_SIZE = 200
|
||||
|
||||
var RENAMING_MAX_TRIES = 5
|
||||
var RENAMING_DELAY = 1000
|
||||
|
||||
val SECRET_GMAPS = "AIzaSyBEtUDhCQKEH6i2Mn1GAiQ9M_tLN0vxHIs"
|
||||
|
||||
init {
|
||||
val p = Properties()
|
||||
try {
|
||||
p.load(Config::class.java!!.getResourceAsStream("/build.properties"))
|
||||
APP_APPVER = p.getProperty("version")
|
||||
} catch (e: IOException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
739
src/main/kotlin/de/fabianonline/telegram_backup/Database.kt
Normal file
739
src/main/kotlin/de/fabianonline/telegram_backup/Database.kt
Normal file
@ -0,0 +1,739 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.core.TLVector
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.Logger
|
||||
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
import java.sql.Statement
|
||||
import java.sql.SQLException
|
||||
import java.sql.ResultSet
|
||||
import java.sql.ResultSetMetaData
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.Types
|
||||
import java.sql.Time
|
||||
import java.io.File
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.LinkedList
|
||||
import java.util.LinkedHashMap
|
||||
import java.util.HashMap
|
||||
import java.util.Date
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||
|
||||
class Database private constructor(var client: TelegramClient) {
|
||||
private var conn: Connection? = null
|
||||
private var stmt: Statement? = null
|
||||
var user_manager: UserManager
|
||||
|
||||
val topMessageID: Int
|
||||
get() {
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT MAX(message_id) FROM messages WHERE source_type IN ('group', 'dialog')")
|
||||
rs.next()
|
||||
return rs.getInt(1)
|
||||
} catch (e: SQLException) {
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val messageCount: Int
|
||||
get() = queryInt("SELECT COUNT(*) FROM messages")
|
||||
val chatCount: Int
|
||||
get() = queryInt("SELECT COUNT(*) FROM chats")
|
||||
val userCount: Int
|
||||
get() = queryInt("SELECT COUNT(*) FROM users")
|
||||
|
||||
val missingIDs: LinkedList<Integer>
|
||||
get() {
|
||||
try {
|
||||
val missing = LinkedList<Integer>()
|
||||
val max = topMessageID
|
||||
val rs = stmt!!.executeQuery("SELECT message_id FROM messages WHERE source_type IN ('group', 'dialog') ORDER BY id")
|
||||
rs.next()
|
||||
var id = rs.getInt(1)
|
||||
for (i in 1..max) {
|
||||
if (i == id) {
|
||||
rs.next()
|
||||
if (rs.isClosed()) {
|
||||
id = Integer.MAX_VALUE
|
||||
} else {
|
||||
id = rs.getInt(1)
|
||||
}
|
||||
} else if (i < id) {
|
||||
missing.add(i)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
} catch (e: SQLException) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Could not get list of ids.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val messagesWithMedia: LinkedList<TLMessage>
|
||||
get() {
|
||||
try {
|
||||
val list = LinkedList<TLMessage>()
|
||||
val rs = stmt!!.executeQuery("SELECT data FROM messages WHERE has_media=1")
|
||||
while (rs.next()) {
|
||||
list.add(bytesToTLMessage(rs.getBytes(1)))
|
||||
}
|
||||
rs.close()
|
||||
return list
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Exception occured. See above.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val messagesFromUserCount: Int
|
||||
get() {
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT COUNT(*) FROM messages WHERE sender_id=" + user_manager.getUser().getId())
|
||||
rs.next()
|
||||
return rs.getInt(1)
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val messageTypesWithCount: HashMap<String, Integer>
|
||||
get() = getMessageTypesWithCount(GlobalChat())
|
||||
|
||||
val messageMediaTypesWithCount: HashMap<String, Integer>
|
||||
get() = getMessageMediaTypesWithCount(GlobalChat())
|
||||
|
||||
val messageApiLayerWithCount: HashMap<String, Integer>
|
||||
get() {
|
||||
val map = HashMap<String, Integer>()
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT COUNT(id), api_layer FROM messages GROUP BY api_layer ORDER BY api_layer")
|
||||
while (rs.next()) {
|
||||
var layer = rs.getInt(2)
|
||||
if (layer == null) layer = 0
|
||||
map.put("count.messages.api_layer." + layer!!, rs.getInt(1))
|
||||
}
|
||||
rs.close()
|
||||
return map
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val messageAuthorsWithCount: HashMap<String, Object>
|
||||
get() = getMessageAuthorsWithCount(GlobalChat())
|
||||
|
||||
val messageTimesMatrix: Array<IntArray>
|
||||
get() = getMessageTimesMatrix(GlobalChat())
|
||||
|
||||
val encoding: String
|
||||
get() {
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("PRAGMA encoding")
|
||||
rs.next()
|
||||
return rs.getString(1)
|
||||
} catch (e: SQLException) {
|
||||
logger.debug("SQLException: {}", e)
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val listOfChatsForExport: LinkedList<Chat>
|
||||
get() {
|
||||
val list = LinkedList<Chat>()
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT chats.id, chats.name, COUNT(messages.id) as c " +
|
||||
"FROM chats, messages WHERE messages.source_type IN('group', 'supergroup', 'channel') AND messages.source_id=chats.id " +
|
||||
"GROUP BY chats.id ORDER BY c DESC")
|
||||
while (rs.next()) {
|
||||
list.add(Chat(rs.getInt(1), rs.getString(2), rs.getInt(3)))
|
||||
}
|
||||
rs.close()
|
||||
return list
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Exception above!")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val listOfDialogsForExport: LinkedList<Dialog>
|
||||
get() {
|
||||
val list = LinkedList<Dialog>()
|
||||
try {
|
||||
val rs = stmt!!.executeQuery(
|
||||
"SELECT users.id, first_name, last_name, username, COUNT(messages.id) as c " +
|
||||
"FROM users, messages WHERE messages.source_type='dialog' AND messages.source_id=users.id " +
|
||||
"GROUP BY users.id ORDER BY c DESC")
|
||||
while (rs.next()) {
|
||||
list.add(Dialog(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getInt(5)))
|
||||
}
|
||||
rs.close()
|
||||
return list
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Exception above!")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
this.user_manager = UserManager.getInstance()
|
||||
System.out.println("Opening database...")
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC")
|
||||
} catch (e: ClassNotFoundException) {
|
||||
CommandLineController.show_error("Could not load jdbc-sqlite class.")
|
||||
}
|
||||
|
||||
val path = "jdbc:sqlite:" +
|
||||
user_manager.getFileBase() +
|
||||
Config.FILE_NAME_DB
|
||||
|
||||
try {
|
||||
conn = DriverManager.getConnection(path)
|
||||
stmt = conn!!.createStatement()
|
||||
} catch (e: SQLException) {
|
||||
CommandLineController.show_error("Could not connect to SQLITE database.")
|
||||
}
|
||||
|
||||
// Run updates
|
||||
val updates = DatabaseUpdates(conn, this)
|
||||
updates.doUpdates()
|
||||
|
||||
System.out.println("Database is ready.")
|
||||
}
|
||||
|
||||
fun backupDatabase(currentVersion: Int) {
|
||||
val filename = String.format(Config.FILE_NAME_DB_BACKUP, currentVersion)
|
||||
System.out.println(" Creating a backup of your database as " + filename)
|
||||
try {
|
||||
val src = user_manager.getFileBase() + Config.FILE_NAME_DB
|
||||
val dst = user_manager.getFileBase() + filename
|
||||
logger.debug("Copying {} to {}", src, dst)
|
||||
Files.copy(
|
||||
File(src).toPath(),
|
||||
File(dst).toPath())
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
logger.warn("Backup already exists:", e)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Could not create backup.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getTopMessageIDForChannel(id: Int): Int {
|
||||
return queryInt("SELECT MAX(message_id) FROM messages WHERE source_id=$id AND source_type IN('channel', 'supergroup')")
|
||||
}
|
||||
|
||||
fun logRun(start_id: Int, end_id: Int, count: Int) {
|
||||
try {
|
||||
val ps = conn!!.prepareStatement("INSERT INTO runs " +
|
||||
"(time, start_id, end_id, count_missing) " +
|
||||
"VALUES " +
|
||||
"(DateTime('now'), ?, ?, ? )")
|
||||
ps.setInt(1, start_id)
|
||||
ps.setInt(2, end_id)
|
||||
ps.setInt(3, count)
|
||||
ps.execute()
|
||||
} catch (e: SQLException) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun queryInt(query: String): Int {
|
||||
try {
|
||||
val rs = stmt!!.executeQuery(query)
|
||||
rs.next()
|
||||
return rs.getInt(1)
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException("Could not get count of messages.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun saveMessages(all: TLVector<TLAbsMessage>, api_layer: Integer) {
|
||||
try {
|
||||
//"(id, dialog_id, from_id, from_type, text, time, has_media, data, sticker, type) " +
|
||||
//"VALUES " +
|
||||
//"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
val columns = "(message_id, message_type, source_type, source_id, sender_id, fwd_from_id, text, time, has_media, media_type, media_file, media_size, data, api_layer) " +
|
||||
"VALUES " +
|
||||
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
//1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||
val ps = conn!!.prepareStatement("INSERT OR REPLACE INTO messages " + columns)
|
||||
val ps_insert_or_ignore = conn!!.prepareStatement("INSERT OR IGNORE INTO messages " + columns)
|
||||
|
||||
for (abs in all) {
|
||||
if (abs is TLMessage) {
|
||||
val msg = abs as TLMessage
|
||||
ps.setInt(1, msg.getId())
|
||||
ps.setString(2, "message")
|
||||
val peer = msg.getToId()
|
||||
if (peer is TLPeerChat) {
|
||||
ps.setString(3, "group")
|
||||
ps.setInt(4, (peer as TLPeerChat).getChatId())
|
||||
} else if (peer is TLPeerUser) {
|
||||
var id = (peer as TLPeerUser).getUserId()
|
||||
if (id == this.user_manager.getUser().getId()) {
|
||||
id = msg.getFromId()
|
||||
}
|
||||
ps.setString(3, "dialog")
|
||||
ps.setInt(4, id)
|
||||
} else if (peer is TLPeerChannel) {
|
||||
ps.setString(3, "channel")
|
||||
ps.setInt(4, (peer as TLPeerChannel).getChannelId())
|
||||
} else {
|
||||
throw RuntimeException("Unexpected Peer type: " + peer.getClass().getName())
|
||||
}
|
||||
|
||||
if (peer is TLPeerChannel) {
|
||||
// Message in a channel don't have a sender -> insert a null
|
||||
ps.setNull(5, Types.INTEGER)
|
||||
} else {
|
||||
ps.setInt(5, msg.getFromId())
|
||||
}
|
||||
|
||||
if (msg.getFwdFrom() != null && msg.getFwdFrom().getFromId() != null) {
|
||||
ps.setInt(6, msg.getFwdFrom().getFromId())
|
||||
} else {
|
||||
ps.setNull(6, Types.INTEGER)
|
||||
}
|
||||
|
||||
var text = msg.getMessage()
|
||||
if ((text == null || text!!.equals("")) && msg.getMedia() != null) {
|
||||
if (msg.getMedia() is TLMessageMediaDocument) {
|
||||
text = (msg.getMedia() as TLMessageMediaDocument).getCaption()
|
||||
} else if (msg.getMedia() is TLMessageMediaPhoto) {
|
||||
text = (msg.getMedia() as TLMessageMediaPhoto).getCaption()
|
||||
}
|
||||
}
|
||||
ps.setString(7, text)
|
||||
ps.setString(8, "" + msg.getDate())
|
||||
val f = FileManagerFactory.getFileManager(msg, user_manager, client)
|
||||
if (f == null) {
|
||||
ps.setNull(9, Types.BOOLEAN)
|
||||
ps.setNull(10, Types.VARCHAR)
|
||||
ps.setNull(11, Types.VARCHAR)
|
||||
ps.setNull(12, Types.INTEGER)
|
||||
} else {
|
||||
ps.setBoolean(9, true)
|
||||
ps.setString(10, f!!.getName())
|
||||
ps.setString(11, f!!.getTargetFilename())
|
||||
ps.setInt(12, f!!.getSize())
|
||||
}
|
||||
val stream = ByteArrayOutputStream()
|
||||
msg.serializeBody(stream)
|
||||
ps.setBytes(13, stream.toByteArray())
|
||||
ps.setInt(14, api_layer)
|
||||
ps.addBatch()
|
||||
} else if (abs is TLMessageService) {
|
||||
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||
ps_insert_or_ignore.setString(2, "service_message")
|
||||
ps_insert_or_ignore.setNull(3, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(4, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(5, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(6, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(7, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(8, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(9, Types.BOOLEAN)
|
||||
ps_insert_or_ignore.setNull(10, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(11, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(12, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(13, Types.BLOB)
|
||||
ps_insert_or_ignore.setInt(14, api_layer)
|
||||
ps_insert_or_ignore.addBatch()
|
||||
} else if (abs is TLMessageEmpty) {
|
||||
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||
ps_insert_or_ignore.setString(2, "empty_message")
|
||||
ps_insert_or_ignore.setNull(3, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(4, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(5, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(6, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(7, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(8, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(9, Types.BOOLEAN)
|
||||
ps_insert_or_ignore.setNull(10, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(11, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(12, Types.INTEGER)
|
||||
ps_insert_or_ignore.setNull(13, Types.BLOB)
|
||||
ps_insert_or_ignore.setInt(14, api_layer)
|
||||
ps_insert_or_ignore.addBatch()
|
||||
} else {
|
||||
throw RuntimeException("Unexpected Message type: " + abs.getClass().getName())
|
||||
}
|
||||
}
|
||||
conn!!.setAutoCommit(false)
|
||||
ps.executeBatch()
|
||||
ps.clearBatch()
|
||||
ps_insert_or_ignore.executeBatch()
|
||||
ps_insert_or_ignore.clearBatch()
|
||||
conn!!.commit()
|
||||
conn!!.setAutoCommit(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Exception shown above happened.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun saveChats(all: TLVector<TLAbsChat>) {
|
||||
try {
|
||||
val ps_insert_or_replace = conn!!.prepareStatement(
|
||||
"INSERT OR REPLACE INTO chats " +
|
||||
"(id, name, type) " +
|
||||
"VALUES " +
|
||||
"(?, ?, ?)")
|
||||
val ps_insert_or_ignore = conn!!.prepareStatement(
|
||||
"INSERT OR IGNORE INTO chats " +
|
||||
"(id, name, type) " +
|
||||
"VALUES " +
|
||||
"(?, ?, ?)")
|
||||
|
||||
for (abs in all) {
|
||||
ps_insert_or_replace.setInt(1, abs.getId())
|
||||
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||
if (abs is TLChatEmpty) {
|
||||
ps_insert_or_ignore.setNull(2, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setString(3, "empty_chat")
|
||||
ps_insert_or_ignore.addBatch()
|
||||
} else if (abs is TLChatForbidden) {
|
||||
ps_insert_or_replace.setString(2, (abs as TLChatForbidden).getTitle())
|
||||
ps_insert_or_replace.setString(3, "chat")
|
||||
ps_insert_or_replace.addBatch()
|
||||
} else if (abs is TLChannelForbidden) {
|
||||
ps_insert_or_replace.setString(2, (abs as TLChannelForbidden).getTitle())
|
||||
ps_insert_or_replace.setString(3, "channel")
|
||||
ps_insert_or_replace.addBatch()
|
||||
} else if (abs is TLChat) {
|
||||
ps_insert_or_replace.setString(2, (abs as TLChat).getTitle())
|
||||
ps_insert_or_replace.setString(3, "chat")
|
||||
ps_insert_or_replace.addBatch()
|
||||
} else if (abs is TLChannel) {
|
||||
ps_insert_or_replace.setString(2, (abs as TLChannel).getTitle())
|
||||
ps_insert_or_replace.setString(3, "channel")
|
||||
ps_insert_or_replace.addBatch()
|
||||
} else {
|
||||
throw RuntimeException("Unexpected " + abs.getClass().getName())
|
||||
}
|
||||
}
|
||||
conn!!.setAutoCommit(false)
|
||||
ps_insert_or_ignore.executeBatch()
|
||||
ps_insert_or_ignore.clearBatch()
|
||||
ps_insert_or_replace.executeBatch()
|
||||
ps_insert_or_replace.clearBatch()
|
||||
conn!!.commit()
|
||||
conn!!.setAutoCommit(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Exception shown above happened.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun saveUsers(all: TLVector<TLAbsUser>) {
|
||||
try {
|
||||
val ps_insert_or_replace = conn!!.prepareStatement(
|
||||
"INSERT OR REPLACE INTO users " +
|
||||
"(id, first_name, last_name, username, type, phone) " +
|
||||
"VALUES " +
|
||||
"(?, ?, ?, ?, ?, ?)")
|
||||
val ps_insert_or_ignore = conn!!.prepareStatement(
|
||||
"INSERT OR IGNORE INTO users " +
|
||||
"(id, first_name, last_name, username, type, phone) " +
|
||||
"VALUES " +
|
||||
"(?, ?, ?, ?, ?, ?)")
|
||||
for (abs in all) {
|
||||
if (abs is TLUser) {
|
||||
val user = abs as TLUser
|
||||
ps_insert_or_replace.setInt(1, user.getId())
|
||||
ps_insert_or_replace.setString(2, user.getFirstName())
|
||||
ps_insert_or_replace.setString(3, user.getLastName())
|
||||
ps_insert_or_replace.setString(4, user.getUsername())
|
||||
ps_insert_or_replace.setString(5, "user")
|
||||
ps_insert_or_replace.setString(6, user.getPhone())
|
||||
ps_insert_or_replace.addBatch()
|
||||
} else if (abs is TLUserEmpty) {
|
||||
ps_insert_or_ignore.setInt(1, abs.getId())
|
||||
ps_insert_or_ignore.setNull(2, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(3, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setNull(4, Types.VARCHAR)
|
||||
ps_insert_or_ignore.setString(5, "empty_user")
|
||||
ps_insert_or_ignore.setNull(6, Types.VARCHAR)
|
||||
ps_insert_or_ignore.addBatch()
|
||||
} else {
|
||||
throw RuntimeException("Unexpected " + abs.getClass().getName())
|
||||
}
|
||||
}
|
||||
conn!!.setAutoCommit(false)
|
||||
ps_insert_or_ignore.executeBatch()
|
||||
ps_insert_or_ignore.clearBatch()
|
||||
ps_insert_or_replace.executeBatch()
|
||||
ps_insert_or_replace.clearBatch()
|
||||
conn!!.commit()
|
||||
conn!!.setAutoCommit(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Exception shown above happened.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getIdsFromQuery(query: String): LinkedList<Integer> {
|
||||
try {
|
||||
val list = LinkedList<Integer>()
|
||||
val rs = stmt!!.executeQuery(query)
|
||||
while (rs.next()) {
|
||||
list.add(rs.getInt(1))
|
||||
}
|
||||
rs.close()
|
||||
return list
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getMessageTypesWithCount(c: AbstractChat): HashMap<String, Integer> {
|
||||
val map = HashMap<String, Integer>()
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT message_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY message_type")
|
||||
while (rs.next()) {
|
||||
map.put("count.messages.type." + rs.getString(1), rs.getInt(2))
|
||||
}
|
||||
return map
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getMessageMediaTypesWithCount(c: AbstractChat): HashMap<String, Integer> {
|
||||
val map = HashMap<String, Integer>()
|
||||
try {
|
||||
var count = 0
|
||||
val rs = stmt!!.executeQuery("SELECT media_type, COUNT(message_id) FROM messages WHERE " + c.query + " GROUP BY media_type")
|
||||
while (rs.next()) {
|
||||
var s = rs.getString(1)
|
||||
if (s == null) {
|
||||
s = "null"
|
||||
} else {
|
||||
count += rs.getInt(2)
|
||||
}
|
||||
map.put("count.messages.media_type." + s!!, rs.getInt(2))
|
||||
}
|
||||
map.put("count.messages.media_type.any", count)
|
||||
return map
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getMessageAuthorsWithCount(c: AbstractChat): HashMap<String, Object> {
|
||||
val map = HashMap<String, Object>()
|
||||
val user_map = HashMap<User, Integer>()
|
||||
var count_others = 0
|
||||
// Set a default value for 'me' to fix the charts for channels - cause I
|
||||
// possibly didn't send any messages there.
|
||||
map.put("authors.count.me", 0)
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT users.id, users.first_name, users.last_name, users.username, COUNT(messages.id) " +
|
||||
"FROM messages " +
|
||||
"LEFT JOIN users ON users.id=messages.sender_id " +
|
||||
"WHERE " + c.query + " GROUP BY sender_id")
|
||||
while (rs.next()) {
|
||||
val u: User
|
||||
if (rs.getString(2) != null || rs.getString(3) != null || rs.getString(4) != null) {
|
||||
u = User(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4))
|
||||
} else {
|
||||
u = User(rs.getInt(1), "Unknown", "", "")
|
||||
}
|
||||
if (u.isMe) {
|
||||
map.put("authors.count.me", rs.getInt(5))
|
||||
} else {
|
||||
user_map.put(u, rs.getInt(5))
|
||||
count_others += rs.getInt(5)
|
||||
}
|
||||
}
|
||||
map.put("authors.count.others", count_others)
|
||||
map.put("authors.all", user_map.entrySet())
|
||||
return map
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getMessageTimesMatrix(c: AbstractChat): Array<IntArray> {
|
||||
val result = Array(7) { IntArray(24) }
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT STRFTIME('%w', time, 'unixepoch') as DAY, " +
|
||||
"STRFTIME('%H', time, 'unixepoch') AS hour, " +
|
||||
"COUNT(id) FROM messages WHERE " + c.query + " GROUP BY hour, day " +
|
||||
"ORDER BY hour, day")
|
||||
while (rs.next()) {
|
||||
result[if (rs.getInt(1) === 0) 6 else rs.getInt(1) - 1][rs.getInt(2)] = rs.getInt(3)
|
||||
}
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getMessagesForExport(c: AbstractChat): LinkedList<HashMap<String, Object>> {
|
||||
try {
|
||||
|
||||
val rs = stmt!!.executeQuery("SELECT messages.message_id as message_id, text, time*1000 as time, has_media, " +
|
||||
"media_type, media_file, media_size, users.first_name as user_first_name, users.last_name as user_last_name, " +
|
||||
"users.username as user_username, users.id as user_id, " +
|
||||
"users_fwd.first_name as user_fwd_first_name, users_fwd.last_name as user_fwd_last_name, users_fwd.username as user_fwd_username " +
|
||||
"FROM messages " +
|
||||
"LEFT JOIN users ON users.id=messages.sender_id " +
|
||||
"LEFT JOIN users AS users_fwd ON users_fwd.id=fwd_from_id WHERE " +
|
||||
c.query + " " +
|
||||
"ORDER BY messages.message_id")
|
||||
val format_time = SimpleDateFormat("HH:mm:ss")
|
||||
val format_date = SimpleDateFormat("d MMM yy")
|
||||
val meta = rs.getMetaData()
|
||||
val columns = meta.getColumnCount()
|
||||
val list = LinkedList<HashMap<String, Object>>()
|
||||
|
||||
var count: Integer = 0
|
||||
var old_date: String? = null
|
||||
var old_user: Integer? = null
|
||||
while (rs.next()) {
|
||||
val h = HashMap<String, Object>(columns)
|
||||
for (i in 1..columns) {
|
||||
h.put(meta.getColumnName(i), rs.getObject(i))
|
||||
}
|
||||
// Additional values to make up for Mustache's inability to format dates
|
||||
val d = rs.getTime("time")
|
||||
val date = format_date.format(d)
|
||||
h.put("formatted_time", format_time.format(d))
|
||||
h.put("formatted_date", date)
|
||||
if (rs.getString("media_type") != null) {
|
||||
h.put("media_" + rs.getString("media_type"), true)
|
||||
}
|
||||
h.put("from_me", rs.getInt("user_id") === user_manager.getUser().getId())
|
||||
h.put("is_new_date", !date.equals(old_date))
|
||||
h.put("odd_even", if (count % 2 == 0) "even" else "odd")
|
||||
h.put("same_user", old_user != null && rs.getInt("user_id") === old_user)
|
||||
old_user = rs.getInt("user_id")
|
||||
old_date = date
|
||||
|
||||
list.add(h)
|
||||
count++
|
||||
}
|
||||
rs.close()
|
||||
return list
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Exception above!")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
abstract inner class AbstractChat {
|
||||
abstract val query: String
|
||||
}
|
||||
|
||||
inner class Dialog(var id: Int, var first_name: String, var last_name: String, var username: String, var count: Int) : AbstractChat() {
|
||||
|
||||
override val query: String
|
||||
get() = "source_type='dialog' AND source_id=" + id
|
||||
}
|
||||
|
||||
inner class Chat(var id: Int, var name: String, var count: Int) : AbstractChat() {
|
||||
|
||||
override val query: String
|
||||
get() = "source_type IN('group', 'supergroup', 'channel') AND source_id=" + id
|
||||
}
|
||||
|
||||
inner class User(id: Int, first_name: String?, last_name: String?, username: String) {
|
||||
var name: String
|
||||
var isMe: Boolean = false
|
||||
|
||||
init {
|
||||
isMe = id == user_manager.getUser().getId()
|
||||
val s = StringBuilder()
|
||||
if (first_name != null) s.append(first_name + " ")
|
||||
if (last_name != null) s.append(last_name)
|
||||
name = s.toString().trim()
|
||||
}
|
||||
}
|
||||
|
||||
inner class GlobalChat : AbstractChat() {
|
||||
override val query: String
|
||||
get() = "1=1"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(Database::class.java)
|
||||
private var instance: Database? = null
|
||||
|
||||
fun init(c: TelegramClient) {
|
||||
instance = Database(c)
|
||||
}
|
||||
|
||||
fun getInstance(): Database {
|
||||
if (instance == null) throw RuntimeException("Database is not initialized but getInstance() was called.")
|
||||
return instance
|
||||
}
|
||||
|
||||
fun bytesToTLMessage(b: ByteArray?): TLMessage? {
|
||||
try {
|
||||
if (b == null) return null
|
||||
val stream = ByteArrayInputStream(b)
|
||||
val msg = TLMessage()
|
||||
msg.deserializeBody(stream, TLApiContext.getInstance())
|
||||
return msg
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
throw RuntimeException("Could not deserialize message.")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,379 @@
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import java.util.HashMap
|
||||
import java.util.LinkedHashMap
|
||||
import java.util.LinkedList
|
||||
import java.sql.Connection
|
||||
import java.sql.SQLException
|
||||
import java.sql.Statement
|
||||
import java.sql.Types
|
||||
import java.sql.ResultSet
|
||||
import java.sql.PreparedStatement
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.Logger
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||
|
||||
class DatabaseUpdates(protected var conn: Connection, protected var db: Database) {
|
||||
|
||||
private val maxPossibleVersion: Int
|
||||
get() = updates.size()
|
||||
|
||||
init {
|
||||
logger.debug("Registering Database Updates...")
|
||||
register(DB_Update_1(conn, db))
|
||||
register(DB_Update_2(conn, db))
|
||||
register(DB_Update_3(conn, db))
|
||||
register(DB_Update_4(conn, db))
|
||||
register(DB_Update_5(conn, db))
|
||||
register(DB_Update_6(conn, db))
|
||||
register(DB_Update_7(conn, db))
|
||||
register(DB_Update_8(conn, db))
|
||||
}
|
||||
|
||||
fun doUpdates() {
|
||||
try {
|
||||
val stmt = conn.createStatement()
|
||||
var rs: ResultSet
|
||||
logger.debug("DatabaseUpdate.doUpdates running")
|
||||
|
||||
logger.debug("Getting current database version")
|
||||
val version: Int
|
||||
logger.debug("Checking if table database_versions exists")
|
||||
rs = stmt.executeQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='database_versions'")
|
||||
rs.next()
|
||||
if (rs.getInt(1) === 0) {
|
||||
logger.debug("Table does not exist")
|
||||
version = 0
|
||||
} else {
|
||||
logger.debug("Table exists. Checking max version")
|
||||
rs.close()
|
||||
rs = stmt.executeQuery("SELECT MAX(version) FROM database_versions")
|
||||
rs.next()
|
||||
version = rs.getInt(1)
|
||||
}
|
||||
rs.close()
|
||||
logger.debug("version: {}", version)
|
||||
System.out.println("Database version: " + version)
|
||||
logger.debug("Max available database version is {}", maxPossibleVersion)
|
||||
|
||||
if (version < maxPossibleVersion) {
|
||||
logger.debug("Update is necessary. {} => {}.", version, maxPossibleVersion)
|
||||
var backup = false
|
||||
for (i in version + 1..maxPossibleVersion) {
|
||||
if (getUpdateToVersion(i).needsBackup()) {
|
||||
logger.debug("Update to version {} needs a backup", i)
|
||||
backup = true
|
||||
}
|
||||
}
|
||||
if (backup) {
|
||||
if (version > 0) {
|
||||
logger.debug("Performing backup")
|
||||
db.backupDatabase(version)
|
||||
} else {
|
||||
logger.debug("NOT performing a backup, because we are creating a fresh database and don't need a backup of that.")
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Applying updates")
|
||||
try {
|
||||
for (i in version + 1..maxPossibleVersion) {
|
||||
getUpdateToVersion(i).doUpdate()
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.debug("No update necessary.")
|
||||
}
|
||||
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun getUpdateToVersion(i: Int): DatabaseUpdate {
|
||||
return updates.get(i - 1)
|
||||
}
|
||||
|
||||
private fun register(d: DatabaseUpdate) {
|
||||
logger.debug("Registering {} as update to version {}", d.getClass().getName(), d.version)
|
||||
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))
|
||||
}
|
||||
updates.add(d)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(DatabaseUpdates::class.java)
|
||||
private val updates = LinkedList<DatabaseUpdate>()
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class DatabaseUpdate(protected var conn: Connection, protected var db: Database) {
|
||||
protected var stmt: Statement
|
||||
abstract val version: Int
|
||||
|
||||
init {
|
||||
try {
|
||||
stmt = conn.createStatement()
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
fun doUpdate() {
|
||||
logger.debug("Applying update to version {}", version)
|
||||
System.out.println(" Updating to version $version...")
|
||||
_doUpdate()
|
||||
logger.debug("Saving current database version to the db")
|
||||
stmt.executeUpdate("INSERT INTO database_versions (version) VALUES ($version)")
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
protected abstract fun _doUpdate()
|
||||
|
||||
fun needsBackup(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
protected fun execute(sql: String) {
|
||||
logger.debug("Executing: {}", sql)
|
||||
stmt.executeUpdate(sql)
|
||||
}
|
||||
|
||||
companion object {
|
||||
protected val logger = LoggerFactory.getLogger(DatabaseUpdate::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
internal class DB_Update_1(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||
override val version: Int
|
||||
get() = 1
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun _doUpdate() {
|
||||
stmt.executeUpdate("CREATE TABLE messages ("
|
||||
+ "id INTEGER PRIMARY KEY ASC, "
|
||||
+ "dialog_id INTEGER, "
|
||||
+ "to_id INTEGER, "
|
||||
+ "from_id INTEGER, "
|
||||
+ "from_type TEXT, "
|
||||
+ "text TEXT, "
|
||||
+ "time TEXT, "
|
||||
+ "has_media BOOLEAN, "
|
||||
+ "sticker TEXT, "
|
||||
+ "data BLOB,"
|
||||
+ "type TEXT)")
|
||||
stmt.executeUpdate("CREATE TABLE dialogs ("
|
||||
+ "id INTEGER PRIMARY KEY ASC, "
|
||||
+ "name TEXT, "
|
||||
+ "type TEXT)")
|
||||
stmt.executeUpdate("CREATE TABLE people ("
|
||||
+ "id INTEGER PRIMARY KEY ASC, "
|
||||
+ "first_name TEXT, "
|
||||
+ "last_name TEXT, "
|
||||
+ "username TEXT, "
|
||||
+ "type TEXT)")
|
||||
stmt.executeUpdate("CREATE TABLE database_versions (" + "version INTEGER)")
|
||||
}
|
||||
}
|
||||
|
||||
internal class DB_Update_2(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||
override val version: Int
|
||||
get() = 2
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun _doUpdate() {
|
||||
stmt.executeUpdate("ALTER TABLE people RENAME TO 'users'")
|
||||
stmt.executeUpdate("ALTER TABLE users ADD COLUMN phone TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
internal class DB_Update_3(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||
override val version: Int
|
||||
get() = 3
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun _doUpdate() {
|
||||
stmt.executeUpdate("ALTER TABLE dialogs RENAME TO 'chats'")
|
||||
}
|
||||
}
|
||||
|
||||
internal class DB_Update_4(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||
override val version: Int
|
||||
get() = 4
|
||||
|
||||
@Throws(SQLException::class)
|
||||
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("INSERT INTO messages_new SELECT * FROM messages")
|
||||
stmt.executeUpdate("DROP TABLE messages")
|
||||
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO 'messages'")
|
||||
}
|
||||
}
|
||||
|
||||
internal class DB_Update_5(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||
override val version: Int
|
||||
get() = 5
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun _doUpdate() {
|
||||
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) {
|
||||
override val version: Int
|
||||
get() = 6
|
||||
|
||||
override fun needsBackup(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun _doUpdate() {
|
||||
stmt.executeUpdate(
|
||||
"CREATE TABLE messages_new (\n" +
|
||||
" id INTEGER PRIMARY KEY ASC,\n" +
|
||||
" message_type TEXT,\n" +
|
||||
" dialog_id INTEGER,\n" +
|
||||
" chat_id INTEGER,\n" +
|
||||
" sender_id INTEGER,\n" +
|
||||
" fwd_from_id INTEGER,\n" +
|
||||
" text TEXT,\n" +
|
||||
" time INTEGER,\n" +
|
||||
" has_media BOOLEAN,\n" +
|
||||
" media_type TEXT,\n" +
|
||||
" media_file TEXT,\n" +
|
||||
" media_size INTEGER,\n" +
|
||||
" media_json TEXT,\n" +
|
||||
" markup_json TEXT,\n" +
|
||||
" data BLOB)")
|
||||
val mappings = LinkedHashMap<String, String>()
|
||||
mappings.put("id", "id")
|
||||
mappings.put("message_type", "type")
|
||||
mappings.put("dialog_id", "CASE from_type WHEN 'user' THEN dialog_id ELSE NULL END")
|
||||
mappings.put("chat_id", "CASE from_type WHEN 'chat' THEN dialog_id ELSE NULL END")
|
||||
mappings.put("sender_id", "from_id")
|
||||
mappings.put("text", "text")
|
||||
mappings.put("time", "time")
|
||||
mappings.put("has_media", "has_media")
|
||||
mappings.put("data", "data")
|
||||
val query = StringBuilder("INSERT INTO messages_new\n(")
|
||||
var first: Boolean
|
||||
first = true
|
||||
for (s in mappings.keySet()) {
|
||||
if (!first) query.append(", ")
|
||||
query.append(s)
|
||||
first = false
|
||||
}
|
||||
query.append(")\nSELECT \n")
|
||||
first = true
|
||||
for (s in mappings.values()) {
|
||||
if (!first) query.append(", ")
|
||||
query.append(s)
|
||||
first = false
|
||||
}
|
||||
query.append("\nFROM messages")
|
||||
stmt.executeUpdate(query.toString())
|
||||
|
||||
System.out.println(" Updating the data (this might take some time)...")
|
||||
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=?")
|
||||
while (rs.next()) {
|
||||
ps.setInt(5, rs.getInt(1))
|
||||
val msg = db.bytesToTLMessage(rs.getBytes(2))
|
||||
if (msg == null || msg!!.getFwdFrom() == null) {
|
||||
ps.setNull(1, Types.INTEGER)
|
||||
} else {
|
||||
ps.setInt(1, msg!!.getFwdFrom().getFromId())
|
||||
}
|
||||
val f = FileManagerFactory.getFileManager(msg, db.user_manager, db.client)
|
||||
if (f == null) {
|
||||
ps.setNull(2, Types.VARCHAR)
|
||||
ps.setNull(3, Types.VARCHAR)
|
||||
ps.setNull(4, Types.INTEGER)
|
||||
} else {
|
||||
ps.setString(2, f!!.getName())
|
||||
ps.setString(3, f!!.getTargetFilename())
|
||||
ps.setInt(4, f!!.getSize())
|
||||
}
|
||||
ps.addBatch()
|
||||
}
|
||||
rs.close()
|
||||
conn.setAutoCommit(false)
|
||||
ps.executeBatch()
|
||||
conn.commit()
|
||||
conn.setAutoCommit(true)
|
||||
stmt.executeUpdate("DROP TABLE messages")
|
||||
stmt.executeUpdate("ALTER TABLE messages_new RENAME TO messages")
|
||||
}
|
||||
}
|
||||
|
||||
internal class DB_Update_7(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||
override val version: Int
|
||||
get() = 7
|
||||
|
||||
override fun needsBackup(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun _doUpdate() {
|
||||
stmt.executeUpdate("ALTER TABLE messages ADD COLUMN api_layer INTEGER")
|
||||
|
||||
stmt.executeUpdate("UPDATE messages SET api_layer=51")
|
||||
}
|
||||
}
|
||||
|
||||
internal class DB_Update_8(conn: Connection, db: Database) : DatabaseUpdate(conn, db) {
|
||||
override val version: Int
|
||||
get() = 8
|
||||
|
||||
override fun needsBackup(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
override fun _doUpdate() {
|
||||
execute("ALTER TABLE messages ADD COLUMN source_type TEXT")
|
||||
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='group', source_id=chat_id where chat_id is not null")
|
||||
|
||||
execute("CREATE TABLE messages_new (" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"message_id INTEGER," +
|
||||
"message_type TEXT," +
|
||||
"source_type TEXT," +
|
||||
"source_id INTEGER," +
|
||||
"sender_id INTEGER," +
|
||||
"fwd_from_id INTEGER," +
|
||||
"text TEXT," +
|
||||
"time INTEGER," +
|
||||
"has_media BOOLEAN," +
|
||||
"media_type TEXT," +
|
||||
"media_file TEXT," +
|
||||
"media_size INTEGER," +
|
||||
"media_json TEXT," +
|
||||
"markup_json TEXT," +
|
||||
"data BLOB," +
|
||||
"api_layer INTEGER)")
|
||||
execute("INSERT INTO messages_new" +
|
||||
"(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)" +
|
||||
"SELECT " +
|
||||
"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")
|
||||
execute("DROP TABLE messages")
|
||||
execute("ALTER TABLE messages_new RENAME TO 'messages'")
|
||||
execute("CREATE UNIQUE INDEX unique_messages ON messages (source_type, source_id, message_id)")
|
||||
}
|
||||
}
|
@ -0,0 +1,495 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.api.Kotlogram
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.Logger
|
||||
import com.google.gson.Gson
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.util.HashMap
|
||||
import java.util.Random
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class DownloadManager(internal var client: TelegramClient?, p: DownloadProgressInterface) {
|
||||
internal var user: UserManager? = null
|
||||
internal var db: Database? = null
|
||||
internal var prog: DownloadProgressInterface? = null
|
||||
internal var has_seen_flood_wait_message = false
|
||||
|
||||
init {
|
||||
this.user = UserManager.getInstance()
|
||||
this.prog = p
|
||||
this.db = Database.getInstance()
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class)
|
||||
fun downloadMessages(limit: Integer) {
|
||||
var completed = true
|
||||
do {
|
||||
completed = true
|
||||
try {
|
||||
_downloadMessages(limit)
|
||||
} catch (e: RpcErrorException) {
|
||||
if (e.getCode() === 420) { // FLOOD_WAIT
|
||||
completed = false
|
||||
Utils.obeyFloodWaitException(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
} catch (e: TimeoutException) {
|
||||
completed = false
|
||||
System.out.println("")
|
||||
System.out.println("Telegram took too long to respond to our request.")
|
||||
System.out.println("I'm going to wait a minute and then try again.")
|
||||
try {
|
||||
TimeUnit.MINUTES.sleep(1)
|
||||
} catch (e2: InterruptedException) {
|
||||
}
|
||||
|
||||
System.out.println("")
|
||||
}
|
||||
|
||||
} while (!completed)
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
fun _downloadMessages(limit: Integer?) {
|
||||
logger.info("This is _downloadMessages with limit {}", limit)
|
||||
val dialog_limit = 100
|
||||
logger.info("Downloading the last {} dialogs", dialog_limit)
|
||||
System.out.println("Downloading most recent dialogs... ")
|
||||
var max_message_id = 0
|
||||
val dialogs = client!!.messagesGetDialogs(
|
||||
0,
|
||||
0,
|
||||
TLInputPeerEmpty(),
|
||||
dialog_limit)
|
||||
logger.debug("Got {} dialogs", dialogs.getDialogs().size())
|
||||
|
||||
for (d in dialogs.getDialogs()) {
|
||||
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().getClass().getName())
|
||||
max_message_id = d.getTopMessage()
|
||||
}
|
||||
}
|
||||
System.out.println("Top message ID is " + max_message_id)
|
||||
var max_database_id = db!!.getTopMessageID()
|
||||
System.out.println("Top message ID in database is " + max_database_id)
|
||||
if (limit != null) {
|
||||
System.out.println("Limit is set to " + limit!!)
|
||||
max_database_id = Math.max(max_database_id, max_message_id - limit!!)
|
||||
System.out.println("New top message id 'in database' is " + max_database_id)
|
||||
}
|
||||
if (max_message_id - max_database_id > 1000000) {
|
||||
System.out.println("Would have to load more than 1 million messages which is not supported by telegram. Capping the list.")
|
||||
logger.debug("max_message_id={}, max_database_id={}, difference={}", max_message_id, max_database_id, max_message_id - max_database_id)
|
||||
max_database_id = Math.max(0, max_message_id - 1000000)
|
||||
logger.debug("new max_database_id: {}", max_database_id)
|
||||
}
|
||||
|
||||
if (max_database_id == max_message_id) {
|
||||
System.out.println("No new messages to download.")
|
||||
} else if (max_database_id > max_message_id) {
|
||||
throw 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 {
|
||||
val start_id = max_database_id + 1
|
||||
val end_id = max_message_id
|
||||
|
||||
val ids = makeIdList(start_id, end_id)
|
||||
downloadMessages(ids, null, null)
|
||||
}
|
||||
|
||||
logger.info("Searching for missing messages in the db")
|
||||
val count_missing = 0
|
||||
System.out.println("Checking message database for completeness...")
|
||||
val db_count = db!!.getMessageCount()
|
||||
val db_max = db!!.getTopMessageID()
|
||||
logger.debug("db_count: {}", db_count)
|
||||
logger.debug("db_max: {}", db_max)
|
||||
|
||||
/*if (db_count != db_max) {
|
||||
if (limit != null) {
|
||||
System.out.println("You are missing messages in your database. But since you're using '--limit-messages', I won't download these now.");
|
||||
} else {
|
||||
LinkedList<Integer> all_missing_ids = db.getMissingIDs();
|
||||
LinkedList<Integer> downloadable_missing_ids = new LinkedList<Integer>();
|
||||
for (Integer id : all_missing_ids) {
|
||||
if (id > max_message_id - 1000000) downloadable_missing_ids.add(id);
|
||||
}
|
||||
count_missing = all_missing_ids.size();
|
||||
System.out.println("" + all_missing_ids.size() + " messages are missing in your Database.");
|
||||
System.out.println("I can (and will) download " + downloadable_missing_ids.size() + " of them.");
|
||||
|
||||
downloadMessages(downloadable_missing_ids, null);
|
||||
}
|
||||
|
||||
logger.info("Logging this run");
|
||||
db.logRun(Math.min(max_database_id + 1, max_message_id), max_message_id, count_missing);
|
||||
}
|
||||
*/
|
||||
|
||||
if (CommandLineOptions.cmd_channels || CommandLineOptions.cmd_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.")
|
||||
|
||||
val channel_access_hashes = HashMap<Integer, Long>()
|
||||
val channel_names = HashMap<Integer, String>()
|
||||
val channels = LinkedList<Integer>()
|
||||
val supergroups = LinkedList<Integer>()
|
||||
|
||||
// TODO Add chat title (and other stuff?) to the database
|
||||
for (c in dialogs.getChats()) {
|
||||
if (c is TLChannel) {
|
||||
val ch = c as TLChannel
|
||||
channel_access_hashes.put(c.getId(), ch.getAccessHash())
|
||||
channel_names.put(c.getId(), ch.getTitle())
|
||||
if (ch.getMegagroup()) {
|
||||
supergroups.add(c.getId())
|
||||
} else {
|
||||
channels.add(c.getId())
|
||||
}
|
||||
// Channel: TLChannel
|
||||
// Supergroup: getMegagroup()==true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (d in dialogs.getDialogs()) {
|
||||
if (d.getPeer() is TLPeerChannel) {
|
||||
val channel_id = (d.getPeer() as TLPeerChannel).getChannelId()
|
||||
|
||||
// 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
|
||||
if (channels.contains(channel_id) && !CommandLineOptions.cmd_channels || supergroups.contains(channel_id) && !CommandLineOptions.cmd_supergroups) {
|
||||
// Skip this chat.
|
||||
continue
|
||||
}
|
||||
val max_known_id = db!!.getTopMessageIDForChannel(channel_id)
|
||||
if (d.getTopMessage() > max_known_id) {
|
||||
val ids = makeIdList(max_known_id + 1, d.getTopMessage())
|
||||
val access_hash = channel_access_hashes.get(channel_id) ?: throw RuntimeException("AccessHash for Channel missing.")
|
||||
var channel_name = channel_names.get(channel_id)
|
||||
if (channel_name == null) {
|
||||
channel_name = "?"
|
||||
}
|
||||
val channel = TLInputChannel(channel_id, access_hash)
|
||||
downloadMessages(ids, channel, "channel " + channel_name!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class)
|
||||
private fun downloadMessages(ids: List<Integer>, channel: TLInputChannel?, source_string: String?) {
|
||||
prog!!.onMessageDownloadStart(ids.size(), source_string)
|
||||
|
||||
logger.debug("Entering download loop")
|
||||
while (ids.size() > 0) {
|
||||
logger.trace("Loop")
|
||||
val vector = TLIntVector()
|
||||
val download_count = Config.GET_MESSAGES_BATCH_SIZE
|
||||
logger.trace("download_count: {}", download_count)
|
||||
for (i in 0 until download_count) {
|
||||
if (ids.size() === 0) break
|
||||
vector.add(ids.remove(0))
|
||||
}
|
||||
logger.trace("vector.size(): {}", vector.size())
|
||||
logger.trace("ids.size(): {}", ids.size())
|
||||
|
||||
var response: TLAbsMessages
|
||||
var tries = 0
|
||||
while (true) {
|
||||
logger.trace("Trying getMessages(), tries={}", tries)
|
||||
if (tries >= 5) {
|
||||
CommandLineController.show_error("Couldn't getMessages after 5 tries. Quitting.")
|
||||
}
|
||||
tries++
|
||||
try {
|
||||
if (channel == null) {
|
||||
response = client!!.messagesGetMessages(vector)
|
||||
} else {
|
||||
response = client!!.channelsGetMessages(channel, vector)
|
||||
}
|
||||
break
|
||||
} catch (e: RpcErrorException) {
|
||||
if (e.getCode() === 420) { // FLOOD_WAIT
|
||||
Utils.obeyFloodWaitException(e, has_seen_flood_wait_message)
|
||||
has_seen_flood_wait_message = true
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
logger.trace("response.getMessages().size(): {}", response.getMessages().size())
|
||||
if (response.getMessages().size() !== vector.size()) {
|
||||
CommandLineController.show_error("Requested " + vector.size() + " messages, but got " + response.getMessages().size() + ". That is unexpected. Quitting.")
|
||||
}
|
||||
|
||||
prog!!.onMessageDownloaded(response.getMessages().size())
|
||||
db!!.saveMessages(response.getMessages(), Kotlogram.API_LAYER)
|
||||
db!!.saveChats(response.getChats())
|
||||
db!!.saveUsers(response.getUsers())
|
||||
logger.trace("Sleeping")
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_MESSAGES)
|
||||
} catch (e: InterruptedException) {
|
||||
}
|
||||
|
||||
}
|
||||
logger.debug("Finished.")
|
||||
|
||||
prog!!.onMessageDownloadFinished()
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class)
|
||||
fun downloadMedia() {
|
||||
download_client = client!!.getDownloaderClient()
|
||||
var completed = true
|
||||
do {
|
||||
completed = true
|
||||
try {
|
||||
_downloadMedia()
|
||||
} catch (e: RpcErrorException) {
|
||||
if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
|
||||
completed = false
|
||||
Utils.obeyFloodWaitException(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
/*catch (TimeoutException e) {
|
||||
completed = false;
|
||||
System.out.println("");
|
||||
System.out.println("Telegram took too long to respond to our request.");
|
||||
System.out.println("I'm going to wait a minute and then try again.");
|
||||
logger.warn("TimeoutException caught", e);
|
||||
try { TimeUnit.MINUTES.sleep(1); } catch(InterruptedException e2) {}
|
||||
System.out.println("");
|
||||
}*/
|
||||
} while (!completed)
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class)
|
||||
private fun _downloadMedia() {
|
||||
logger.info("This is _downloadMedia")
|
||||
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)
|
||||
if (ids.size() > 0) {
|
||||
System.out.println("You have " + ids.size() + " messages in your db that need an update. Doing that now.")
|
||||
logger.debug("Found {} messages", ids.size())
|
||||
downloadMessages(ids, null, null)
|
||||
}
|
||||
|
||||
val messages = this.db!!.getMessagesWithMedia()
|
||||
logger.debug("Database returned {} messages with media", messages.size())
|
||||
prog!!.onMediaDownloadStart(messages.size())
|
||||
for (msg in messages) {
|
||||
val m = FileManagerFactory.getFileManager(msg, user, client)
|
||||
logger.trace("message {}, {}, {}, {}, {}",
|
||||
msg.getId(),
|
||||
msg.getMedia().getClass().getSimpleName().replace("TLMessageMedia", "…"),
|
||||
m.getClass().getSimpleName(),
|
||||
if (m.isEmpty()) "empty" else "non-empty",
|
||||
if (m.isDownloaded()) "downloaded" else "not downloaded")
|
||||
if (m.isEmpty()) {
|
||||
prog!!.onMediaDownloadedEmpty()
|
||||
} else if (m.isDownloaded()) {
|
||||
prog!!.onMediaAlreadyPresent(m)
|
||||
} else {
|
||||
try {
|
||||
m.download()
|
||||
prog!!.onMediaDownloaded(m)
|
||||
} catch (e: TimeoutException) {
|
||||
// do nothing - skip this file
|
||||
prog!!.onMediaSkipped()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
prog!!.onMediaDownloadFinished()
|
||||
}
|
||||
|
||||
private fun makeIdList(start: Int, end: Int): List<Integer> {
|
||||
val a = LinkedList<Integer>()
|
||||
for (i in start..end) a.add(i)
|
||||
return a
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal var download_client: TelegramClient? = null
|
||||
internal var last_download_succeeded = true
|
||||
internal val logger = LoggerFactory.getLogger(DownloadManager::class.java)
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
fun downloadFile(client: TelegramClient, targetFilename: String, size: Int, dcId: Int, volumeId: Long, localId: Int, secret: Long) {
|
||||
val loc = TLInputFileLocation(volumeId, localId, secret)
|
||||
downloadFileFromDc(client, targetFilename, loc, dcId, size)
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
fun downloadFile(client: TelegramClient, targetFilename: String, size: Int, dcId: Int, id: Long, accessHash: Long) {
|
||||
val loc = TLInputDocumentFileLocation(id, accessHash)
|
||||
downloadFileFromDc(client, targetFilename, loc, dcId, size)
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
private fun downloadFileFromDc(client: TelegramClient, target: String, loc: TLAbsInputFileLocation, dcID: Integer?, size: Int): Boolean {
|
||||
var fos: FileOutputStream? = null
|
||||
try {
|
||||
val temp_filename = target + ".downloading"
|
||||
logger.debug("Downloading file {}", target)
|
||||
logger.trace("Temporary filename: {}", temp_filename)
|
||||
|
||||
var offset = 0
|
||||
if (File(temp_filename).isFile()) {
|
||||
logger.info("Temporary filename already exists; continuing this file")
|
||||
offset = File(temp_filename).length()
|
||||
if (offset >= size) {
|
||||
logger.warn("Temporary file size is >= the target size. Assuming corrupt file & deleting it")
|
||||
File(temp_filename).delete()
|
||||
offset = 0
|
||||
}
|
||||
}
|
||||
logger.trace("offset before the loop is {}", offset)
|
||||
fos = FileOutputStream(temp_filename, true)
|
||||
var response: TLFile? = null
|
||||
var try_again: Boolean
|
||||
do {
|
||||
try_again = false
|
||||
logger.trace("offset: {} block_size: {} size: {}", offset, size, size)
|
||||
val req = TLRequestUploadGetFile(loc, offset, size)
|
||||
try {
|
||||
if (dcID == null) {
|
||||
response = download_client!!.executeRpcQuery(req) as TLFile
|
||||
} else {
|
||||
response = download_client!!.executeRpcQuery(req, dcID) as TLFile
|
||||
}
|
||||
} catch (e: RpcErrorException) {
|
||||
if (e.getTag().startsWith("420: FLOOD_WAIT_")) {
|
||||
try_again = true
|
||||
Utils.obeyFloodWaitException(e)
|
||||
} else if (e.getCode() === 400) {
|
||||
//Somehow this file is broken. No idea why. Let's skip it for now
|
||||
try_again = true
|
||||
return false
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
offset += response!!.getBytes().getData().length
|
||||
logger.trace("response: {} total size: {}", response!!.getBytes().getData().length, offset)
|
||||
|
||||
fos!!.write(response!!.getBytes().getData())
|
||||
fos!!.flush()
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(Config.DELAY_AFTER_GET_FILE)
|
||||
} catch (e: InterruptedException) {
|
||||
}
|
||||
|
||||
} while (offset < size && (response!!.getBytes().getData().length > 0 || try_again))
|
||||
fos!!.close()
|
||||
if (offset < size) {
|
||||
System.out.println("Requested file $target with $size bytes, but got only $offset bytes.")
|
||||
File(temp_filename).delete()
|
||||
System.exit(1)
|
||||
}
|
||||
logger.trace("Renaming {} to {}", temp_filename, target)
|
||||
var rename_tries = 0
|
||||
var last_exception: IOException? = null
|
||||
while (rename_tries <= Config.RENAMING_MAX_TRIES) {
|
||||
rename_tries++
|
||||
try {
|
||||
Files.move(File(temp_filename).toPath(), File(target).toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
last_exception = null
|
||||
break
|
||||
} catch (e: IOException) {
|
||||
logger.debug("Exception during move. rename_tries: {}. Exception: {}", rename_tries, e)
|
||||
last_exception = e
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(Config.RENAMING_DELAY)
|
||||
} catch (e2: InterruptedException) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if (last_exception != null) {
|
||||
throw last_exception
|
||||
}
|
||||
last_download_succeeded = true
|
||||
return true
|
||||
} catch (ex: java.io.IOException) {
|
||||
if (fos != null) fos!!.close()
|
||||
System.out.println("IOException happened while downloading " + target)
|
||||
throw ex
|
||||
} catch (ex: RpcErrorException) {
|
||||
if (fos != null) fos!!.close()
|
||||
if (ex.getCode() === 500) {
|
||||
if (!last_download_succeeded) {
|
||||
System.out.println("Got an Internal Server Error from Telegram. Since the file downloaded before also happened to get this error, we will stop downloading now. Please try again later.")
|
||||
throw ex
|
||||
}
|
||||
last_download_succeeded = false
|
||||
System.out.println("Got an Internal Server Error from Telegram. Skipping this file for now. Next run of telegram_backup will continue to download this file.")
|
||||
logger.warn(ex.toString())
|
||||
return false
|
||||
}
|
||||
System.out.println("RpcErrorException happened while downloading " + target)
|
||||
throw ex
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun downloadExternalFile(target: String, url: String): Boolean {
|
||||
FileUtils.copyURLToFile(URL(url), File(target), 5000, 5000)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||
|
||||
interface DownloadProgressInterface {
|
||||
fun onMessageDownloadStart(count: Int, source: String)
|
||||
fun onMessageDownloaded(number: Int)
|
||||
fun onMessageDownloadFinished()
|
||||
|
||||
fun onMediaDownloadStart(count: Int)
|
||||
fun onMediaDownloaded(a: AbstractMediaFileManager)
|
||||
fun onMediaDownloadedEmpty()
|
||||
fun onMediaSkipped()
|
||||
fun onMediaAlreadyPresent(a: AbstractMediaFileManager)
|
||||
fun onMediaDownloadFinished()
|
||||
}
|
101
src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt
Normal file
101
src/main/kotlin/de/fabianonline/telegram_backup/GUIController.kt
Normal file
@ -0,0 +1,101 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import javax.swing.*
|
||||
import javax.swing.event.ListSelectionEvent
|
||||
import javax.swing.event.ListSelectionListener
|
||||
import java.awt.*
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.ActionListener
|
||||
import java.util.Vector
|
||||
|
||||
class GUIController {
|
||||
init {
|
||||
showAccountChooserDialog()
|
||||
}
|
||||
|
||||
private fun showAccountChooserDialog() {
|
||||
val accountChooser = JDialog()
|
||||
accountChooser.setTitle("Choose account")
|
||||
accountChooser.setSize(400, 200)
|
||||
val vert = JPanel()
|
||||
vert.setLayout(BorderLayout())
|
||||
vert.add(JLabel("Please select the account to use or create a new one."), BorderLayout.NORTH)
|
||||
val accounts = Utils.getAccounts()
|
||||
val list = JList<String>(accounts)
|
||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
vert.add(list, BorderLayout.CENTER)
|
||||
val bottom = JPanel(GridLayout(1, 2))
|
||||
val btnAddAccount = JButton("Add account")
|
||||
bottom.add(btnAddAccount)
|
||||
val btnLogin = JButton("Login")
|
||||
btnLogin.setEnabled(false)
|
||||
bottom.add(btnLogin)
|
||||
vert.add(bottom, BorderLayout.SOUTH)
|
||||
accountChooser.add(vert)
|
||||
accountChooser.setVisible(true)
|
||||
accountChooser.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
|
||||
list.addListSelectionListener(object : ListSelectionListener() {
|
||||
@Override
|
||||
fun valueChanged(e: ListSelectionEvent) {
|
||||
btnLogin.setEnabled(true)
|
||||
}
|
||||
})
|
||||
|
||||
btnAddAccount.addActionListener(object : ActionListener() {
|
||||
@Override
|
||||
fun actionPerformed(e: ActionEvent) {
|
||||
accountChooser.setVisible(false)
|
||||
accountChooser.dispose()
|
||||
addAccountDialog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun addAccountDialog() {
|
||||
val loginDialog = JDialog()
|
||||
loginDialog.setTitle("Add an account")
|
||||
loginDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
|
||||
val sections = JPanel()
|
||||
sections.setLayout(BoxLayout(sections, BoxLayout.Y_AXIS))
|
||||
|
||||
val top = JPanel()
|
||||
top.setLayout(BoxLayout(top, BoxLayout.Y_AXIS))
|
||||
top.add(JLabel("Please enter your phone number in international format:"))
|
||||
top.add(JTextField("+49123773212"))
|
||||
|
||||
sections.add(top)
|
||||
sections.add(Box.createVerticalStrut(5))
|
||||
sections.add(JSeparator(SwingConstants.HORIZONTAL))
|
||||
|
||||
val middle = JPanel()
|
||||
middle.setLayout(BoxLayout(middle, BoxLayout.Y_AXIS))
|
||||
middle.add(JLabel("Telegram sent you a code. Enter it here:"))
|
||||
middle.add(JTextField())
|
||||
middle.setEnabled(false)
|
||||
|
||||
sections.add(middle)
|
||||
sections.add(Box.createVerticalStrut(5))
|
||||
sections.add(JSeparator(SwingConstants.HORIZONTAL))
|
||||
|
||||
loginDialog.add(sections)
|
||||
loginDialog.setVisible(true)
|
||||
}
|
||||
}
|
16
src/main/kotlin/de/fabianonline/telegram_backup/LICENSE
Normal file
16
src/main/kotlin/de/fabianonline/telegram_backup/LICENSE
Normal file
@ -0,0 +1,16 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
@ -0,0 +1,52 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import java.lang.StringBuilder
|
||||
import java.io.File
|
||||
|
||||
object StickerConverter {
|
||||
fun makeFilenameWithPath(attr: TLDocumentAttributeSticker): String {
|
||||
val file = StringBuilder()
|
||||
file.append(makePath())
|
||||
file.append(makeFilename(attr))
|
||||
return file.toString()
|
||||
}
|
||||
|
||||
fun makeFilename(attr: TLDocumentAttributeSticker): String {
|
||||
val file = StringBuilder()
|
||||
if (attr.getStickerset() is TLInputStickerSetShortName) {
|
||||
file.append((attr.getStickerset() as TLInputStickerSetShortName).getShortName())
|
||||
} else if (attr.getStickerset() is TLInputStickerSetID) {
|
||||
file.append((attr.getStickerset() as TLInputStickerSetID).getId())
|
||||
}
|
||||
file.append("_")
|
||||
file.append(attr.getAlt().hashCode())
|
||||
file.append(".webp")
|
||||
return file.toString()
|
||||
}
|
||||
|
||||
fun makePath(): String {
|
||||
val path = Config.FILE_BASE +
|
||||
File.separatorChar +
|
||||
Config.FILE_STICKER_BASE +
|
||||
File.separatorChar
|
||||
File(path).mkdirs()
|
||||
return path
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.tl.TLContext
|
||||
import com.github.badoualy.telegram.tl.api.account.TLPassword
|
||||
import com.github.badoualy.telegram.tl.core.TLMethod
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
import com.github.badoualy.telegram.tl.StreamUtils.readTLObject
|
||||
|
||||
class TLRequestAccountGetPasswordWithCurrentSalt : TLMethod<TLPassword>() {
|
||||
private val _constructor = "account.getPassword#548a30f5"
|
||||
val constructorId: Int
|
||||
get() = CONSTRUCTOR_ID
|
||||
|
||||
@Throws(IOException::class)
|
||||
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()")
|
||||
return response as TLPassword
|
||||
}
|
||||
|
||||
fun toString(): String {
|
||||
return _constructor
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CONSTRUCTOR_ID = 0x548a30f5
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.api.UpdateCallback
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.api.Kotlogram
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.core.TLVector
|
||||
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.AbstractMediaFileManager
|
||||
import de.fabianonline.telegram_backup.mediafilemanager.FileManagerFactory
|
||||
|
||||
internal class TelegramUpdateHandler : UpdateCallback {
|
||||
private var user: UserManager? = null
|
||||
private var db: Database? = null
|
||||
var debug = false
|
||||
|
||||
fun activate() {
|
||||
this.user = UserManager.getInstance()
|
||||
this.db = Database.getInstance()
|
||||
}
|
||||
|
||||
fun onUpdates(c: TelegramClient, u: TLUpdates) {
|
||||
if (db == null) return
|
||||
if (debug) System.out.println("onUpdates - " + u.getUpdates().size() + " Updates, " + u.getUsers().size() + " Users, " + u.getChats().size() + " Chats")
|
||||
for (update in u.getUpdates()) {
|
||||
processUpdate(update, c)
|
||||
if (debug) System.out.println(" " + update.getClass().getName())
|
||||
}
|
||||
db!!.saveUsers(u.getUsers())
|
||||
db!!.saveChats(u.getChats())
|
||||
}
|
||||
|
||||
fun onUpdatesCombined(c: TelegramClient, u: TLUpdatesCombined) {
|
||||
if (db == null) return
|
||||
if (debug) System.out.println("onUpdatesCombined")
|
||||
for (update in u.getUpdates()) {
|
||||
processUpdate(update, c)
|
||||
}
|
||||
db!!.saveUsers(u.getUsers())
|
||||
db!!.saveChats(u.getChats())
|
||||
}
|
||||
|
||||
fun onUpdateShort(c: TelegramClient, u: TLUpdateShort) {
|
||||
if (db == null) return
|
||||
if (debug) System.out.println("onUpdateShort")
|
||||
processUpdate(u.getUpdate(), c)
|
||||
if (debug) System.out.println(" " + u.getUpdate().getClass().getName())
|
||||
}
|
||||
|
||||
fun onShortChatMessage(c: TelegramClient, m: TLUpdateShortChatMessage) {
|
||||
if (db == null) return
|
||||
if (debug) System.out.println("onShortChatMessage - " + m.getMessage())
|
||||
val msg = TLMessage(
|
||||
m.getOut(),
|
||||
m.getMentioned(),
|
||||
m.getMediaUnread(),
|
||||
m.getSilent(),
|
||||
false,
|
||||
m.getId(),
|
||||
m.getFromId(),
|
||||
TLPeerChat(m.getChatId()),
|
||||
m.getFwdFrom(),
|
||||
m.getViaBotId(),
|
||||
m.getReplyToMsgId(),
|
||||
m.getDate(),
|
||||
m.getMessage(), null, null,
|
||||
m.getEntities(), null, null)
|
||||
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
|
||||
vector.add(msg)
|
||||
db!!.saveMessages(vector, Kotlogram.API_LAYER)
|
||||
System.out.print('.')
|
||||
}
|
||||
|
||||
fun onShortMessage(c: TelegramClient, m: TLUpdateShortMessage) {
|
||||
if (db == null) return
|
||||
if (debug) System.out.println("onShortMessage - " + m.getOut() + " - " + m.getUserId() + " - " + m.getMessage())
|
||||
val from_id: Int
|
||||
val to_id: Int
|
||||
if (m.getOut() === true) {
|
||||
from_id = user!!.getUser().getId()
|
||||
to_id = m.getUserId()
|
||||
} else {
|
||||
to_id = user!!.getUser().getId()
|
||||
from_id = m.getUserId()
|
||||
}
|
||||
val msg = TLMessage(
|
||||
m.getOut(),
|
||||
m.getMentioned(),
|
||||
m.getMediaUnread(),
|
||||
m.getSilent(),
|
||||
false,
|
||||
m.getId(),
|
||||
from_id,
|
||||
TLPeerUser(to_id),
|
||||
m.getFwdFrom(),
|
||||
m.getViaBotId(),
|
||||
m.getReplyToMsgId(),
|
||||
m.getDate(),
|
||||
m.getMessage(), null, null,
|
||||
m.getEntities(), null, null)
|
||||
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
|
||||
vector.add(msg)
|
||||
db!!.saveMessages(vector, Kotlogram.API_LAYER)
|
||||
System.out.print('.')
|
||||
}
|
||||
|
||||
fun onShortSentMessage(c: TelegramClient, m: TLUpdateShortSentMessage) {
|
||||
if (db == null) return
|
||||
System.out.println("onShortSentMessage")
|
||||
}
|
||||
|
||||
fun onUpdateTooLong(c: TelegramClient) {
|
||||
if (db == null) return
|
||||
System.out.println("onUpdateTooLong")
|
||||
}
|
||||
|
||||
private fun processUpdate(update: TLAbsUpdate, client: TelegramClient) {
|
||||
if (update is TLUpdateNewMessage) {
|
||||
val abs_msg = (update as TLUpdateNewMessage).getMessage()
|
||||
val vector = TLVector<TLAbsMessage>(TLAbsMessage::class.java)
|
||||
vector.add(abs_msg)
|
||||
db!!.saveMessages(vector, Kotlogram.API_LAYER)
|
||||
System.out.print('.')
|
||||
if (abs_msg is TLMessage) {
|
||||
val fm = FileManagerFactory.getFileManager(abs_msg as TLMessage, user, client)
|
||||
if (fm != null && !fm!!.isEmpty() && !fm!!.isDownloaded()) {
|
||||
try {
|
||||
fm!!.download()
|
||||
} catch (e: Exception) {
|
||||
System.out.println("We got an exception while downloading media, but we're going to ignore it.")
|
||||
System.out.println("Here it is anyway:")
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ignore everything else...
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
import java.sql.Statement
|
||||
import java.sql.SQLException
|
||||
import java.sql.ResultSet
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
internal object TestFeatures {
|
||||
fun test1() {
|
||||
// Tests entries in a cache4.db in the current working directory for compatibility
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC")
|
||||
} catch (e: ClassNotFoundException) {
|
||||
CommandLineController.show_error("Could not load jdbc-sqlite class.")
|
||||
}
|
||||
|
||||
val path = "jdbc:sqlite:cache4.db"
|
||||
|
||||
var conn: Connection? = null
|
||||
var stmt: Statement? = null
|
||||
|
||||
try {
|
||||
conn = DriverManager.getConnection(path)
|
||||
stmt = conn!!.createStatement()
|
||||
} catch (e: SQLException) {
|
||||
CommandLineController.show_error("Could not connect to SQLITE database.")
|
||||
}
|
||||
|
||||
var unsupported_constructor = 0
|
||||
var success = 0
|
||||
|
||||
try {
|
||||
val rs = stmt!!.executeQuery("SELECT data FROM messages")
|
||||
while (rs.next()) {
|
||||
try {
|
||||
TLApiContext.getInstance().deserializeMessage(rs.getBytes(1))
|
||||
} catch (e: com.github.badoualy.telegram.tl.exception.UnsupportedConstructorException) {
|
||||
unsupported_constructor++
|
||||
} catch (e: IOException) {
|
||||
System.out.println("IOException: " + e)
|
||||
}
|
||||
|
||||
success++
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
System.out.println("SQL exception: " + e)
|
||||
}
|
||||
|
||||
System.out.println("Success: " + success)
|
||||
System.out.println("Unsupported constructor: " + unsupported_constructor)
|
||||
}
|
||||
|
||||
fun test2(user: UserManager, client: TelegramClient) {
|
||||
// Prints system.encoding and default charset
|
||||
System.out.println("Default Charset: " + Charset.defaultCharset())
|
||||
System.out.println("file.encoding: " + System.getProperty("file.encoding"))
|
||||
val db = Database.getInstance()
|
||||
System.out.println("Database encoding: " + db.getEncoding())
|
||||
}
|
||||
}
|
140
src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt
Normal file
140
src/main/kotlin/de/fabianonline/telegram_backup/UserManager.kt
Normal file
@ -0,0 +1,140 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.api.auth.TLSentCode
|
||||
import com.github.badoualy.telegram.tl.api.auth.TLAuthorization
|
||||
import com.github.badoualy.telegram.tl.api.TLUser
|
||||
import com.github.badoualy.telegram.tl.api.TLUserFull
|
||||
import com.github.badoualy.telegram.tl.api.TLInputUserSelf
|
||||
import com.github.badoualy.telegram.tl.api.account.TLPassword
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.core.TLBytes
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.Logger
|
||||
|
||||
class UserManager @Throws(IOException::class)
|
||||
private constructor(c: TelegramClient) {
|
||||
var user: TLUser? = null
|
||||
var phone: String? = null
|
||||
private var code: String? = null
|
||||
private val client: TelegramClient? = null
|
||||
private var sent_code: TLSentCode? = null
|
||||
private var auth: TLAuthorization? = null
|
||||
var isPasswordNeeded = false
|
||||
private set
|
||||
|
||||
val isLoggedIn: Boolean
|
||||
get() = user != null
|
||||
|
||||
val userString: String
|
||||
get() {
|
||||
if (this.user == null) return "Not logged in"
|
||||
val sb = StringBuilder()
|
||||
if (this.user!!.getFirstName() != null) {
|
||||
sb.append(this.user!!.getFirstName())
|
||||
}
|
||||
if (this.user!!.getLastName() != null) {
|
||||
sb.append(" ")
|
||||
sb.append(this.user!!.getLastName())
|
||||
}
|
||||
if (this.user!!.getUsername() != null) {
|
||||
sb.append(" (@")
|
||||
sb.append(this.user!!.getUsername())
|
||||
sb.append(")")
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
val fileBase: String
|
||||
get() = Config.FILE_BASE + File.separatorChar + "+" + this.user!!.getPhone() + File.separatorChar
|
||||
|
||||
init {
|
||||
this.client = c
|
||||
logger.debug("Calling getFullUser")
|
||||
try {
|
||||
val full_user = this.client!!.usersGetFullUser(TLInputUserSelf())
|
||||
this.user = full_user.getUser().getAsUser()
|
||||
} catch (e: RpcErrorException) {
|
||||
// This may happen. Ignoring it.
|
||||
logger.debug("Ignoring exception:", e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class)
|
||||
fun sendCodeToPhoneNumber(number: String) {
|
||||
this.phone = number
|
||||
this.sent_code = this.client!!.authSendCode(false, this.phone, true)
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class)
|
||||
fun verifyCode(code: String) {
|
||||
this.code = code
|
||||
try {
|
||||
this.auth = client!!.authSignIn(phone, this.sent_code!!.getPhoneCodeHash(), this.code)
|
||||
this.user = auth!!.getUser().getAsUser()
|
||||
} catch (e: RpcErrorException) {
|
||||
if (e.getCode() !== 401 || !e.getTag().equals("SESSION_PASSWORD_NEEDED")) throw e
|
||||
this.isPasswordNeeded = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class)
|
||||
fun verifyPassword(pw: String) {
|
||||
val password = pw.getBytes("UTF-8")
|
||||
val salt = (client!!.accountGetPassword() as TLPassword).getCurrentSalt().getData()
|
||||
var md: MessageDigest? = null
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
e.printStackTrace()
|
||||
return
|
||||
}
|
||||
|
||||
val salted = ByteArray(2 * salt.size + password.size)
|
||||
System.arraycopy(salt, 0, salted, 0, salt.size)
|
||||
System.arraycopy(password, 0, salted, salt.size, password.size)
|
||||
System.arraycopy(salt, 0, salted, salt.size + password.size, salt.size)
|
||||
val hash = md!!.digest(salted)
|
||||
auth = client!!.authCheckPassword(TLBytes(hash))
|
||||
this.user = auth!!.getUser().getAsUser()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(UserManager::class.java)
|
||||
private var instance: UserManager? = null
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun init(c: TelegramClient) {
|
||||
instance = UserManager(c)
|
||||
}
|
||||
|
||||
fun getInstance(): UserManager {
|
||||
if (instance == null) throw RuntimeException("UserManager is not yet initialized.")
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
184
src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt
Normal file
184
src/main/kotlin/de/fabianonline/telegram_backup/Utils.kt
Normal file
@ -0,0 +1,184 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import java.io.File
|
||||
import java.util.Vector
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.google.gson.*
|
||||
import java.net.URL
|
||||
import org.apache.commons.io.IOUtils
|
||||
import de.fabianonline.telegram_backup.Version
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object Utils {
|
||||
val VERSIONS_EQUAL = 0
|
||||
val VERSION_1_NEWER = 1
|
||||
val VERSION_2_NEWER = 2
|
||||
|
||||
private val logger = LoggerFactory.getLogger(Utils::class.java) as Logger
|
||||
|
||||
internal val accounts: Vector<String>
|
||||
get() {
|
||||
val accounts = Vector<String>()
|
||||
val folder = File(Config.FILE_BASE)
|
||||
val files = folder.listFiles()
|
||||
if (files != null)
|
||||
for (f in files!!) {
|
||||
if (f.isDirectory() && f.getName().startsWith("+")) {
|
||||
accounts.add(f.getName())
|
||||
}
|
||||
}
|
||||
return accounts
|
||||
}
|
||||
|
||||
internal val newestVersion: Version?
|
||||
get() {
|
||||
try {
|
||||
val data_url = "https://api.github.com/repos/fabianonline/telegram_backup/releases"
|
||||
logger.debug("Requesting current release info from {}", data_url)
|
||||
val json = IOUtils.toString(URL(data_url))
|
||||
val parser = JsonParser()
|
||||
val root_elm = parser.parse(json)
|
||||
if (root_elm.isJsonArray()) {
|
||||
val root = root_elm.getAsJsonArray()
|
||||
var newest_version: JsonObject? = null
|
||||
for (e in root)
|
||||
if (e.isJsonObject()) {
|
||||
val version = e.getAsJsonObject()
|
||||
if (version.getAsJsonPrimitive("prerelease").getAsBoolean() === false) {
|
||||
newest_version = version
|
||||
break
|
||||
}
|
||||
}
|
||||
if (newest_version == null) return null
|
||||
val new_v = newest_version!!.getAsJsonPrimitive("tag_name").getAsString()
|
||||
logger.debug("Found current release version {}", new_v)
|
||||
val cur_v = Config.APP_APPVER
|
||||
|
||||
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 null
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class)
|
||||
@JvmOverloads internal fun obeyFloodWaitException(e: RpcErrorException?, silent: Boolean = false) {
|
||||
if (e == null || e!!.getCode() !== 420) return
|
||||
|
||||
val delay = e!!.getTagInteger()
|
||||
if (!silent) {
|
||||
System.out.println("")
|
||||
System.out.println(
|
||||
"Telegram complained about us (okay, me) making too many requests in too short time by\n" +
|
||||
"sending us \"" + e!!.getTag() + "\" as an error. So we now have to wait a bit. Telegram\n" +
|
||||
"asked us to wait for " + delay + " seconds.\n" +
|
||||
"\n" +
|
||||
"So I'm going to do just that for now. If you don't want to wait, you can quit by pressing\n" +
|
||||
"Ctrl+C. You can restart me at any time and I will just continue to download your\n" +
|
||||
"messages and media. But be advised that just restarting me is not going to change\n" +
|
||||
"the fact that Telegram won't talk to me until then.")
|
||||
System.out.println("")
|
||||
}
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(delay + 1)
|
||||
} catch (e2: InterruptedException) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun compareVersions(v1: String, v2: String): Int {
|
||||
logger.debug("Comparing versions {} and {}.", v1, v2)
|
||||
if (v1.equals(v2)) return VERSIONS_EQUAL
|
||||
|
||||
val v1_p = v1.split("-", 2)
|
||||
val v2_p = v2.split("-", 2)
|
||||
|
||||
logger.trace("Parts to compare without suffixes: {} and {}.", v1_p[0], v2_p[0])
|
||||
|
||||
val v1_p2 = v1_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)
|
||||
|
||||
var i: Int
|
||||
i = 0
|
||||
while (i < v1_p2.size && i < v2_p2.size) {
|
||||
val i_1 = Integer.parseInt(v1_p2[i])
|
||||
val i_2 = Integer.parseInt(v2_p2[i])
|
||||
logger.trace("Comparing parts: {} and {}.", i_1, i_2)
|
||||
if (i_1 > i_2) {
|
||||
logger.debug("v1 is newer")
|
||||
return VERSION_1_NEWER
|
||||
} else if (i_2 > i_1) {
|
||||
logger.debug("v2 is newer")
|
||||
return VERSION_2_NEWER
|
||||
}
|
||||
i++
|
||||
}
|
||||
logger.trace("At least one of the versions has run out of parts.")
|
||||
if (v1_p2.size > v2_p2.size) {
|
||||
logger.debug("v1 is longer, so it is newer")
|
||||
return VERSION_1_NEWER
|
||||
} else if (v2_p2.size > v1_p2.size) {
|
||||
logger.debug("v2 is longer, so it is newer")
|
||||
return VERSION_2_NEWER
|
||||
}
|
||||
|
||||
// startsWith
|
||||
if (v1_p.size > 1 && v2_p.size == 1) {
|
||||
logger.debug("v1 has a suffix, v2 not.")
|
||||
if (v1_p[1].startsWith("pre")) {
|
||||
logger.debug("v1 is a pre version, so v1 is newer")
|
||||
return VERSION_2_NEWER
|
||||
} else {
|
||||
return VERSION_1_NEWER
|
||||
}
|
||||
} else if (v1_p.size == 1 && v2_p.size > 1) {
|
||||
logger.debug("v1 has no suffix, but v2 has")
|
||||
if (v2_p[1].startsWith("pre")) {
|
||||
logger.debug("v2 is a pre version, so v1 is better")
|
||||
return VERSION_1_NEWER
|
||||
} else {
|
||||
return VERSION_2_NEWER
|
||||
}
|
||||
} else if (v1_p.size > 1 && v2_p.size > 1) {
|
||||
logger.debug("Both have a suffix")
|
||||
if (v1_p[1].startsWith("pre") && !v2_p[1].startsWith("pre")) {
|
||||
logger.debug("v1 is a 'pre' version, v2 not.")
|
||||
return VERSION_2_NEWER
|
||||
} else if (!v1_p[1].startsWith("pre") && v2_p[1].startsWith("pre")) {
|
||||
logger.debug("v2 is a 'pre' version, v2 not.")
|
||||
return VERSION_1_NEWER
|
||||
}
|
||||
return VERSIONS_EQUAL
|
||||
}
|
||||
logger.debug("We couldn't find a real difference, so we're assuming the versions are equal-ish.")
|
||||
return VERSIONS_EQUAL
|
||||
}
|
||||
|
||||
fun anonymize(str: String): String {
|
||||
return if (!CommandLineOptions.cmd_anonymize) str else str.replaceAll("[0-9]", "1").replaceAll("[A-Z]", "A").replaceAll("[a-z]", "a") + " (ANONYMIZED)"
|
||||
}
|
||||
}
|
19
src/main/kotlin/de/fabianonline/telegram_backup/Version.kt
Normal file
19
src/main/kotlin/de/fabianonline/telegram_backup/Version.kt
Normal file
@ -0,0 +1,19 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup
|
||||
|
||||
class Version(val version: String, val url: String, val body: String, val isNewer: Boolean)
|
@ -0,0 +1,186 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.exporter
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.Utils
|
||||
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.OutputStreamWriter
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.Charset
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.io.FileNotFoundException
|
||||
import java.net.URL
|
||||
import org.apache.commons.io.FileUtils
|
||||
import java.util.LinkedList
|
||||
import java.util.HashMap
|
||||
|
||||
import com.github.mustachejava.DefaultMustacheFactory
|
||||
import com.github.mustachejava.Mustache
|
||||
import com.github.mustachejava.MustacheFactory
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class HTMLExporter {
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun export() {
|
||||
try {
|
||||
val user = UserManager.getInstance()
|
||||
val db = Database.getInstance()
|
||||
|
||||
// Create base dir
|
||||
logger.debug("Creating base dir")
|
||||
val base = user.getFileBase() + "files" + File.separatorChar
|
||||
File(base).mkdirs()
|
||||
File(base + "dialogs").mkdirs()
|
||||
|
||||
logger.debug("Fetching dialogs")
|
||||
val dialogs = db.getListOfDialogsForExport()
|
||||
logger.trace("Got {} dialogs", dialogs.size())
|
||||
logger.debug("Fetching chats")
|
||||
val chats = db.getListOfChatsForExport()
|
||||
logger.trace("Got {} chats", chats.size())
|
||||
|
||||
logger.debug("Generating index.html")
|
||||
val scope = HashMap<String, Object>()
|
||||
scope.put("user", user)
|
||||
scope.put("dialogs", dialogs)
|
||||
scope.put("chats", chats)
|
||||
|
||||
// Collect stats data
|
||||
scope.put("count.chats", chats.size())
|
||||
scope.put("count.dialogs", dialogs.size())
|
||||
|
||||
var count_messages_chats = 0
|
||||
var count_messages_dialogs = 0
|
||||
for (c in chats) count_messages_chats += c.count
|
||||
for (d in dialogs) count_messages_dialogs += d.count
|
||||
|
||||
scope.put("count.messages", count_messages_chats + count_messages_dialogs)
|
||||
scope.put("count.messages.chats", count_messages_chats)
|
||||
scope.put("count.messages.dialogs", count_messages_dialogs)
|
||||
|
||||
scope.put("count.messages.from_me", db.getMessagesFromUserCount())
|
||||
|
||||
scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix()))
|
||||
|
||||
scope.putAll(db.getMessageAuthorsWithCount())
|
||||
scope.putAll(db.getMessageTypesWithCount())
|
||||
scope.putAll(db.getMessageMediaTypesWithCount())
|
||||
|
||||
val mf = DefaultMustacheFactory()
|
||||
var mustache = mf.compile("templates/html/index.mustache")
|
||||
var w = getWriter(base + "index.html")
|
||||
mustache.execute(w, scope)
|
||||
w.close()
|
||||
|
||||
mustache = mf.compile("templates/html/chat.mustache")
|
||||
|
||||
var i = 0
|
||||
logger.debug("Generating {} dialog pages", dialogs.size())
|
||||
for (d in dialogs) {
|
||||
i++
|
||||
logger.trace("Dialog {}/{}: {}", i, dialogs.size(), Utils.anonymize("" + d.id))
|
||||
val messages = db.getMessagesForExport(d)
|
||||
scope.clear()
|
||||
scope.put("user", user)
|
||||
scope.put("dialog", d)
|
||||
scope.put("messages", messages)
|
||||
|
||||
scope.putAll(db.getMessageAuthorsWithCount(d))
|
||||
scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(d)))
|
||||
scope.putAll(db.getMessageTypesWithCount(d))
|
||||
scope.putAll(db.getMessageMediaTypesWithCount(d))
|
||||
|
||||
w = getWriter(base + "dialogs" + File.separatorChar + "user_" + d.id + ".html")
|
||||
mustache.execute(w, scope)
|
||||
w.close()
|
||||
}
|
||||
|
||||
i = 0
|
||||
logger.debug("Generating {} chat pages", chats.size())
|
||||
for (c in chats) {
|
||||
i++
|
||||
logger.trace("Chat {}/{}: {}", i, chats.size(), Utils.anonymize("" + c.id))
|
||||
val messages = db.getMessagesForExport(c)
|
||||
scope.clear()
|
||||
scope.put("user", user)
|
||||
scope.put("chat", c)
|
||||
scope.put("messages", messages)
|
||||
|
||||
scope.putAll(db.getMessageAuthorsWithCount(c))
|
||||
scope.put("heatmap_data", intArrayToString(db.getMessageTimesMatrix(c)))
|
||||
scope.putAll(db.getMessageTypesWithCount(c))
|
||||
scope.putAll(db.getMessageMediaTypesWithCount(c))
|
||||
|
||||
w = getWriter(base + "dialogs" + File.separatorChar + "chat_" + c.id + ".html")
|
||||
mustache.execute(w, scope)
|
||||
w.close()
|
||||
}
|
||||
|
||||
logger.debug("Generating additional files")
|
||||
// Copy CSS
|
||||
val cssFile = getClass().getResource("/templates/html/style.css")
|
||||
val dest = File(base + "style.css")
|
||||
FileUtils.copyURLToFile(cssFile, dest)
|
||||
logger.debug("Done exporting.")
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
logger.error("Caught an exception!", e)
|
||||
throw e
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
private fun getWriter(filename: String): OutputStreamWriter {
|
||||
logger.trace("Creating writer for file {}", Utils.anonymize(filename))
|
||||
return OutputStreamWriter(FileOutputStream(filename), Charset.forName("UTF-8").newEncoder())
|
||||
}
|
||||
|
||||
private fun intArrayToString(data: Array<IntArray>): String {
|
||||
val sb = StringBuilder()
|
||||
sb.append("[")
|
||||
for (x in data.indices) {
|
||||
for (y in 0 until data[x].size) {
|
||||
if (x > 0 || y > 0) sb.append(",")
|
||||
sb.append("[" + x + "," + y + "," + data[x][y] + "]")
|
||||
}
|
||||
}
|
||||
sb.append("]")
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun mapToString(map: Map<String, Integer>): String {
|
||||
val sb = StringBuilder("[")
|
||||
for (entry in map.entrySet()) {
|
||||
sb.append("['" + entry.getKey() + "', " + entry.getValue() + "],")
|
||||
}
|
||||
sb.append("]")
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(HTMLExporter::class.java)
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.mediafilemanager
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.Config
|
||||
import de.fabianonline.telegram_backup.DownloadManager
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
abstract class AbstractMediaFileManager(protected var message: TLMessage, protected var user: UserManager, protected var client: TelegramClient) {
|
||||
var isEmpty = false
|
||||
protected set
|
||||
abstract val size: Int
|
||||
abstract val extension: String
|
||||
val isDownloaded: Boolean
|
||||
get() = File(targetPathAndFilename).isFile()
|
||||
val isDownloading: Boolean
|
||||
get() = File(targetPathAndFilename + ".downloading").isFile()
|
||||
val targetPath: String
|
||||
get() {
|
||||
val path = user.getFileBase() + Config.FILE_FILES_BASE + File.separatorChar
|
||||
File(path).mkdirs()
|
||||
return path
|
||||
}
|
||||
val targetFilename: String
|
||||
get() = if (message.getToId() is TLPeerChannel) {
|
||||
"channel_" + (message.getToId() as TLPeerChannel).getChannelId() + "_" + message.getId() + "." + extension
|
||||
} else "" + message.getId() + "." + extension
|
||||
val targetPathAndFilename: String
|
||||
get() = targetPath + targetFilename
|
||||
|
||||
abstract val letter: String
|
||||
abstract val name: String
|
||||
abstract val description: String
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
abstract fun download()
|
||||
|
||||
protected fun extensionFromMimetype(mime: String): String {
|
||||
when (mime) {
|
||||
"text/plain" -> return "txt"
|
||||
}
|
||||
|
||||
val i = mime.lastIndexOf('/')
|
||||
val ext = mime.substring(i + 1).toLowerCase()
|
||||
|
||||
return if (ext === "unknown") "dat" else ext
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun throwUnexpectedObjectError(o: Object) {
|
||||
throw RuntimeException("Unexpected " + o.getClass().getName())
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.mediafilemanager
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.DownloadManager
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class DocumentFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
|
||||
protected var doc: TLDocument? = null
|
||||
private var extension: String? = null
|
||||
|
||||
val isSticker: Boolean
|
||||
get() {
|
||||
var sticker: TLDocumentAttributeSticker? = null
|
||||
if (this.isEmpty || doc == null) return false
|
||||
if (doc!!.getAttributes() != null)
|
||||
for (attr in doc!!.getAttributes()) {
|
||||
if (attr is TLDocumentAttributeSticker) {
|
||||
sticker = attr as TLDocumentAttributeSticker
|
||||
}
|
||||
}
|
||||
return sticker != null
|
||||
}
|
||||
|
||||
val size: Int
|
||||
get() = if (doc != null) doc!!.getSize() else 0
|
||||
|
||||
val letter: String
|
||||
get() = "d"
|
||||
val name: String
|
||||
get() = "document"
|
||||
val description: String
|
||||
get() = "Document"
|
||||
|
||||
init {
|
||||
val d = (msg.getMedia() as TLMessageMediaDocument).getDocument()
|
||||
if (d is TLDocument) {
|
||||
this.doc = d as TLDocument
|
||||
} else if (d is TLDocumentEmpty) {
|
||||
this.isEmpty = true
|
||||
} else {
|
||||
throwUnexpectedObjectError(d)
|
||||
}
|
||||
}
|
||||
|
||||
fun getExtension(): String? {
|
||||
if (extension != null) return extension
|
||||
if (doc == null) return "empty"
|
||||
var ext: String? = null
|
||||
var original_filename: String? = null
|
||||
if (doc!!.getAttributes() != null)
|
||||
for (attr in doc!!.getAttributes()) {
|
||||
if (attr is TLDocumentAttributeFilename) {
|
||||
original_filename = (attr as TLDocumentAttributeFilename).getFileName()
|
||||
}
|
||||
}
|
||||
if (original_filename != null) {
|
||||
val i = original_filename.lastIndexOf('.')
|
||||
if (i > 0) ext = original_filename.substring(i + 1)
|
||||
|
||||
}
|
||||
if (ext == null) {
|
||||
ext = extensionFromMimetype(doc!!.getMimeType())
|
||||
}
|
||||
|
||||
// Sometimes, extensions contain a trailing double quote. Remove this. Fixes #12.
|
||||
ext = ext!!.replace("\"", "")
|
||||
|
||||
this.extension = ext
|
||||
return ext
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
fun download() {
|
||||
if (doc != null) {
|
||||
DownloadManager.downloadFile(client, getTargetPathAndFilename(), size, doc!!.getDcId(), doc!!.getId(), doc!!.getAccessHash())
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.mediafilemanager
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
object FileManagerFactory {
|
||||
fun getFileManager(m: TLMessage?, u: UserManager, c: TelegramClient): AbstractMediaFileManager? {
|
||||
if (m == null) return null
|
||||
val media = m!!.getMedia() ?: return null
|
||||
|
||||
if (media is TLMessageMediaPhoto) {
|
||||
return PhotoFileManager(m, u, c)
|
||||
} else if (media is TLMessageMediaDocument) {
|
||||
val d = DocumentFileManager(m, u, c)
|
||||
return if (d.isSticker()) {
|
||||
StickerFileManager(m, u, c)
|
||||
} else d
|
||||
} else if (media is TLMessageMediaGeo) {
|
||||
return GeoFileManager(m, u, c)
|
||||
} else if (media is TLMessageMediaEmpty) {
|
||||
return UnsupportedFileManager(m, u, c, "empty")
|
||||
} else if (media is TLMessageMediaUnsupported) {
|
||||
return UnsupportedFileManager(m, u, c, "unsupported")
|
||||
} else if (media is TLMessageMediaWebPage) {
|
||||
return UnsupportedFileManager(m, u, c, "webpage")
|
||||
} else if (media is TLMessageMediaContact) {
|
||||
return UnsupportedFileManager(m, u, c, "contact")
|
||||
} else if (media is TLMessageMediaVenue) {
|
||||
return UnsupportedFileManager(m, u, c, "venue")
|
||||
} else {
|
||||
AbstractMediaFileManager.throwUnexpectedObjectError(media)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.mediafilemanager
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.DownloadManager
|
||||
import de.fabianonline.telegram_backup.Config
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class GeoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
|
||||
protected var geo: TLGeoPoint
|
||||
|
||||
// We don't know the size, so we just guess.
|
||||
val size: Int
|
||||
get() {
|
||||
val f = File(getTargetPathAndFilename())
|
||||
return if (f.isFile()) f.length() else 100000
|
||||
}
|
||||
|
||||
val extension: String
|
||||
get() = "png"
|
||||
|
||||
val letter: String
|
||||
get() = "g"
|
||||
val name: String
|
||||
get() = "geo"
|
||||
val description: String
|
||||
get() = "Geolocation"
|
||||
|
||||
init {
|
||||
val g = (msg.getMedia() as TLMessageMediaGeo).getGeo()
|
||||
if (g is TLGeoPoint) {
|
||||
this.geo = g as TLGeoPoint
|
||||
} else if (g is TLGeoPointEmpty) {
|
||||
this.isEmpty = true
|
||||
} else {
|
||||
throwUnexpectedObjectError(g)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun download() {
|
||||
val url = "https://maps.googleapis.com/maps/api/staticmap?" +
|
||||
"center=" + geo.getLat() + "," + geo.getLong() + "&" +
|
||||
"zoom=14&size=300x150&scale=2&format=png&" +
|
||||
"key=" + Config.SECRET_GMAPS
|
||||
DownloadManager.downloadExternalFile(getTargetPathAndFilename(), url)
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.mediafilemanager
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.DownloadManager
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class PhotoFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : AbstractMediaFileManager(msg, user, client) {
|
||||
private var photo: TLPhoto? = null
|
||||
private var size: TLPhotoSize? = null
|
||||
|
||||
val extension: String
|
||||
get() = "jpg"
|
||||
|
||||
val letter: String
|
||||
get() = "p"
|
||||
val name: String
|
||||
get() = "photo"
|
||||
val description: String
|
||||
get() = "Photo"
|
||||
|
||||
init {
|
||||
val p = (msg.getMedia() as TLMessageMediaPhoto).getPhoto()
|
||||
if (p is TLPhoto) {
|
||||
this.photo = p as TLPhoto
|
||||
|
||||
var biggest: TLPhotoSize? = null
|
||||
for (s in photo!!.getSizes())
|
||||
if (s is TLPhotoSize) {
|
||||
val size = s as TLPhotoSize
|
||||
if (biggest == null || size.getW() > biggest!!.getW() && size.getH() > biggest!!.getH()) {
|
||||
biggest = size
|
||||
}
|
||||
}
|
||||
if (biggest == null) {
|
||||
throw RuntimeException("Could not find a size for a photo.")
|
||||
}
|
||||
this.size = biggest
|
||||
} else if (p is TLPhotoEmpty) {
|
||||
this.isEmpty = true
|
||||
} else {
|
||||
throwUnexpectedObjectError(p)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSize(): Int {
|
||||
return if (size != null) size!!.getSize() else 0
|
||||
}
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
fun download() {
|
||||
if (isEmpty) return
|
||||
val loc = size!!.getLocation() as TLFileLocation
|
||||
DownloadManager.downloadFile(client, getTargetPathAndFilename(), getSize(), loc.getDcId(), loc.getVolumeId(), loc.getLocalId(), loc.getSecret())
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.mediafilemanager
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.DownloadManager
|
||||
import de.fabianonline.telegram_backup.Config
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class StickerFileManager(msg: TLMessage, user: UserManager, client: TelegramClient) : DocumentFileManager(msg, user, client) {
|
||||
|
||||
val isSticker: Boolean
|
||||
get() = true
|
||||
|
||||
private val filenameBase: String
|
||||
get() {
|
||||
var sticker: TLDocumentAttributeSticker? = null
|
||||
for (attr in doc.getAttributes()) {
|
||||
if (attr is TLDocumentAttributeSticker) {
|
||||
sticker = attr as TLDocumentAttributeSticker
|
||||
}
|
||||
}
|
||||
|
||||
val file = StringBuilder()
|
||||
if (sticker!!.getStickerset() is TLInputStickerSetShortName) {
|
||||
file.append((sticker!!.getStickerset() as TLInputStickerSetShortName).getShortName())
|
||||
} else if (sticker!!.getStickerset() is TLInputStickerSetID) {
|
||||
file.append((sticker!!.getStickerset() as TLInputStickerSetID).getId())
|
||||
}
|
||||
file.append("_")
|
||||
file.append(sticker!!.getAlt().hashCode())
|
||||
return file.toString()
|
||||
}
|
||||
|
||||
val targetFilename: String
|
||||
get() = filenameBase + "." + extension
|
||||
|
||||
val targetPath: String
|
||||
get() {
|
||||
val path = user.getFileBase() + Config.FILE_FILES_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar
|
||||
File(path).mkdirs()
|
||||
return path
|
||||
}
|
||||
|
||||
val extension: String
|
||||
get() = "webp"
|
||||
|
||||
val letter: String
|
||||
get() = "s"
|
||||
val name: String
|
||||
get() = "sticker"
|
||||
val description: String
|
||||
get() = "Sticker"
|
||||
|
||||
@Throws(RpcErrorException::class, IOException::class, TimeoutException::class)
|
||||
fun download() {
|
||||
val old_file = Config.FILE_BASE + File.separatorChar + Config.FILE_STICKER_BASE + File.separatorChar + targetFilename
|
||||
|
||||
logger.trace("Old filename exists: {}", File(old_file).exists())
|
||||
|
||||
if (File(old_file).exists()) {
|
||||
Files.copy(Paths.get(old_file), Paths.get(getTargetPathAndFilename()), StandardCopyOption.REPLACE_EXISTING)
|
||||
return
|
||||
}
|
||||
super.download()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(StickerFileManager::class.java)
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/* Telegram_Backup
|
||||
* Copyright (C) 2016 Fabian Schlenz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package de.fabianonline.telegram_backup.mediafilemanager
|
||||
|
||||
import de.fabianonline.telegram_backup.UserManager
|
||||
import de.fabianonline.telegram_backup.Database
|
||||
import de.fabianonline.telegram_backup.StickerConverter
|
||||
import de.fabianonline.telegram_backup.DownloadProgressInterface
|
||||
import de.fabianonline.telegram_backup.DownloadManager
|
||||
import de.fabianonline.telegram_backup.Config
|
||||
|
||||
import com.github.badoualy.telegram.api.TelegramClient
|
||||
import com.github.badoualy.telegram.tl.core.TLIntVector
|
||||
import com.github.badoualy.telegram.tl.core.TLObject
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsMessages
|
||||
import com.github.badoualy.telegram.tl.api.messages.TLAbsDialogs
|
||||
import com.github.badoualy.telegram.tl.api.*
|
||||
import com.github.badoualy.telegram.tl.api.upload.TLFile
|
||||
import com.github.badoualy.telegram.tl.exception.RpcErrorException
|
||||
import com.github.badoualy.telegram.tl.api.request.TLRequestUploadGetFile
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedList
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class UnsupportedFileManager(msg: TLMessage, user: UserManager, client: TelegramClient, type: String) : AbstractMediaFileManager(msg, user, client) {
|
||||
var name: String? = null
|
||||
internal set
|
||||
|
||||
val targetFilename: String
|
||||
get() = ""
|
||||
|
||||
val targetPath: String
|
||||
get() = ""
|
||||
|
||||
val extension: String
|
||||
get() = ""
|
||||
|
||||
val size: Int
|
||||
get() = 0
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = false
|
||||
val isDownloaded: Boolean
|
||||
get() = false
|
||||
|
||||
val letter: String
|
||||
get() = " "
|
||||
val description: String
|
||||
get() = "Unsupported / non-downloadable Media"
|
||||
|
||||
init {
|
||||
this.name = type
|
||||
}
|
||||
|
||||
fun download() {}
|
||||
}
|
Reference in New Issue
Block a user