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

250 lines
7.9 KiB
Kotlin

/* Telegram_Backup
* Copyright (C) 2016 Fabian Schlenz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
package de.fabianonline.telegram_backup
import com.github.badoualy.telegram.tl.exception.RpcErrorException
import com.github.badoualy.telegram.tl.api.TLMessage
import java.io.File
import java.util.Vector
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import com.google.gson.*
import com.github.salomonbrys.kotson.*
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 {
@JvmField public val VERSIONS_EQUAL = 0
@JvmField public val VERSION_1_NEWER = 1
@JvmField public val VERSION_2_NEWER = 2
var hasSeenFloodWaitMessage = false
var anonymize = false
private val logger = LoggerFactory.getLogger(Utils::class.java) as Logger
fun print_accounts(file_base: String) {
println("List of available accounts:")
val accounts = getAccounts(file_base)
if (accounts.size > 0) {
for (str in accounts) {
println(" " + str.anonymize())
}
println("Use '--account <x>' to use one of those accounts.")
} else {
println("NO ACCOUNTS FOUND")
println("Use '--login' to login to a telegram account.")
}
}
fun getAccounts(file_base: String): Vector<String> {
val accounts = Vector<String>()
val folder = File(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
}
fun getNewestVersion(): Version? {
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
}
}
fun obeyFloodWait(max_tries: Int = -1, method: () -> Unit) {
var tries = 0
while (true) {
tries++
if (max_tries>0 && tries>max_tries) throw MaxTriesExceededException()
logger.trace("This is try ${tries}.")
try {
method.invoke()
// If we reach this, the method has returned successfully -> we are done
return
} catch(e: RpcErrorException) {
// If we got something else than a FLOOD_WAIT error, we just rethrow it
if (e.getCode() != 420) throw e
val delay = e.getTagInteger()!!.toLong()
if (!hasSeenFloodWaitMessage) {
println(
"\n" +
"Telegram complained about us (okay, me) making too many requests in too short time by\n" +
"sending us \"${e.getTag()}\" as an error. So we now have to wait a bit. Telegram\n" +
"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." +
"\n")
}
hasSeenFloodWaitMessage = true
try { TimeUnit.SECONDS.sleep(delay + 1) } catch (e: InterruptedException) { }
} catch (e: TimeoutException) {
println(
"\n" +
"Telegram took too long to respond to our request.\n" +
"I'm going to wait a minute and then try again." +
"\n")
try { TimeUnit.MINUTES.sleep(1) } catch (e: InterruptedException) { }
}
}
}
@JvmStatic
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("-", limit = 2)
val v2_p = v2.split("-", limit = 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 String.anonymize(): String {
return if (!Utils.anonymize) this else this.replace(Regex("[0-9]"), "1").replace(Regex("[A-Z]"), "A").replace(Regex("[a-z]"), "a") + " (ANONYMIZED)"
}
fun Any.toJson(): String = Gson().toJson(this)
fun Any.toPrettyJson(): String = GsonBuilder().setPrettyPrinting().create().toJson(this)
class MaxTriesExceededException(): RuntimeException("Max tries exceeded") {}
fun TLMessage.toJson(): String {
val json = Gson().toJsonTree(this)
cleanUpMessageJson(json)
return json.toString()
}
fun cleanUpMessageJson(json : JsonElement) {
if (json.isJsonArray) {
json.array.forEach {cleanUpMessageJson(it)}
return
} else if (!json.isJsonObject) {
return
}
if (json.obj.has("bytes")) {
json.obj -= "bytes"
return
}
json.obj.forEach {_: String, elm: JsonElement ->
if (elm.isJsonObject || elm.isJsonArray) cleanUpMessageJson(elm)
}
}