package com.lagradost.cloudstream3.utils import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Build import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.app.NotificationManagerCompat import androidx.core.content.FileProvider import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.R import java.io.* import java.net.URL import java.net.URLConnection import kotlin.concurrent.thread const val UPDATE_TIME = 1000 class InAppUpdater { companion object { // === IN APP UPDATER === data class GithubAsset( @JsonProperty("name") val name: String, @JsonProperty("size") val size: Int, // Size bytes @JsonProperty("browser_download_url") val browser_download_url: String, // download link @JsonProperty("content_type") val content_type: String, // application/vnd.android.package-archive ) data class GithubRelease( @JsonProperty("tag_name") val tag_name: String, // Version code @JsonProperty("body") val body: String, // Desc @JsonProperty("assets") val assets: List, @JsonProperty("target_commitish") val target_commitish: String, // branch ) data class Update( @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, @JsonProperty("updateURL") val updateURL: String?, @JsonProperty("updateVersion") val updateVersion: String?, @JsonProperty("changelog") val changelog: String?, ) private val mapper = JsonMapper.builder().addModule(KotlinModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build() private fun Activity.getAppUpdate(): Update { try { val url = "https://api.github.com/repos/LagradOst/CloudStream-3/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") val response = mapper.readValue>(khttp.get(url, headers = headers).text) val versionRegex = Regex("""(.*?((\d)\.(\d)\.(\d)).*\.apk)""") /* val releases = response.map { it.assets }.flatten() .filter { it.content_type == "application/vnd.android.package-archive" } val found = releases.sortedWith(compareBy { versionRegex.find(it.name)?.groupValues?.get(2) }).toList().lastOrNull()*/ val found = response.sortedWith(compareBy { release -> release.assets.filter { it.content_type == "application/vnd.android.package-archive" } .getOrNull(0)?.name?.let { it1 -> versionRegex.find( it1 )?.groupValues?.get(2) } }).toList().lastOrNull() val foundAsset = found?.assets?.getOrNull(0) val currentVersion = packageName?.let { packageManager.getPackageInfo(it, 0) } val foundVersion = foundAsset?.name?.let { versionRegex.find(it) } val shouldUpdate = if (found != null && foundAsset?.browser_download_url != "" && foundVersion != null) currentVersion?.versionName?.compareTo( foundVersion.groupValues[2] )!! < 0 else false return if (foundVersion != null) { Update(shouldUpdate, foundAsset.browser_download_url, foundVersion.groupValues[2], found.body) } else { Update(false, null, null, null) } } catch (e: Exception) { println(e) return Update(false, null, null, null) } } private fun Activity.downloadUpdate(url: String): Boolean { println("DOWNLOAD UPDATE $url") var fullResume = false // IF FULL RESUME try { // =================== DOWNLOAD POSTERS AND SETUP PATH =================== val path = filesDir.toString() + "/Download/apk/update.apk" // =================== MAKE DIRS =================== val rFile = File(path) try { rFile.parentFile?.mkdirs() } catch (_ex: Exception) { println("FAILED:::$_ex") } val url = url.replace(" ", "%20") val _url = URL(url) val connection: URLConnection = _url.openConnection() var bytesRead = 0L // =================== STORAGE =================== try { if (!rFile.exists()) { rFile.createNewFile() } else { rFile.delete() rFile.createNewFile() } } catch (e: Exception) { println(e) runOnUiThread { Toast.makeText(this, "Permission error", Toast.LENGTH_SHORT).show() } return false } // =================== CONNECTION =================== connection.setRequestProperty("Accept-Encoding", "identity") connection.connectTimeout = 10000 var clen = 0 try { connection.connect() clen = connection.contentLength println("CONTENTN LENGTH: $clen") } catch (_ex: Exception) { println("CONNECT:::$_ex") _ex.printStackTrace() } // =================== VALIDATE =================== if (clen < 5000000) { // min of 5 MB clen = 0 } if (clen <= 0) { // TO SMALL OR INVALID //showNot(0, 0, 0, DownloadType.IsFailed, info) return false } // =================== SETUP VARIABLES =================== //val bytesTotal: Long = (clen + bytesRead.toInt()).toLong() val input: InputStream = BufferedInputStream(connection.inputStream) val output: OutputStream = FileOutputStream(rFile, false) var bytesPerSec = 0L val buffer = ByteArray(1024) var count: Int //var lastUpdate = System.currentTimeMillis() while (true) { try { count = input.read(buffer) if (count < 0) break bytesRead += count bytesPerSec += count output.write(buffer, 0, count) } catch (_ex: Exception) { println("CONNECT TRUE:::$_ex") _ex.printStackTrace() fullResume = true break } } if (fullResume) { // IF FULL RESUME DELETE CURRENT AND DONT SHOW DONE with(NotificationManagerCompat.from(this)) { cancel(-1) } } output.flush() output.close() input.close() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val contentUri = FileProvider.getUriForFile( this, BuildConfig.APPLICATION_ID + ".provider", rFile ) val install = Intent(Intent.ACTION_VIEW) install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) install.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) install.data = contentUri startActivity(install) return true } else { val apkUri = Uri.fromFile(rFile) val install = Intent(Intent.ACTION_VIEW) install.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP install.setDataAndType( apkUri, "application/vnd.android.package-archive" ) startActivity(install) return true } } catch (_ex: Exception) { println("FATAL EX DOWNLOADING:::$_ex") return false } } fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) if (!checkAutoUpdate || settingsManager.getBoolean(getString(R.string.auto_update_key), true) ) { val update = getAppUpdate() if (update.shouldUpdate && update.updateURL != null) { runOnUiThread { val currentVersion = packageName?.let { packageManager.getPackageInfo(it, 0) } val builder: AlertDialog.Builder = AlertDialog.Builder(this) builder.setTitle("New update found!\n${currentVersion?.versionName} -> ${update.updateVersion}") builder.setMessage("${update.changelog}") val context = this builder.apply { setPositiveButton("Update") { _, _ -> Toast.makeText(context, "Download started", Toast.LENGTH_LONG).show() thread { val downloadStatus = context.downloadUpdate(update.updateURL) if (!downloadStatus) { runOnUiThread { Toast.makeText(context, "Download Failed", Toast.LENGTH_LONG).show() } } /*else { activity.runOnUiThread { Toast.makeText(localContext, "Downloaded APK", Toast.LENGTH_LONG).show() } }*/ } } setNegativeButton("Cancel") { _, _ -> } if(checkAutoUpdate) { setNeutralButton("Don't show again") { _, _ -> settingsManager.edit().putBoolean("auto_update", false).apply() } } } builder.show() } return true } return false } return false } } }