Telegram backups refactoring stage 1
This commit is contained in:
@@ -7,12 +7,6 @@ import androidx.documentfile.provider.DocumentFile
|
|||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.runInterruptible
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
||||||
import okhttp3.MultipartBody
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import okio.source
|
import okio.source
|
||||||
@@ -21,7 +15,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
|||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ExternalBackupStorage @Inject constructor(
|
class ExternalBackupStorage @Inject constructor(
|
||||||
@@ -96,36 +89,3 @@ class ExternalBackupStorage @Inject constructor(
|
|||||||
return checkNotNull(root) { "Cannot obtain DocumentFile from $uri" }
|
return checkNotNull(root) { "Cannot obtain DocumentFile from $uri" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class TelegramBackupUploader @Inject constructor(private val settings: AppSettings) {
|
|
||||||
|
|
||||||
private val client = OkHttpClient()
|
|
||||||
|
|
||||||
suspend fun uploadBackupToTelegram(file: File) = withContext(Dispatchers.IO) {
|
|
||||||
val botToken = "7455491254:AAGYJKgpP1DZN3d9KZfb8tvtIdaIMxUayXM"
|
|
||||||
val chatId = settings.telegramChatId
|
|
||||||
|
|
||||||
if (botToken.isNullOrEmpty() || chatId.isNullOrEmpty()) {
|
|
||||||
throw IllegalStateException("Telegram API key or chat ID not set in settings.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val mediaType = "application/zip".toMediaTypeOrNull()
|
|
||||||
val requestBody = file.asRequestBody(mediaType)
|
|
||||||
|
|
||||||
val multipartBody = MultipartBody.Builder()
|
|
||||||
.setType(MultipartBody.FORM)
|
|
||||||
.addFormDataPart("chat_id", chatId)
|
|
||||||
.addFormDataPart("document", file.name, requestBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("https://api.telegram.org/bot$botToken/sendDocument")
|
|
||||||
.post(multipartBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
throw IOException("Failed to send backup to Telegram: ${response.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.koitharu.kotatsu.core.backup
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.annotation.UiContext
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import okhttp3.internal.closeQuietly
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.network.BaseHttpClient
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||||
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TelegramBackupUploader @Inject constructor(
|
||||||
|
private val settings: AppSettings,
|
||||||
|
@BaseHttpClient private val client: OkHttpClient,
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val botToken = context.getString(R.string.tg_backup_bot_token)
|
||||||
|
|
||||||
|
suspend fun uploadBackupToTelegram(file: File) = withContext(Dispatchers.IO) {
|
||||||
|
|
||||||
|
val mediaType = "application/zip".toMediaTypeOrNull()
|
||||||
|
val requestBody = file.asRequestBody(mediaType)
|
||||||
|
|
||||||
|
val multipartBody = MultipartBody.Builder()
|
||||||
|
.setType(MultipartBody.FORM)
|
||||||
|
.addFormDataPart("chat_id", requireChatId())
|
||||||
|
.addFormDataPart("document", file.name, requestBody)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("https://api.telegram.org/bot$botToken/sendDocument")
|
||||||
|
.post(multipartBody)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).await().ensureSuccess().closeQuietly()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun checkTelegramBotApiKey(apiKey: String) {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("https://api.telegram.org/bot$apiKey/getMe")
|
||||||
|
.build()
|
||||||
|
client.newCall(request).await().ensureSuccess().closeQuietly()
|
||||||
|
sendMessageToTelegram(apiKey, context.getString(R.string.backup_tg_echo))
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeImplicitIntentLaunch")
|
||||||
|
fun openTelegramBot(@UiContext context: Context) {
|
||||||
|
val botUsername = context.getString(R.string.tg_backup_bot_name)
|
||||||
|
try {
|
||||||
|
val telegramIntent = Intent(Intent.ACTION_VIEW)
|
||||||
|
telegramIntent.data = Uri.parse("tg://resolve?domain=$botUsername")
|
||||||
|
context.startActivity(telegramIntent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/$botUsername"))
|
||||||
|
context.startActivity(browserIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendMessageToTelegram(apiKey: String, message: String) {
|
||||||
|
val url = "https://api.telegram.org/bot$apiKey/sendMessage?chat_id=${requireChatId()}&text=$message"
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).await().ensureSuccess().closeQuietly()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requireChatId() = checkNotNull(settings.backupTelegramChatId) {
|
||||||
|
"Telegram chat ID not set in settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,14 +43,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
private val connectivityManager = context.connectivityManager
|
private val connectivityManager = context.connectivityManager
|
||||||
private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
|
|
||||||
|
|
||||||
var telegramChatId: String?
|
|
||||||
get() = preferences.getString("telegram_chat_id", null)
|
|
||||||
set(value) {
|
|
||||||
preferences.edit().putString("telegram_chat_id", value).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
var listMode: ListMode
|
var listMode: ListMode
|
||||||
get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.GRID)
|
get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.GRID)
|
||||||
@@ -497,6 +489,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
get() = prefs.getString(KEY_BACKUP_PERIODICAL_OUTPUT, null)?.toUriOrNull()
|
get() = prefs.getString(KEY_BACKUP_PERIODICAL_OUTPUT, null)?.toUriOrNull()
|
||||||
set(value) = prefs.edit { putString(KEY_BACKUP_PERIODICAL_OUTPUT, value?.toString()) }
|
set(value) = prefs.edit { putString(KEY_BACKUP_PERIODICAL_OUTPUT, value?.toString()) }
|
||||||
|
|
||||||
|
val backupTelegramChatId: String?
|
||||||
|
get() = prefs.getString(KEY_BACKUP_TG_CHAT, null)
|
||||||
|
|
||||||
val isReadingTimeEstimationEnabled: Boolean
|
val isReadingTimeEstimationEnabled: Boolean
|
||||||
get() = prefs.getBoolean(KEY_READING_TIME, true)
|
get() = prefs.getBoolean(KEY_READING_TIME, true)
|
||||||
|
|
||||||
@@ -724,6 +719,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_SEARCH_SUGGESTION_TYPES = "search_suggest_types"
|
const val KEY_SEARCH_SUGGESTION_TYPES = "search_suggest_types"
|
||||||
const val KEY_SOURCES_VERSION = "sources_version"
|
const val KEY_SOURCES_VERSION = "sources_version"
|
||||||
const val KEY_QUICK_FILTER = "quick_filter"
|
const val KEY_QUICK_FILTER = "quick_filter"
|
||||||
|
const val KEY_BACKUP_TG_CHAT = "telegram_chat_id"
|
||||||
|
|
||||||
// keys for non-persistent preferences
|
// keys for non-persistent preferences
|
||||||
const val KEY_APP_VERSION = "app_version"
|
const val KEY_APP_VERSION = "app_version"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResultCallback
|
import androidx.activity.result.ActivityResultCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
@@ -18,19 +17,13 @@ import kotlinx.coroutines.withContext
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.backup.BackupZipOutput.Companion.DIR_BACKUPS
|
import org.koitharu.kotatsu.core.backup.BackupZipOutput.Companion.DIR_BACKUPS
|
||||||
import org.koitharu.kotatsu.core.backup.ExternalBackupStorage
|
import org.koitharu.kotatsu.core.backup.ExternalBackupStorage
|
||||||
|
import org.koitharu.kotatsu.core.backup.TelegramBackupUploader
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
import org.koitharu.kotatsu.core.util.ext.resolveFile
|
import org.koitharu.kotatsu.core.util.ext.resolveFile
|
||||||
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
import okhttp3.Call
|
|
||||||
import okhttp3.Callback
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -40,10 +33,10 @@ class PeriodicalBackupSettingsFragment : BasePreferenceFragment(R.string.periodi
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var backupStorage: ExternalBackupStorage
|
lateinit var backupStorage: ExternalBackupStorage
|
||||||
|
|
||||||
private val outputSelectCall = registerForActivityResult(
|
@Inject
|
||||||
ActivityResultContracts.OpenDocumentTree(),
|
lateinit var telegramBackupUploader: TelegramBackupUploader
|
||||||
this,
|
|
||||||
)
|
private val outputSelectCall = registerForActivityResult(ActivityResultContracts.OpenDocumentTree(), this)
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
addPreferencesFromResource(R.xml.pref_backup_periodic)
|
addPreferencesFromResource(R.xml.pref_backup_periodic)
|
||||||
@@ -51,92 +44,10 @@ class PeriodicalBackupSettingsFragment : BasePreferenceFragment(R.string.periodi
|
|||||||
val openTelegramBotPreference = findPreference<Preference>("open_telegram_chat")
|
val openTelegramBotPreference = findPreference<Preference>("open_telegram_chat")
|
||||||
|
|
||||||
openTelegramBotPreference?.setOnPreferenceClickListener {
|
openTelegramBotPreference?.setOnPreferenceClickListener {
|
||||||
openTelegramBot("kotatsu_backup_bot")
|
telegramBackupUploader.openTelegramBot(it.context, "kotatsu_backup_bot")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
val checkApiButton = Preference(requireContext()).apply {
|
|
||||||
key = "check_api_working"
|
|
||||||
title = context.getString(R.string.api_telegram_check)
|
|
||||||
summary = context.getString(R.string.api_check_desc)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkApiButton.setOnPreferenceClickListener {
|
|
||||||
val apiKey = "7455491254:AAGYJKgpP1DZN3d9KZfb8tvtIdaIMxUayXM"
|
|
||||||
if (apiKey.isNotEmpty()) {
|
|
||||||
checkTelegramBotApiKey(apiKey)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceScreen.addPreference(checkApiButton)
|
|
||||||
}
|
}
|
||||||
private fun checkTelegramBotApiKey(apiKey: String) {
|
|
||||||
val url = "https://api.telegram.org/bot$apiKey/getMe"
|
|
||||||
|
|
||||||
val client = OkHttpClient()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
requireActivity().runOnUiThread {
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
context?.let { sendMessageToTelegram(apiKey, it.getString(R.string.api_is_work)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
requireActivity().runOnUiThread {
|
|
||||||
Toast.makeText(requireContext(), R.string.api_net_error, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
private fun openTelegramBot(botUsername: String) {
|
|
||||||
try {
|
|
||||||
val telegramIntent = Intent(Intent.ACTION_VIEW)
|
|
||||||
telegramIntent.data = Uri.parse("https://t.me/$botUsername")
|
|
||||||
telegramIntent.setPackage("org.telegram.messenger")
|
|
||||||
startActivity(telegramIntent)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/$botUsername"))
|
|
||||||
startActivity(browserIntent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private fun sendMessageToTelegram(apiKey: String, message: String) {
|
|
||||||
val chatId = settings.telegramChatId
|
|
||||||
if (chatId.isNullOrEmpty()) {
|
|
||||||
Toast.makeText(requireContext(), R.string.id_not_set, Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val url = "https://api.telegram.org/bot$apiKey/sendMessage?chat_id=$chatId&text=$message"
|
|
||||||
val client = OkHttpClient()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
requireActivity().runOnUiThread {
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
Toast.makeText(requireContext(), R.string.api_check_success, Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), R.string.api_check_error, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
requireActivity().runOnUiThread {
|
|
||||||
Toast.makeText(requireContext(), R.string.api_error, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.backup
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import androidx.hilt.work.HiltWorker
|
|
||||||
import androidx.work.Constraints
|
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
|
||||||
import androidx.work.PeriodicWorkRequestBuilder
|
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import androidx.work.WorkerParameters
|
|
||||||
import androidx.work.await
|
|
||||||
import androidx.work.workDataOf
|
|
||||||
import dagger.Reusable
|
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import org.koitharu.kotatsu.core.backup.BackupRepository
|
|
||||||
import org.koitharu.kotatsu.core.backup.BackupZipOutput
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
|
||||||
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
|
||||||
import okhttp3.Call
|
|
||||||
import okhttp3.Callback
|
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
||||||
import okhttp3.MultipartBody
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
|
||||||
import okhttp3.Response
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
@HiltWorker
|
|
||||||
class PeriodicalBackupWorker @AssistedInject constructor(
|
|
||||||
@Assisted appContext: Context,
|
|
||||||
@Assisted params: WorkerParameters,
|
|
||||||
private val repository: BackupRepository,
|
|
||||||
private val settings: AppSettings,
|
|
||||||
) : CoroutineWorker(appContext, params) {
|
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
|
||||||
val resultData = workDataOf(DATA_TIMESTAMP to Date().time)
|
|
||||||
val file = BackupZipOutput(applicationContext).use { backup ->
|
|
||||||
backup.put(repository.createIndex())
|
|
||||||
backup.put(repository.dumpHistory())
|
|
||||||
backup.put(repository.dumpCategories())
|
|
||||||
backup.put(repository.dumpFavourites())
|
|
||||||
backup.put(repository.dumpBookmarks())
|
|
||||||
backup.put(repository.dumpSources())
|
|
||||||
backup.put(repository.dumpSettings())
|
|
||||||
backup.finish()
|
|
||||||
backup.file
|
|
||||||
}
|
|
||||||
val dirUri = settings.periodicalBackupOutput ?: return Result.success(resultData)
|
|
||||||
val target = DocumentFile.fromTreeUri(applicationContext, dirUri)
|
|
||||||
?.createFile("application/zip", file.nameWithoutExtension)
|
|
||||||
?.uri ?: return Result.failure()
|
|
||||||
applicationContext.contentResolver.openOutputStream(target, "wt")?.use { output ->
|
|
||||||
file.inputStream().copyTo(output)
|
|
||||||
} ?: return Result.failure()
|
|
||||||
|
|
||||||
val botToken = "7455491254:AAGYJKgpP1DZN3d9KZfb8tvtIdaIMxUayXM"
|
|
||||||
val chatId = settings.telegramChatId ?: return Result.failure()
|
|
||||||
|
|
||||||
val success = sendBackupToTelegram(file, botToken, chatId)
|
|
||||||
|
|
||||||
file.deleteAwait()
|
|
||||||
|
|
||||||
return if (success) {
|
|
||||||
Result.success(resultData)
|
|
||||||
} else {
|
|
||||||
Result.failure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendBackupToTelegram(file: File, botToken: String, chatId: String): Boolean {
|
|
||||||
val client = OkHttpClient()
|
|
||||||
val mediaType = "application/zip".toMediaTypeOrNull()
|
|
||||||
val requestBody = file.asRequestBody(mediaType)
|
|
||||||
|
|
||||||
val multipartBody = MultipartBody.Builder()
|
|
||||||
.setType(MultipartBody.FORM)
|
|
||||||
.addFormDataPart("chat_id", chatId)
|
|
||||||
.addFormDataPart("document", file.name, requestBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("https://api.telegram.org/bot$botToken/sendDocument")
|
|
||||||
.post(multipartBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
return response.isSuccessful
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Reusable
|
|
||||||
class Scheduler @Inject constructor(
|
|
||||||
private val workManager: WorkManager,
|
|
||||||
private val settings: AppSettings,
|
|
||||||
) : PeriodicWorkScheduler {
|
|
||||||
|
|
||||||
override suspend fun schedule() {
|
|
||||||
val constraints = Constraints.Builder()
|
|
||||||
.setRequiresStorageNotLow(true)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
constraints.setRequiresDeviceIdle(true)
|
|
||||||
}
|
|
||||||
val request = PeriodicWorkRequestBuilder<PeriodicalBackupWorker>(
|
|
||||||
settings.periodicalBackupFrequency,
|
|
||||||
TimeUnit.DAYS,
|
|
||||||
).setConstraints(constraints.build())
|
|
||||||
.keepResultsForAtLeast(20, TimeUnit.DAYS)
|
|
||||||
.addTag(TAG)
|
|
||||||
.build()
|
|
||||||
workManager
|
|
||||||
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)
|
|
||||||
.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun unschedule() {
|
|
||||||
workManager
|
|
||||||
.cancelUniqueWork(TAG)
|
|
||||||
.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun isScheduled(): Boolean {
|
|
||||||
return workManager
|
|
||||||
.awaitUniqueWorkInfoByName(TAG)
|
|
||||||
.any { !it.state.isFinished }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getLastSuccessfulBackup(): Date? {
|
|
||||||
return workManager
|
|
||||||
.awaitUniqueWorkInfoByName(TAG)
|
|
||||||
.lastOrNull { x -> x.state == WorkInfo.State.SUCCEEDED }
|
|
||||||
?.outputData
|
|
||||||
?.getLong(DATA_TIMESTAMP, 0)
|
|
||||||
?.let { if (it != 0L) Date(it) else null }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
const val TAG = "backups"
|
|
||||||
const val DATA_TIMESTAMP = "ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
<string name="acra_password" translatable="false">kgpuhoNJpSsQDCwu</string>
|
<string name="acra_password" translatable="false">kgpuhoNJpSsQDCwu</string>
|
||||||
<string name="sync_authority_history" translatable="false">org.koitharu.kotatsu.history</string>
|
<string name="sync_authority_history" translatable="false">org.koitharu.kotatsu.history</string>
|
||||||
<string name="sync_authority_favourites" translatable="false">org.koitharu.kotatsu.favourites</string>
|
<string name="sync_authority_favourites" translatable="false">org.koitharu.kotatsu.favourites</string>
|
||||||
|
<string name="tg_backup_bot_token" translatable="false">7455491254:AAGYJKgpP1DZN3d9KZfb8tvtIdaIMxUayXM</string>
|
||||||
|
<string name="tg_backup_bot_name" translatable="false">kotatsu_backup_bot</string>
|
||||||
<string-array name="values_theme" translatable="false">
|
<string-array name="values_theme" translatable="false">
|
||||||
<item>-1</item>
|
<item>-1</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
|
|||||||
@@ -778,12 +778,9 @@
|
|||||||
<!-- Button label, should be as short as possible -->
|
<!-- Button label, should be as short as possible -->
|
||||||
<string name="incognito">Incognito</string>
|
<string name="incognito">Incognito</string>
|
||||||
<string name="error_connection_reset">Connection reset by remote host</string>
|
<string name="error_connection_reset">Connection reset by remote host</string>
|
||||||
<string name="api_telegram_check">Check API work</string>
|
<string name="backup_tg_check">Check if API works</string>
|
||||||
<string name="api_check_desc">Click to check the operation of the Telegram Bot API</string>
|
<string name="backup_tg_echo">Kotatsu backup in Telegram is working!!</string>
|
||||||
<string name="api_is_work">Kotatsu backup in Telegram is working!!</string>
|
<string name="backup_tg_id_not_set">Chat ID is not set</string>
|
||||||
<string name="api_net_error">Network error! Check your Net</string>
|
<string name="telegram_chat_id">Telegram chat ID</string>
|
||||||
<string name="id_not_set">Chat ID is not set!</string>
|
<string name="open_telegram_bot">Open the Telegram bot</string>
|
||||||
<string name="api_check_success">Success! Check Telegram Bot</string>
|
|
||||||
<string name="api_check_error">OOPS! Something went wrong</string>
|
|
||||||
<string name="api_error">Network error!</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -50,15 +50,16 @@
|
|||||||
app:allowDividerAbove="true"
|
app:allowDividerAbove="true"
|
||||||
app:isPreferenceVisible="false" />
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
android:inputType="text"
|
||||||
|
android:key="telegram_chat_id"
|
||||||
|
android:summary="Enter the chat ID where backups should be sent"
|
||||||
|
android:title="@string/telegram_chat_id"
|
||||||
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="open_telegram_chat"
|
android:key="open_telegram_chat"
|
||||||
android:title="Open Telegram Bot"
|
android:summary="Press to open chat with Kotatsu Backup Bot"
|
||||||
android:summary="Press to open chat with Kotatsu Backup Bot" />
|
android:title="@string/open_telegram_bot" />
|
||||||
<EditTextPreference
|
|
||||||
android:key="telegram_chat_id"
|
|
||||||
android:title="Telegram Chat ID"
|
|
||||||
android:inputType="text"
|
|
||||||
android:defaultValue=""
|
|
||||||
android:summary="Enter the chat ID where backups should be sent" />
|
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
||||||
|
|||||||
Reference in New Issue
Block a user