package me.eternal.purrfect.setup.patch import com.google.gson.JsonParser import java.security.SecureRandom import java.util.concurrent.TimeUnit import me.eternal.purrfect.common.TargetApp import okhttp3.OkHttpClient import okhttp3.Request class AutoPatchServer( private val okHttpClient: OkHttpClient = OkHttpClient.Builder() .callTimeout(1, TimeUnit.MINUTES) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(5, TimeUnit.MINUTES) .writeTimeout(5, TimeUnit.MINUTES) .addInterceptor { chain -> chain.proceed( chain.request().newBuilder() .addHeader("Accept", "application/vnd.github+json") .addHeader("User-Agent", "Purrfect") .build() ) } .build() ) { private val snapchatAssetRandom = SecureRandom() private val releaseApiBases = listOf( "https://www.purrfectgit.com/api/repos", "https://api.github.com/repos" ) data class LatestApk( val tagName: String, val apkName: String, val downloadUrl: String, ) fun fetchLatestSnapchatApk(): LatestApk? = fetchLatestApk(TargetApp.SNAPCHAT) fun fetchLatestRedditApk(): LatestApk? = fetchLatestApk(TargetApp.REDDIT) fun fetchLatestApk(targetApp: TargetApp): LatestApk? { targetApp.releaseRepositories().forEach { repository -> fetchLatestApkFromRepository(targetApp, repository)?.let { return it } } return null } private fun fetchLatestApkFromRepository( targetApp: TargetApp, repository: GithubRepository ): LatestApk? { releaseApiBases.forEach { apiBase -> fetchLatestApkFromApi(targetApp, repository, apiBase)?.let { return it } } return null } private fun fetchLatestApkFromApi( targetApp: TargetApp, repository: GithubRepository, apiBase: String ): LatestApk? { val request = Request.Builder() .url("$apiBase/${repository.owner}/${repository.name}/releases/latest") .build() return runCatching { okHttpClient.newCall(request).execute().use { response -> if (!response.isSuccessful) return@runCatching null val json = response.body?.string() ?: return@runCatching null val release = JsonParser.parseString(json).asJsonObject val tagName = release.getAsJsonPrimitive("tag_name")?.asString ?: "latest" val assets = release.getAsJsonArray("assets") ?: return@runCatching null val apkAssets = assets.mapNotNull { element -> val asset = element.asJsonObject val name = asset.getAsJsonPrimitive("name")?.asString ?: return@mapNotNull null val downloadUrl = asset.getAsJsonPrimitive("browser_download_url")?.asString ?: return@mapNotNull null if (!name.endsWith(".apk", ignoreCase = true)) return@mapNotNull null name to downloadUrl } val selected = selectApkAsset(targetApp, apkAssets) ?: return@runCatching null LatestApk( tagName = tagName, apkName = selected.first, downloadUrl = selected.second, ) } }.getOrNull() } private fun selectApkAsset( targetApp: TargetApp, apkAssets: List> ): Pair? { if (apkAssets.isEmpty()) return null return when (targetApp) { TargetApp.SNAPCHAT -> { apkAssets[snapchatAssetRandom.nextInt(apkAssets.size)] } TargetApp.REDDIT -> apkAssets.first() TargetApp.WHATSAPP -> null TargetApp.INSTAGRAM -> null } } private data class GithubRepository( val owner: String, val name: String ) private fun TargetApp.releaseRepositories(): List { return when (this) { TargetApp.SNAPCHAT -> listOf( GithubRepository("particle-box", "download-snap"), GithubRepository("curious-freak", "download-snap") ) TargetApp.REDDIT -> listOf( GithubRepository("particle-box", "download-reddit"), GithubRepository("curious-freak", "download-reddit") ) TargetApp.WHATSAPP -> emptyList() TargetApp.INSTAGRAM -> emptyList() } } }