From 4b18c099b6fadbd65267676694ce2b721fa52345 Mon Sep 17 00:00:00 2001 From: shabinder Date: Sun, 6 Jun 2021 00:13:55 +0530 Subject: [PATCH] Dep Updates, Maintenance Tasks Updated --- android/build.gradle.kts | 2 +- build.gradle.kts | 1 + buildSrc/buildSrc/src/main/kotlin/Versions.kt | 13 +- .../multiplatform-compose-setup.gradle.kts | 18 +- .../multiplatform-setup-test.gradle.kts | 23 +- .../kotlin/multiplatform-setup.gradle.kts | 30 +- gradle/wrapper/gradle-wrapper.properties | 4 +- maintenance-tasks/build.gradle.kts | 20 +- .../UpdateAnalyticsImage.kt | 88 ------ .../java/audio_conversion/AudioQuality.kt | 25 -- .../main/java/audio_conversion/AudioToMp3.kt | 101 ------ .../{analytics_html_img => common}/Common.kt | 7 +- .../src/main/java/common/ContentUpdation.kt | 30 ++ .../src/main/java/common/Date.kt | 16 + .../GithubService.kt | 27 +- .../HCTIService.kt | 2 +- .../{analytics_html_img => common}/Secrets.kt | 2 +- .../main/java/jiosaavn/JioSaavnRequests.kt | 289 ------------------ .../src/main/java/jiosaavn/JioSaavnUtils.kt | 121 -------- .../src/main/java/jiosaavn/models/MoreInfo.kt | 10 - .../main/java/jiosaavn/models/SaavnAlbum.kt | 17 -- .../java/jiosaavn/models/SaavnPlaylist.kt | 22 -- .../java/jiosaavn/models/SaavnSearchResult.kt | 17 -- .../main/java/jiosaavn/models/SaavnSong.kt | 41 --- maintenance-tasks/src/main/java/main.kt | 43 ++- .../src/main/java/models/github/Asset.kt | 17 ++ .../src/main/java/models/github/Author.kt | 22 ++ .../java/models/github/GithubFileContent.kt | 6 + .../models/github/GithubReleaseInfoItem.kt | 23 ++ .../java/models/github/GithubReleasesInfo.kt | 3 + .../src/main/java/models/github/Reactions.kt | 16 + .../src/main/java/models/github/Uploader.kt | 22 ++ .../main/java/scripts/UpdateAnalyticsImage.kt | 65 ++++ .../main/java/scripts/UpdateDownloadCards.kt | 213 +++++++++++++ maintenance-tasks/src/main/java/utils/Ext.kt | 2 +- .../src/main/java/utils/JsonUtils.kt | 92 ------ .../src/main/java/utils/TestClass.kt | 3 +- web-app/build.gradle.kts | 5 +- 38 files changed, 539 insertions(+), 919 deletions(-) delete mode 100644 maintenance-tasks/src/main/java/analytics_html_img/UpdateAnalyticsImage.kt delete mode 100644 maintenance-tasks/src/main/java/audio_conversion/AudioQuality.kt delete mode 100644 maintenance-tasks/src/main/java/audio_conversion/AudioToMp3.kt rename maintenance-tasks/src/main/java/{analytics_html_img => common}/Common.kt (90%) create mode 100644 maintenance-tasks/src/main/java/common/ContentUpdation.kt create mode 100644 maintenance-tasks/src/main/java/common/Date.kt rename maintenance-tasks/src/main/java/{analytics_html_img => common}/GithubService.kt (77%) rename maintenance-tasks/src/main/java/{analytics_html_img => common}/HCTIService.kt (99%) rename maintenance-tasks/src/main/java/{analytics_html_img => common}/Secrets.kt (97%) delete mode 100644 maintenance-tasks/src/main/java/jiosaavn/JioSaavnRequests.kt delete mode 100644 maintenance-tasks/src/main/java/jiosaavn/JioSaavnUtils.kt delete mode 100644 maintenance-tasks/src/main/java/jiosaavn/models/MoreInfo.kt delete mode 100644 maintenance-tasks/src/main/java/jiosaavn/models/SaavnAlbum.kt delete mode 100644 maintenance-tasks/src/main/java/jiosaavn/models/SaavnPlaylist.kt delete mode 100644 maintenance-tasks/src/main/java/jiosaavn/models/SaavnSearchResult.kt delete mode 100644 maintenance-tasks/src/main/java/jiosaavn/models/SaavnSong.kt create mode 100644 maintenance-tasks/src/main/java/models/github/Asset.kt create mode 100644 maintenance-tasks/src/main/java/models/github/Author.kt create mode 100644 maintenance-tasks/src/main/java/models/github/GithubFileContent.kt create mode 100644 maintenance-tasks/src/main/java/models/github/GithubReleaseInfoItem.kt create mode 100644 maintenance-tasks/src/main/java/models/github/GithubReleasesInfo.kt create mode 100644 maintenance-tasks/src/main/java/models/github/Reactions.kt create mode 100644 maintenance-tasks/src/main/java/models/github/Uploader.kt create mode 100644 maintenance-tasks/src/main/java/scripts/UpdateAnalyticsImage.kt create mode 100644 maintenance-tasks/src/main/java/scripts/UpdateDownloadCards.kt delete mode 100644 maintenance-tasks/src/main/java/utils/JsonUtils.kt diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 09812f1a..db765da8 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -131,7 +131,7 @@ dependencies { //implementation("com.jakewharton.timber:timber:4.7.1") implementation("dev.icerock.moko:parcelize:0.6.1") implementation("com.github.shabinder:storage-chooser:2.0.4.45") - implementation("com.google.accompanist:accompanist-insets:0.9.1") + implementation("com.google.accompanist:accompanist-insets:0.11.1") // Test testImplementation("junit:junit:4.13.2") diff --git a/build.gradle.kts b/build.gradle.kts index 9c115950..c73376d8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,6 +32,7 @@ allprojects { } tasks.withType().configureEach { kotlinOptions { + jvmTarget = "1.8" useIR = true } } diff --git a/buildSrc/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/buildSrc/src/main/kotlin/Versions.kt index f9f7e411..f32cef0b 100644 --- a/buildSrc/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/buildSrc/src/main/kotlin/Versions.kt @@ -22,7 +22,7 @@ object Versions { const val versionCode = 20 // Kotlin - const val kotlinVersion = "1.4.32" + const val kotlinVersion = "1.5.10" const val coroutinesVersion = "1.4.2" // Code Formatting @@ -51,6 +51,7 @@ object Versions { const val targetSdkVersion = 29 const val androidLifecycle = "2.3.0" } + object HostOS { // Host OS Properties private val hostOs = System.getProperty("os.name") @@ -58,12 +59,14 @@ object HostOS { val isMac = hostOs.startsWith("Mac",true) val isLinux = hostOs.startsWith("Linux",true) } + object Koin { val core = "io.insert-koin:koin-core:${Versions.koin}" val test = "io.insert-koin:koin-test:${Versions.koin}" val android = "io.insert-koin:koin-android:${Versions.koin}" val compose = "io.insert-koin:koin-androidx-compose:3.0.1" } + object Androidx { const val androidxActivity = "androidx.activity:activity-compose:1.3.0-alpha07" const val core = "androidx.core:core-ktx:1.3.2" @@ -83,6 +86,7 @@ object Androidx { const val runtimeLiveData = "androidx.compose.runtime:runtime-livedata:${Versions.compose}" }*/ } + object JetBrains { object Kotlin { const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlinVersion}" @@ -94,17 +98,19 @@ object JetBrains { object Compose { // __LATEST_COMPOSE_RELEASE_VERSION__ - const val VERSION = "0.4.0-build188" + const val VERSION = "0.4.0" const val gradlePlugin = "org.jetbrains.compose:compose-gradle-plugin:$VERSION" } } + object Decompose { - private const val VERSION = "0.2.3" + private const val VERSION = "0.2.6" const val decompose = "com.arkivanov.decompose:decompose:$VERSION" const val decomposeIosX64 = "com.arkivanov.decompose:decompose-iosx64:$VERSION" const val decomposeIosArm64 = "com.arkivanov.decompose:decompose-iosarm64:$VERSION" const val extensionsCompose = "com.arkivanov.decompose:extensions-compose-jetbrains:$VERSION" } + object MVIKotlin { private const val VERSION = "2.0.3" const val rx = "com.arkivanov.mvikotlin:rx:$VERSION" @@ -118,6 +124,7 @@ object MVIKotlin { const val mvikotlinTimeTravel = "com.arkivanov.mvikotlin:mvikotlin-timetravel:$VERSION" const val mvikotlinExtensionsReaktive = "com.arkivanov.mvikotlin:mvikotlin-extensions-reaktive:$VERSION" } + object Ktor { val clientCore = "io.ktor:ktor-client-core:${Versions.ktor}" val clientJson = "io.ktor:ktor-client-json:${Versions.ktor}" diff --git a/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts index 3586e838..167d9bfb 100644 --- a/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts @@ -23,16 +23,8 @@ plugins { } kotlin { - jvm("desktop").compilations.all { - kotlinOptions { - useIR = true - } - } - android().compilations.all { - kotlinOptions { - useIR = true - } - } + jvm("desktop") + android() sourceSets { named("commonMain") { dependencies { @@ -51,7 +43,7 @@ kotlin { implementation(Extras.kermit) implementation("dev.icerock.moko:parcelize:0.6.1") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt") { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt") { @Suppress("DEPRECATION") isForce = true } @@ -69,8 +61,4 @@ kotlin { } } } - - tasks.withType { - kotlinOptions.jvmTarget = "1.8" - } } diff --git a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts index dbfdbe57..148ce332 100644 --- a/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-setup-test.gradle.kts @@ -31,25 +31,12 @@ kotlin { } } - jvm("desktop").compilations.all { - kotlinOptions { - useIR = true - } - } - android().compilations.all { - kotlinOptions { - useIR = true - } - } + jvm("desktop") + android() - js { - /* - * TODO Enable JS IR Compiler - * waiting for Decompose & MVI Kotlin to support same - * */ + js(/*BOTH*/) { browser() // nodejs() - binaries.executable() } sourceSets { named("commonTest") { @@ -73,8 +60,4 @@ kotlin { dependencies {} } } - - tasks.withType { - kotlinOptions.jvmTarget = "1.8" - } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts index c00a376e..427355b2 100644 --- a/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts @@ -34,24 +34,12 @@ kotlin { } } - jvm("desktop").compilations.all { - kotlinOptions { - useIR = true - } - } - android().compilations.all { - kotlinOptions { - useIR = true - } - } - js { - /* - * TODO Enable JS IR Compiler - * waiting for Decompose & MVI Kotlin to support same - * */ + jvm("desktop") + android() + + js(/*BOTH*/) { browser() // nodejs() - binaries.executable() } sourceSets { @@ -75,10 +63,10 @@ kotlin { // Extras implementation(Extras.kermit) + implementation(Serialization.json) implementation("co.touchlab:stately-common:1.1.7") implementation("dev.icerock.moko:parcelize:0.6.1") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt") { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0-native-mt") { @Suppress("DEPRECATION") isForce = true } @@ -87,7 +75,7 @@ kotlin { named("androidMain") { dependencies { - implementation("androidx.appcompat:appcompat:1.2.0") + implementation("androidx.appcompat:appcompat:1.3.0") implementation(Androidx.core) implementation(compose.runtime) implementation(compose.material) @@ -127,8 +115,4 @@ kotlin { } } } - - tasks.withType { - kotlinOptions.jvmTarget = "1.8" - } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 87e87435..51d930a3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=e2774e6fb77c43657decde25542dea710aafd78c4022d19b196e7e78d79d8c6c -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip +distributionSha256Sum=7faa7198769f872826c8ef4f1450f839ec27f0b4d5d1e51bade63667cbccd205 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/maintenance-tasks/build.gradle.kts b/maintenance-tasks/build.gradle.kts index d2c76da4..8e71a704 100644 --- a/maintenance-tasks/build.gradle.kts +++ b/maintenance-tasks/build.gradle.kts @@ -18,14 +18,13 @@ application { } dependencies { - implementation(Extras.fuzzyWuzzy) - implementation("org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}") - implementation("io.ktor:ktor-client-core:1.5.4") - implementation("io.ktor:ktor-client-apache:1.5.4") - implementation("io.ktor:ktor-client-logging:1.5.4") implementation(Ktor.slf4j) - implementation("io.ktor:ktor-client-serialization:1.5.4") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1") + implementation(Ktor.clientCore) + implementation(Ktor.clientJson) + implementation(Ktor.clientApache) + implementation(Ktor.clientLogging) + implementation(Ktor.clientSerialization) + implementation(Serialization.json) // testDeps testImplementation(kotlin("test-junit")) } @@ -33,10 +32,3 @@ dependencies { tasks.test { useJUnit() } - -tasks.withType { - kotlinOptions { - jvmTarget = "1.8" - useIR = true - } -} diff --git a/maintenance-tasks/src/main/java/analytics_html_img/UpdateAnalyticsImage.kt b/maintenance-tasks/src/main/java/analytics_html_img/UpdateAnalyticsImage.kt deleted file mode 100644 index 0ffa05c0..00000000 --- a/maintenance-tasks/src/main/java/analytics_html_img/UpdateAnalyticsImage.kt +++ /dev/null @@ -1,88 +0,0 @@ -package analytics_html_img - -import io.ktor.client.features.timeout -import io.ktor.client.request.head -import io.ktor.client.statement.HttpResponse -import kotlinx.coroutines.runBlocking -import utils.RETRY_LIMIT_EXHAUSTED -import utils.debug - -internal fun updateAnalyticsImage() { - val secrets = Secrets.initSecrets() - // debug("fun main: secrets -> $secrets") - - runBlocking { - val oldGithubFile = GithubService.getGithubFileContent( - token = secrets.githubToken, - ownerName = secrets.ownerName, - repoName = secrets.repoName, - branchName = secrets.branchName, - fileName = "README.md" - ) - // debug("OLD FILE CONTENT",oldGithubFile) - val imageURL = getAnalyticsImage().also { - debug("Updated IMAGE", it) - } - - val replacementText = """ - ${Common.START_SECTION(secrets.tagName)} - ![Today's Analytics]($imageURL) - ${Common.END_SECTION(secrets.tagName)} - """.trimIndent() - debug("Updated Text to be Inserted", replacementText) - - val regex = """${Common.START_SECTION(secrets.tagName)}(?s)(.*)${Common.END_SECTION(secrets.tagName)}""".toRegex() - val updatedContent = regex.replace( - oldGithubFile.decryptedContent, - replacementText - ) - // debug("Updated File Content",updatedContent) - - val updationResponse = GithubService.updateGithubFileContent( - token = secrets.githubToken, - ownerName = secrets.ownerName, - repoName = secrets.repoName, - branchName = secrets.branchName, - fileName = secrets.filePath, - commitMessage = secrets.commitMessage, - rawContent = updatedContent, - sha = oldGithubFile.sha - ) - - debug("File Updation Response", updationResponse.toString()) - } -} - -internal suspend fun getAnalyticsImage(): String { - var contentLength: Long - var analyticsImage: String - var retryCount = 5 - - do { - /* - * Get a new Image from Analytics, - * - Use Any Random useless query param , - * As HCTI Demo, `caches value for a specific Link` - * */ - val randomID = (1..100000).random() - analyticsImage = HCTIService.getImageURLFromURL( - url = "https://kind-grasshopper-73.telebit.io/matomo/index.php?module=Widgetize&action=iframe&containerId=VisitOverviewWithGraph&disableLink=0&widget=1&moduleToWidgetize=CoreHome&actionToWidgetize=renderWidgetContainer&idSite=1&period=day&date=yesterday&disableLink=1&widget=$randomID", - delayInMilliSeconds = 5000 - ) - - // Sometimes we get incomplete image, hence verify `content-length` - val req = client.head(analyticsImage) { - timeout { - socketTimeoutMillis = 100_000 - } - } - contentLength = req.headers["Content-Length"]?.toLong() ?: 0 - debug(contentLength.toString()) - - if(retryCount-- == 0){ - // FAIL Gracefully - throw(RETRY_LIMIT_EXHAUSTED()) - } - }while (contentLength<1_20_000) - return analyticsImage -} diff --git a/maintenance-tasks/src/main/java/audio_conversion/AudioQuality.kt b/maintenance-tasks/src/main/java/audio_conversion/AudioQuality.kt deleted file mode 100644 index 0a47ee77..00000000 --- a/maintenance-tasks/src/main/java/audio_conversion/AudioQuality.kt +++ /dev/null @@ -1,25 +0,0 @@ -package audio_conversion - -@Suppress("EnumEntryName") -enum class AudioQuality(val kbps: String) { - `128KBPS`("128"), - `160KBPS`("160"), - `192KBPS`("192"), - `224KBPS`("224"), - `256KBPS`("256"), - `320KBPS`("320"); - - companion object { - fun getQuality(kbps: String): AudioQuality { - return when (kbps) { - "128" -> `128KBPS` - "160" -> `160KBPS` - "192" -> `192KBPS` - "224" -> `224KBPS` - "256" -> `256KBPS` - "320" -> `320KBPS` - else -> `160KBPS` - } - } - } -} diff --git a/maintenance-tasks/src/main/java/audio_conversion/AudioToMp3.kt b/maintenance-tasks/src/main/java/audio_conversion/AudioToMp3.kt deleted file mode 100644 index 05ee8d6d..00000000 --- a/maintenance-tasks/src/main/java/audio_conversion/AudioToMp3.kt +++ /dev/null @@ -1,101 +0,0 @@ -package audio_conversion - -import analytics_html_img.client -import io.ktor.client.request.forms.formData -import io.ktor.client.request.forms.submitFormWithBinaryData -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.headers -import io.ktor.client.statement.HttpStatement -import io.ktor.http.isSuccess -import kotlinx.coroutines.delay -import utils.debug - -object AudioToMp3 { - - suspend fun convertToMp3( - URL: String, - audioQuality: AudioQuality = AudioQuality.getQuality(URL.substringBeforeLast(".").takeLast(3)), - ): String? { - val activeHost = getHost() // ex - https://hostveryfast.onlineconverter.com/file/send - val jobLink = convertRequest(URL, activeHost, audioQuality) // ex - https://www.onlineconverter.com/convert/309a0f2bbaeb5687b04f96b6d65b47bfdd - - // (jobStatus.contains("d")) == COMPLETION - var jobStatus: String - var retryCount = 40 // Set it to optimal level - - do { - jobStatus = try { - client.get( - "${activeHost.removeSuffix("send")}${jobLink.substringAfterLast("/")}" - ) - } catch (e: Exception) { - e.printStackTrace() - "" - } - retryCount-- - debug("Job Status", jobStatus) - if (!jobStatus.contains("d")) delay(400) // Add Delay , to give Server Time to process audio - } while (!jobStatus.contains("d", true) && retryCount != 0) - - return if (jobStatus.equals("d", true)) { - // Return MP3 Download Link - "${activeHost.removeSuffix("send")}${jobLink.substringAfterLast("/")}/download" - } else null - } - - /* - * Response Link Ex : `https://www.onlineconverter.com/convert/11affb6d88d31861fe5bcd33da7b10a26c` - * - to start the conversion - * */ - private suspend fun convertRequest( - URL: String, - host: String? = null, - audioQuality: AudioQuality = AudioQuality.`320KBPS`, - ): String { - val activeHost = host ?: getHost() - val res = client.submitFormWithBinaryData( - url = activeHost, - formData = formData { - append("class", "audio") - append("from", "audio") - append("to", "mp3") - append("source", "url") - append("url", URL.replace("https:", "http:")) - append("audio_quality", audioQuality.kbps) - } - ) { - headers { - header("Host", activeHost.getHostDomain().also { debug(it) }) - header("Origin", "https://www.onlineconverter.com") - header("Referer", "https://www.onlineconverter.com/") - } - }.run { - debug(this) - dropLast(3) // last 3 are useless unicode char - } - - val job = client.get(res) { - headers { - header("Host", "www.onlineconverter.com") - } - }.execute() - debug("Schedule Job ${job.status.isSuccess()}") - return res - } - - // Active Host free to process conversion - // ex - https://hostveryfast.onlineconverter.com/file/send - private suspend fun getHost(): String { - return client.get("https://www.onlineconverter.com/get/host") { - headers { - header("Host", "www.onlineconverter.com") - } - }.also { debug("Active Host", it) } - } - // Extract full Domain from URL - // ex - hostveryfast.onlineconverter.com - private fun String.getHostDomain(): String { - return this.removePrefix("https://").substringBeforeLast(".") + "." + this.substringAfterLast(".").substringBefore("/") - } -} diff --git a/maintenance-tasks/src/main/java/analytics_html_img/Common.kt b/maintenance-tasks/src/main/java/common/Common.kt similarity index 90% rename from maintenance-tasks/src/main/java/analytics_html_img/Common.kt rename to maintenance-tasks/src/main/java/common/Common.kt index a637467d..4d4f427e 100644 --- a/maintenance-tasks/src/main/java/analytics_html_img/Common.kt +++ b/maintenance-tasks/src/main/java/common/Common.kt @@ -1,6 +1,6 @@ @file:Suppress("FunctionName") -package analytics_html_img +package common import io.ktor.client.HttpClient import io.ktor.client.features.HttpTimeout @@ -18,6 +18,7 @@ internal object Common { fun END_SECTION(tagName: String = "HTI") = "" const val USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0" } + internal val client = HttpClient { install(HttpTimeout) install(JsonFeature) { @@ -33,7 +34,3 @@ internal val client = HttpClient { level = LogLevel.INFO } } -internal data class GithubFileContent( - val decryptedContent: String, - val sha: String -) diff --git a/maintenance-tasks/src/main/java/common/ContentUpdation.kt b/maintenance-tasks/src/main/java/common/ContentUpdation.kt new file mode 100644 index 00000000..141e80c7 --- /dev/null +++ b/maintenance-tasks/src/main/java/common/ContentUpdation.kt @@ -0,0 +1,30 @@ +package common + +/* +* Helper Function to Replace Obsolete Content with new Updated Content +* */ +fun getUpdatedContent( + oldContent: String, + newInsertionText: String, + tagName: String +): String{ + return getReplaceableRegex(tagName).replace( + oldContent, + getReplacementText(tagName,newInsertionText) + ) +} + +private fun getReplaceableRegex(tagName: String): Regex { + return """${Common.START_SECTION(tagName)}(?s)(.*)${Common.END_SECTION(tagName)}""".toRegex() +} + +private fun getReplacementText( + tagName: String, + newInsertionText: String +): String { + return """ + ${Common.START_SECTION(tagName)} + $newInsertionText + ${Common.END_SECTION(tagName)} + """.trimIndent() +} \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/common/Date.kt b/maintenance-tasks/src/main/java/common/Date.kt new file mode 100644 index 00000000..0beec41d --- /dev/null +++ b/maintenance-tasks/src/main/java/common/Date.kt @@ -0,0 +1,16 @@ +package common + +import java.util.* + +fun getTodayDate(): String { + val c: Calendar = Calendar.getInstance() + val monthName = arrayOf( + "January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", + "December" + ) + val month = monthName[c.get(Calendar.MONTH)] + val year: Int = c.get(Calendar.YEAR) + val date: Int = c.get(Calendar.DATE) + return " $date $month, $year" +} \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/analytics_html_img/GithubService.kt b/maintenance-tasks/src/main/java/common/GithubService.kt similarity index 77% rename from maintenance-tasks/src/main/java/analytics_html_img/GithubService.kt rename to maintenance-tasks/src/main/java/common/GithubService.kt index 66fbe2ec..5d4496c6 100644 --- a/maintenance-tasks/src/main/java/analytics_html_img/GithubService.kt +++ b/maintenance-tasks/src/main/java/common/GithubService.kt @@ -1,4 +1,4 @@ -package analytics_html_img +package common import io.ktor.client.request.get import io.ktor.client.request.header @@ -8,15 +8,38 @@ import io.ktor.http.ContentType import io.ktor.http.contentType import io.ktor.util.InternalAPI import io.ktor.util.encodeBase64 -import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put +import models.github.GithubFileContent +import models.github.GithubReleasesInfo internal object GithubService { + private const val baseURL = Common.GITHUB_API + + suspend fun getGithubRepoReleasesInfo( + ownerName: String, + repoName: String, + ): GithubReleasesInfo { + return client.get("$baseURL/repos/$ownerName/$repoName/releases") + } + + suspend fun getGithubFileContent( + secrets: Secrets, + fileName: String = "README.md" + ): GithubFileContent { + return getGithubFileContent( + token = secrets.githubToken, + ownerName = secrets.ownerName, + repoName = secrets.repoName, + branchName = secrets.branchName, + fileName = fileName + ) + } + suspend fun getGithubFileContent( token: String, ownerName: String, diff --git a/maintenance-tasks/src/main/java/analytics_html_img/HCTIService.kt b/maintenance-tasks/src/main/java/common/HCTIService.kt similarity index 99% rename from maintenance-tasks/src/main/java/analytics_html_img/HCTIService.kt rename to maintenance-tasks/src/main/java/common/HCTIService.kt index 68f97642..e36400d5 100644 --- a/maintenance-tasks/src/main/java/analytics_html_img/HCTIService.kt +++ b/maintenance-tasks/src/main/java/common/HCTIService.kt @@ -1,4 +1,4 @@ -package analytics_html_img +package common import io.ktor.client.request.header import io.ktor.client.request.headers diff --git a/maintenance-tasks/src/main/java/analytics_html_img/Secrets.kt b/maintenance-tasks/src/main/java/common/Secrets.kt similarity index 97% rename from maintenance-tasks/src/main/java/analytics_html_img/Secrets.kt rename to maintenance-tasks/src/main/java/common/Secrets.kt index a9f1412a..0fce08e4 100644 --- a/maintenance-tasks/src/main/java/analytics_html_img/Secrets.kt +++ b/maintenance-tasks/src/main/java/common/Secrets.kt @@ -1,4 +1,4 @@ -package analytics_html_img +package common import utils.byOptionalProperty import utils.byProperty diff --git a/maintenance-tasks/src/main/java/jiosaavn/JioSaavnRequests.kt b/maintenance-tasks/src/main/java/jiosaavn/JioSaavnRequests.kt deleted file mode 100644 index 3c2fcc98..00000000 --- a/maintenance-tasks/src/main/java/jiosaavn/JioSaavnRequests.kt +++ /dev/null @@ -1,289 +0,0 @@ -package jiosaavn - -import analytics_html_img.client -import audio_conversion.AudioToMp3 -import io.github.shabinder.fuzzywuzzy.diffutils.FuzzySearch -import io.ktor.client.request.forms.FormDataContent -import io.ktor.client.request.get -import io.ktor.http.Parameters -import jiosaavn.models.SaavnAlbum -import jiosaavn.models.SaavnPlaylist -import jiosaavn.models.SaavnSearchResult -import jiosaavn.models.SaavnSong -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put -import utils.debug - -val serializer = Json { - ignoreUnknownKeys = true - isLenient = true -} - -interface JioSaavnRequests { - - suspend fun findSongDownloadURL( - trackName: String, - trackArtists: List, - ): String? { - val songs = searchForSong(trackName) - val bestMatches = sortByBestMatch(songs, trackName, trackArtists) - val m4aLink = bestMatches.keys.firstOrNull()?.let { - getSongFromID(it).media_url - } - val mp3Link = m4aLink?.let { AudioToMp3.convertToMp3(it) } - return mp3Link - } - - suspend fun searchForSong( - query: String, - includeLyrics: Boolean = false - ): List { - /*if (query.startsWith("http") && query.contains("saavn.com")) { - return listOf(getSong(query)) - }*/ - - val searchURL = search_base_url + query - val results = mutableListOf() - (serializer.parseToJsonElement(client.get(searchURL)) as JsonObject).getJsonObject("songs").getJsonArray("data")?.forEach { - (it as? JsonObject)?.formatData()?.let { jsonObject -> - results.add(serializer.decodeFromJsonElement(SaavnSearchResult.serializer(), jsonObject)) - } - } - return results - } - - suspend fun getLyrics(ID: String): String? { - return (Json.parseToJsonElement(client.get(lyrics_base_url + ID)) as JsonObject) - .getString("lyrics") - } - - suspend fun getSong( - URL: String, - fetchLyrics: Boolean = false - ): SaavnSong { - val id = getSongID(URL) - val data = ((serializer.parseToJsonElement(client.get(song_details_base_url + id)) as JsonObject)[id] as JsonObject) - .formatData(fetchLyrics) - return serializer.decodeFromJsonElement(SaavnSong.serializer(), data) - } - suspend fun getSongFromID( - ID: String, - fetchLyrics: Boolean = false - ): SaavnSong { - val data = ((serializer.parseToJsonElement(client.get(song_details_base_url + ID)) as JsonObject)[ID] as JsonObject) - .formatData(fetchLyrics) - return serializer.decodeFromJsonElement(SaavnSong.serializer(), data) - } - - private suspend fun getSongID( - URL: String, - ): String { - val res = client.get(URL) { - body = FormDataContent( - Parameters.build { - append("bitrate", "320") - } - ) - } - return try { - res.split("\"song\":{\"type\":\"")[1].split("\",\"image\":")[0].split("\"id\":\"").last() - } catch (e: IndexOutOfBoundsException) { - res.split("\"pid\":\"")[1].split("\",\"").first() - } - } - - suspend fun getPlaylist( - URL: String, - includeLyrics: Boolean = false - ): SaavnPlaylist? { - return try { - serializer.decodeFromJsonElement( - SaavnPlaylist.serializer(), - (serializer.parseToJsonElement(client.get(playlist_details_base_url + getPlaylistID(URL))) as JsonObject) - .formatData(includeLyrics) - ) - } catch (e: Exception) { - e.printStackTrace() - null - } - } - - private suspend fun getPlaylistID( - URL: String - ): String { - val res = client.get(URL) - return try { - res.split("\"type\":\"playlist\",\"id\":\"")[1].split('"')[0] - } catch (e: IndexOutOfBoundsException) { - res.split("\"page_id\",\"")[1].split("\",\"")[0] - } - } - - suspend fun getAlbum( - URL: String, - includeLyrics: Boolean = false - ): SaavnAlbum? { - return try { - serializer.decodeFromJsonElement( - SaavnAlbum.serializer(), - (serializer.parseToJsonElement(client.get(album_details_base_url + getAlbumID(URL))) as JsonObject) - .formatData(includeLyrics) - ) - } catch (e: Exception) { - e.printStackTrace() - null - } - } - - private suspend fun getAlbumID( - URL: String - ): String { - val res = client.get(URL) - return try { - res.split("\"album_id\":\"")[1].split('"')[0] - } catch (e: IndexOutOfBoundsException) { - res.split("\"page_id\",\"")[1].split("\",\"")[0] - } - } - - private suspend fun JsonObject.formatData( - includeLyrics: Boolean = false - ): JsonObject { - return buildJsonObject { - // Accommodate Incoming Json Object Data - // And `Format` everything while iterating - this@formatData.forEach { - if (it.value is JsonPrimitive && it.value.jsonPrimitive.isString) { - put(it.key, it.value.jsonPrimitive.content.format()) - } else { - // Format Songs Nested Collection Too - if (it.key == "songs" && it.value is JsonArray) { - put( - it.key, - buildJsonArray { - getJsonArray("songs")?.forEach { song -> - (song as? JsonObject)?.formatData(includeLyrics)?.let { formattedSong -> - add(formattedSong) - } - } - } - ) - } else { - put(it.key, it.value) - } - } - } - - try { - var url = getString("media_preview_url")!!.replace("preview", "aac") // We Will catch NPE - url = if (getBoolean("320kbps") == true) { - url.replace("_96_p.mp4", "_320.mp4") - } else { - url.replace("_96_p.mp4", "_160.mp4") - } - // Add Media URL to JSON Object - put("media_url", url) - } catch (e: Exception) { - // e.printStackTrace() - // DECRYPT Encrypted Media URL - getString("encrypted_media_url")?.let { - put("media_url", decryptURL(it)) - } - // Check if 320 Kbps is available or not - if (getBoolean("320kbps") != true && containsKey("media_url")) { - put("media_url", getString("media_url")?.replace("_320.mp4", "_160.mp4")) - } - } - // Increase Image Resolution - put( - "image", - getString("image") - ?.replace("150x150", "500x500") - ?.replace("50x50", "500x500") - ) - - // Fetch Lyrics if Requested - // Lyrics is HTML Based - if (includeLyrics) { - if (getBoolean("has_lyrics") == true) { - put("lyrics", getString("id")?.let { getLyrics(it) }) - } else { - put("lyrics", "") - } - } - } - } - - fun sortByBestMatch( - tracks: List, - trackName: String, - trackArtists: List, - ): Map { - - /* - * "linksWithMatchValue" is map with Saavn VideoID and its rating/match with 100 as Max Value - **/ - val linksWithMatchValue = mutableMapOf() - - for (result in tracks) { - var hasCommonWord = false - - val resultName = result.title.toLowerCase().replace("/", " ") - val trackNameWords = trackName.toLowerCase().split(" ") - - for (nameWord in trackNameWords) { - if (nameWord.isNotBlank() && FuzzySearch.partialRatio(nameWord, resultName) > 85) hasCommonWord = true - } - - // Skip this Result if No Word is Common in Name - if (!hasCommonWord) { - debug("Saavn Removing Common Word: ", result.toString()) - continue - } - - // Find artist match - // Will Be Using Fuzzy Search Because YT Spelling might be mucked up - // match = (no of artist names in result) / (no. of artist names on spotify) * 100 - var artistMatchNumber = 0 - - // String Containing All Artist Names from JioSaavn Search Result - val artistListString = mutableSetOf().apply { - result.more_info?.singers?.split(",")?.let { addAll(it) } - result.more_info?.primary_artists?.toLowerCase()?.split(",")?.let { addAll(it) } - }.joinToString(" , ") - - for (artist in trackArtists) { - if (FuzzySearch.partialRatio(artist.toLowerCase(), artistListString) > 85) - artistMatchNumber++ - } - - if (artistMatchNumber == 0) { - debug("Artist Match Saavn Removing: $result") - continue - } - val artistMatch: Float = (artistMatchNumber.toFloat() / trackArtists.size) * 100 - val nameMatch: Float = FuzzySearch.partialRatio(resultName, trackName).toFloat() / 100 - val avgMatch = (artistMatch + nameMatch) / 2 - - linksWithMatchValue[result.id] = avgMatch - } - return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also { - debug("Match Found for $trackName - ${!it.isNullOrEmpty()}") - } - } - - companion object { - // EndPoints - const val search_base_url = "https://www.jiosaavn.com/api.php?__call=autocomplete.get&_format=json&_marker=0&cc=in&includeMetaTags=1&query=" - const val song_details_base_url = "https://www.jiosaavn.com/api.php?__call=song.getDetails&cc=in&_marker=0%3F_marker%3D0&_format=json&pids=" - const val album_details_base_url = "https://www.jiosaavn.com/api.php?__call=content.getAlbumDetails&_format=json&cc=in&_marker=0%3F_marker%3D0&albumid=" - const val playlist_details_base_url = "https://www.jiosaavn.com/api.php?__call=playlist.getDetails&_format=json&cc=in&_marker=0%3F_marker%3D0&listid=" - const val lyrics_base_url = "https://www.jiosaavn.com/api.php?__call=lyrics.getLyrics&ctx=web6dot0&api_version=4&_format=json&_marker=0%3F_marker%3D0&lyrics_id=" - } -} diff --git a/maintenance-tasks/src/main/java/jiosaavn/JioSaavnUtils.kt b/maintenance-tasks/src/main/java/jiosaavn/JioSaavnUtils.kt deleted file mode 100644 index 2d203028..00000000 --- a/maintenance-tasks/src/main/java/jiosaavn/JioSaavnUtils.kt +++ /dev/null @@ -1,121 +0,0 @@ -package jiosaavn - -import io.ktor.util.InternalAPI -import io.ktor.util.decodeBase64Bytes -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put -import utils.unescape -import java.security.SecureRandom -import javax.crypto.Cipher -import javax.crypto.SecretKey -import javax.crypto.SecretKeyFactory -import javax.crypto.spec.DESKeySpec - -internal suspend fun JsonObject.formatData( - includeLyrics: Boolean = false -): JsonObject { - return buildJsonObject { - // Accommodate Incoming Json Object Data - // And `Format` everything while iterating - this@formatData.forEach { - if (it.value is JsonPrimitive && it.value.jsonPrimitive.isString) { - put(it.key, it.value.jsonPrimitive.content.format()) - } else { - // Format Songs Nested Collection Too - if (it.key == "songs" && it.value is JsonArray) { - put( - it.key, - buildJsonArray { - getJsonArray("songs")?.forEach { song -> - (song as? JsonObject)?.formatData(includeLyrics)?.let { formattedSong -> - add(formattedSong) - } - } - } - ) - } else { - put(it.key, it.value) - } - } - } - - try { - var url = getString("media_preview_url")!!.replace("preview", "aac") // We Will catch NPE - url = if (getBoolean("320kbps") == true) { - url.replace("_96_p.mp4", "_320.mp4") - } else { - url.replace("_96_p.mp4", "_160.mp4") - } - // Add Media URL to JSON Object - put("media_url", url) - } catch (e: Exception) { - // e.printStackTrace() - // DECRYPT Encrypted Media URL - getString("encrypted_media_url")?.let { - put("media_url", decryptURL(it)) - } - // Check if 320 Kbps is available or not - if (getBoolean("320kbps") != true && containsKey("media_url")) { - put("media_url", getString("media_url")?.replace("_320.mp4", "_160.mp4")) - } - } - // Increase Image Resolution - put( - "image", - getString("image") - ?.replace("150x150", "500x500") - ?.replace("50x50", "500x500") - ) - - // Fetch Lyrics if Requested - // Lyrics is HTML Based - if (includeLyrics) { - if (getBoolean("has_lyrics") == true) { - put("lyrics", getString("id")?.let { object : JioSaavnRequests {}.getLyrics(it) }) - } else { - put("lyrics", "") - } - } - } -} - -@Suppress("GetInstance") -@OptIn(InternalAPI::class) -suspend fun decryptURL(url: String): String { - val dks = DESKeySpec("38346591".toByteArray()) - val keyFactory = SecretKeyFactory.getInstance("DES") - val key: SecretKey = keyFactory.generateSecret(dks) - - val cipher: Cipher = Cipher.getInstance("DES/ECB/PKCS5Padding").apply { - init(Cipher.DECRYPT_MODE, key, SecureRandom()) - } - - return cipher.doFinal(url.decodeBase64Bytes()) - .decodeToString() - .replace("_96.mp4", "_320.mp4") -} - -internal fun String.format(): String { - return this.unescape() - .replace(""", "'") - .replace("&", "&") - .replace("'", "'") - .replace("©", "©") -} - -fun JsonObject.getString(key: String): String? = this[key]?.jsonPrimitive?.content -fun JsonObject.getLong(key: String): Long = this[key]?.jsonPrimitive?.content?.toLongOrNull() ?: 0 -fun JsonObject.getInteger(key: String): Int = this[key]?.jsonPrimitive?.content?.toIntOrNull() ?: 0 -fun JsonObject.getBoolean(key: String): Boolean? = this[key]?.jsonPrimitive?.content?.toBoolean() -fun JsonObject.getFloat(key: String): Float? = this[key]?.jsonPrimitive?.content?.toFloatOrNull() -fun JsonObject.getDouble(key: String): Double? = this[key]?.jsonPrimitive?.content?.toDoubleOrNull() -fun JsonObject?.getJsonObject(key: String): JsonObject? = this?.get(key)?.jsonObject -fun JsonArray?.getJsonObject(index: Int): JsonObject? = this?.get(index)?.jsonObject -fun JsonObject?.getJsonArray(key: String): JsonArray? = this?.get(key)?.jsonArray diff --git a/maintenance-tasks/src/main/java/jiosaavn/models/MoreInfo.kt b/maintenance-tasks/src/main/java/jiosaavn/models/MoreInfo.kt deleted file mode 100644 index db1d70cd..00000000 --- a/maintenance-tasks/src/main/java/jiosaavn/models/MoreInfo.kt +++ /dev/null @@ -1,10 +0,0 @@ -package jiosaavn.models - -import kotlinx.serialization.Serializable - -@Serializable -data class MoreInfo( - val language: String, - val primary_artists: String, - val singers: String, -) diff --git a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnAlbum.kt b/maintenance-tasks/src/main/java/jiosaavn/models/SaavnAlbum.kt deleted file mode 100644 index 558084d0..00000000 --- a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnAlbum.kt +++ /dev/null @@ -1,17 +0,0 @@ -package jiosaavn.models - -import kotlinx.serialization.Serializable - -@Serializable -data class SaavnAlbum( - val albumid: String, - val image: String, - val name: String, - val perma_url: String, - val primary_artists: String, - val primary_artists_id: String, - val release_date: String, - val songs: List, - val title: String, - val year: String -) diff --git a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnPlaylist.kt b/maintenance-tasks/src/main/java/jiosaavn/models/SaavnPlaylist.kt deleted file mode 100644 index df0bfd9a..00000000 --- a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnPlaylist.kt +++ /dev/null @@ -1,22 +0,0 @@ -package jiosaavn.models - -import kotlinx.serialization.Serializable - -@Serializable -data class SaavnPlaylist( - val fan_count: Int? = 0, - val firstname: String? = null, - val follower_count: Long? = null, - val image: String, - val images: List? = null, - val last_updated: String, - val lastname: String? = null, - val list_count: String? = null, - val listid: String? = null, - val listname: String, // Title - val perma_url: String, - val songs: List, - val sub_types: List? = null, - val type: String = "", // chart,etc - val uid: String? = null, -) diff --git a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnSearchResult.kt b/maintenance-tasks/src/main/java/jiosaavn/models/SaavnSearchResult.kt deleted file mode 100644 index 628018fd..00000000 --- a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnSearchResult.kt +++ /dev/null @@ -1,17 +0,0 @@ -package jiosaavn.models - -import kotlinx.serialization.Serializable - -@Serializable -data class SaavnSearchResult( - val album: String? = "", - val description: String, - val id: String, - val image: String, - val title: String, - val type: String, - val url: String, - val ctr: Int? = 0, - val position: Int? = 0, - val more_info: MoreInfo? = null, -) diff --git a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnSong.kt b/maintenance-tasks/src/main/java/jiosaavn/models/SaavnSong.kt deleted file mode 100644 index b7c113bf..00000000 --- a/maintenance-tasks/src/main/java/jiosaavn/models/SaavnSong.kt +++ /dev/null @@ -1,41 +0,0 @@ -package jiosaavn.models - -import kotlinx.serialization.Serializable - -@Serializable -data class SaavnSong( - val `320kbps`: Boolean, - val album: String, - val album_url: String? = null, - val albumid: String? = null, - val artistMap: Map, - val copyright_text: String? = null, - val duration: String, - val encrypted_media_path: String, - val encrypted_media_url: String, - val explicit_content: Int = 0, - val has_lyrics: Boolean = false, - val id: String, - val image: String, - val label: String? = null, - val label_url: String? = null, - val language: String, - val lyrics_snippet: String? = null, - val media_preview_url: String? = null, - val media_url: String? = null, // Downloadable M4A Link - val music: String, - val music_id: String, - val origin: String? = null, - val perma_url: String? = null, - val play_count: Int = 0, - val primary_artists: String, - val primary_artists_id: String, - val release_date: String, // Format - 2021-05-04 - val singers: String, - val song: String, // title - val starring: String? = null, - val type: String = "", - val vcode: String? = null, - val vlink: String? = null, - val year: String -) diff --git a/maintenance-tasks/src/main/java/main.kt b/maintenance-tasks/src/main/java/main.kt index 745ce592..5977d134 100644 --- a/maintenance-tasks/src/main/java/main.kt +++ b/maintenance-tasks/src/main/java/main.kt @@ -1,9 +1,46 @@ -import analytics_html_img.updateAnalyticsImage +import common.GithubService +import common.Secrets +import kotlinx.coroutines.runBlocking +import scripts.updateAnalyticsImage +import scripts.updateDownloadCards import utils.debug fun main(args: Array) { debug("fun main: args -> ${args.joinToString(";")}") + val secrets = Secrets.initSecrets() - // TASK -> Update Analytics Image in Readme - updateAnalyticsImage() + runBlocking { + + val githubFileContent = GithubService.getGithubFileContent( + secrets = secrets, + fileName = "README.md" + ) + + // Content To be Processed + var updatedGithubContent: String = githubFileContent.decryptedContent + + // TASK -> Update Analytics Image in Readme + updatedGithubContent = updateAnalyticsImage( + updatedGithubContent, + secrets + ) + + // TASK -> Update Total Downloads Card + updatedGithubContent = updateDownloadCards( + updatedGithubContent, + secrets.copy(tagName = "DCI") + ) + + // Write New Updated README.md + GithubService.updateGithubFileContent( + token = secrets.githubToken, + ownerName = secrets.ownerName, + repoName = secrets.repoName, + branchName = secrets.branchName, + fileName = secrets.filePath, + commitMessage = secrets.commitMessage, + rawContent = updatedGithubContent, + sha = githubFileContent.sha + ) + } } diff --git a/maintenance-tasks/src/main/java/models/github/Asset.kt b/maintenance-tasks/src/main/java/models/github/Asset.kt new file mode 100644 index 00000000..02e11004 --- /dev/null +++ b/maintenance-tasks/src/main/java/models/github/Asset.kt @@ -0,0 +1,17 @@ +package models.github + +data class Asset( + val browser_download_url: String, + val content_type: String, + val created_at: String, + val download_count: Int, + val id: Int, + val label: Any, + val name: String, + val node_id: String, + val size: Int, + val state: String, + val updated_at: String, + val uploader: Uploader, + val url: String +) \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/models/github/Author.kt b/maintenance-tasks/src/main/java/models/github/Author.kt new file mode 100644 index 00000000..895cce81 --- /dev/null +++ b/maintenance-tasks/src/main/java/models/github/Author.kt @@ -0,0 +1,22 @@ +package models.github + +data class Author( + val avatar_url: String, + val events_url: String, + val followers_url: String, + val following_url: String, + val gists_url: String, + val gravatar_id: String, + val html_url: String, + val id: Int, + val login: String, + val node_id: String, + val organizations_url: String, + val received_events_url: String, + val repos_url: String, + val site_admin: Boolean, + val starred_url: String, + val subscriptions_url: String, + val type: String, + val url: String +) \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/models/github/GithubFileContent.kt b/maintenance-tasks/src/main/java/models/github/GithubFileContent.kt new file mode 100644 index 00000000..bc274ff4 --- /dev/null +++ b/maintenance-tasks/src/main/java/models/github/GithubFileContent.kt @@ -0,0 +1,6 @@ +package models.github + +data class GithubFileContent( + val decryptedContent: String, + val sha: String +) \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/models/github/GithubReleaseInfoItem.kt b/maintenance-tasks/src/main/java/models/github/GithubReleaseInfoItem.kt new file mode 100644 index 00000000..33563163 --- /dev/null +++ b/maintenance-tasks/src/main/java/models/github/GithubReleaseInfoItem.kt @@ -0,0 +1,23 @@ +package models.github + +data class GithubReleaseInfoItem( + val assets: List, + val assets_url: String, + val author: Author, + val body: String, + val created_at: String, + val draft: Boolean, + val html_url: String, + val id: Int, + val name: String, + val node_id: String, + val prerelease: Boolean, + val published_at: String, + val reactions: Reactions, + val tag_name: String, + val tarball_url: String, + val target_commitish: String, + val upload_url: String, + val url: String, + val zipball_url: String +) \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/models/github/GithubReleasesInfo.kt b/maintenance-tasks/src/main/java/models/github/GithubReleasesInfo.kt new file mode 100644 index 00000000..38e0340c --- /dev/null +++ b/maintenance-tasks/src/main/java/models/github/GithubReleasesInfo.kt @@ -0,0 +1,3 @@ +package models.github + +class GithubReleasesInfo : ArrayList() \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/models/github/Reactions.kt b/maintenance-tasks/src/main/java/models/github/Reactions.kt new file mode 100644 index 00000000..71b4acd9 --- /dev/null +++ b/maintenance-tasks/src/main/java/models/github/Reactions.kt @@ -0,0 +1,16 @@ +package models.github + +import kotlinx.serialization.json.JsonNames + +data class Reactions( + @JsonNames("+1") val upVotes: Int = 0, + @JsonNames("-1") val downVotes: Int = 0, + val confused: Int = 0, + val eyes: Int = 0, + val heart: Int = 0, + val hooray: Int = 0, + val laugh: Int = 0, + val rocket: Int = 0, + val total_count: Int = 0, + val url: String? = null +) \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/models/github/Uploader.kt b/maintenance-tasks/src/main/java/models/github/Uploader.kt new file mode 100644 index 00000000..8085f98b --- /dev/null +++ b/maintenance-tasks/src/main/java/models/github/Uploader.kt @@ -0,0 +1,22 @@ +package models.github + +data class Uploader( + val avatar_url: String, + val events_url: String, + val followers_url: String, + val following_url: String, + val gists_url: String, + val gravatar_id: String, + val html_url: String, + val id: Int, + val login: String, + val node_id: String, + val organizations_url: String, + val received_events_url: String, + val repos_url: String, + val site_admin: Boolean, + val starred_url: String, + val subscriptions_url: String, + val type: String, + val url: String +) \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/scripts/UpdateAnalyticsImage.kt b/maintenance-tasks/src/main/java/scripts/UpdateAnalyticsImage.kt new file mode 100644 index 00000000..2934a998 --- /dev/null +++ b/maintenance-tasks/src/main/java/scripts/UpdateAnalyticsImage.kt @@ -0,0 +1,65 @@ +package scripts + +import common.* +import io.ktor.client.features.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import utils.RETRY_LIMIT_EXHAUSTED +import utils.debug + +internal suspend fun updateAnalyticsImage( + fileContent: String? = null, + secrets: Secrets +): String { + // debug("fun main: secrets -> $secrets") + + val oldContent = fileContent ?: GithubService.getGithubFileContent( + secrets = secrets, + fileName = "README.md" + ).decryptedContent + + // debug("OLD FILE CONTENT",oldGithubFile) + val imageURL = getAnalyticsImage().also { + debug("Updated IMAGE", it) + } + + return getUpdatedContent( + oldContent, + "![Today's Analytics]($imageURL)", + secrets.tagName + ) +} + +internal suspend fun getAnalyticsImage(): String { + var contentLength: Long + var analyticsImage: String + var retryCount = 5 + + do { + /* + * Get a new Image from Analytics, + * - Use Any Random useless query param , + * As HCTI Demo, `caches value for a specific Link` + * */ + val randomID = (1..100000).random() + analyticsImage = HCTIService.getImageURLFromURL( + url = "https://kind-grasshopper-73.telebit.io/matomo/index.php?module=Widgetize&action=iframe&containerId=VisitOverviewWithGraph&disableLink=0&widget=1&moduleToWidgetize=CoreHome&actionToWidgetize=renderWidgetContainer&idSite=1&period=day&date=yesterday&disableLink=1&widget=$randomID", + delayInMilliSeconds = 5000 + ) + + // Sometimes we get incomplete image, hence verify `content-length` + val req = client.head(analyticsImage) { + timeout { + socketTimeoutMillis = 100_000 + } + } + contentLength = req.headers["Content-Length"]?.toLong() ?: 0 + debug(contentLength.toString()) + + if(retryCount-- == 0){ + // FAIL Gracefully + throw(RETRY_LIMIT_EXHAUSTED()) + } + }while (contentLength<1_20_000) + return analyticsImage +} diff --git a/maintenance-tasks/src/main/java/scripts/UpdateDownloadCards.kt b/maintenance-tasks/src/main/java/scripts/UpdateDownloadCards.kt new file mode 100644 index 00000000..b38cde5c --- /dev/null +++ b/maintenance-tasks/src/main/java/scripts/UpdateDownloadCards.kt @@ -0,0 +1,213 @@ +package scripts + +import common.* +import io.ktor.client.features.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import utils.RETRY_LIMIT_EXHAUSTED +import utils.debug + +internal suspend fun updateDownloadCards( + fileContent: String? = null, + secrets: Secrets +): String { + + val oldContent = fileContent ?: GithubService.getGithubFileContent( + secrets = secrets, + fileName = "README.md" + ).decryptedContent + + val totalDownloads:Int = GithubService.getGithubRepoReleasesInfo( + secrets.ownerName, + secrets.repoName + ).let { allReleases -> + var totalCount = 0 + + for(release in allReleases){ + release.assets.forEach { + debug("${it.name}: ${release.tag_name}" ,"Downloads: ${it.download_count}") + totalCount += it.download_count + } + } + + debug("Total Download Count: $totalCount") + + return@let totalCount + } + + + return getUpdatedContent( + oldContent, + """ + + Total Downloads + + """.trimIndent(), + secrets.tagName + ) +} + +private suspend fun getDownloadCard( + count: Int +): String { + var contentLength: Long + var downloadCard: String + var retryCount = 5 + + do { + downloadCard = HCTIService.getImageURLFromHtml( + html = getDownloadCardHtml( + count = count, + date = getTodayDate() + ), + css = downloadCardCSS, + viewPortHeight = "170", + viewPortWidth = "385" + ) + + // Sometimes we get incomplete image, hence verify `content-length` + val req = client.head(downloadCard) { + timeout { + socketTimeoutMillis = 100_000 + } + } + contentLength = req.headers["Content-Length"]?.toLong() ?: 0 + // debug(contentLength.toString()) + + if(retryCount-- == 0){ + // FAIL Gracefully + throw(RETRY_LIMIT_EXHAUSTED()) + } + }while (contentLength<40_000) + return downloadCard +} + + +fun getDownloadCardHtml( + count: Int, + date: String, // ex: 06 Jun 2021 +):String { + return """ +
+
+ +
+

Total Downloads

+

Github & F-Droid

+ +
+
+
+ """.trimIndent() +} + +val downloadCardCSS = + """ + @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); + + * { + margin: 0; + padding: 0; + } + + html, + body { + overflow: hidden; + } + + .card-container { + height: 150px; + width: 360px; + padding: 8px 12px; + display: flex; + transition: 0.3s; + } + + #card { + display: flex; + align-self: center; + width: fit-content; + background: linear-gradient(120deg, #f0f0f0 20%, #f9f9f9 30%); + border-radius: 22px; + padding: 20px 40px; + margin: 0 auto; + box-shadow: 4px 8px 20px rgba(0, 0, 0, 0.06); + transition: 0.3s; + } + + #card:hover { + box-shadow: none; + cursor: pointer; + transform: translateY(2px) + } + + #card:hover > #profile-photo { + opacity: 1 + } + + #profile-photo { + height: 90px; + width: 90px; + border-radius: 100px; + align-self: center; + box-shadow: 0 6px 30px rgba(199, 199, 199, 0.5); + opacity: 0.8; + transition: 0.3s; + } + + .text-wrapper { + font-family: 'Poppins', sans-serif; + line-height: 0; + align-self: center; + margin-left: 20px; + } + + .text-wrapper p { + margin: 0; + } + + .contact-wrapper a { + display: block; + text-decoration: none; + } + + #title { + font-size: 20px; + color: #5f5f5f; + margin-bottom: 20px; + } + + #source { + font-size: 12px; + color: #9B9B9B; + margin-bottom: 22px; + } + + #count { + padding-top: 8px; + font-size: 30px; + color: #615F5F; + margin-top: 15px; + transition: 0.3s; + } + #date { + padding-top: 12px; + font-size: 14px; + color: #615F5F; + margin-top: 15px; + transition: 0.3s; + } + + #count:hover, + #date:hover { + color: #9B9B9B; + } + """.trimIndent() \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/utils/Ext.kt b/maintenance-tasks/src/main/java/utils/Ext.kt index 82f16fb4..cf7c2503 100644 --- a/maintenance-tasks/src/main/java/utils/Ext.kt +++ b/maintenance-tasks/src/main/java/utils/Ext.kt @@ -6,4 +6,4 @@ val String.byProperty: String get() = System.getenv(this) val String.byOptionalProperty: String? get() = System.getenv(this) fun debug(message: String) = println("::debug::$message") -fun debug(tag: String, message: String) = println("::debug::$tag:\n$message") +fun debug(tag: String, message: String) = println("::debug::$tag:\n$message") \ No newline at end of file diff --git a/maintenance-tasks/src/main/java/utils/JsonUtils.kt b/maintenance-tasks/src/main/java/utils/JsonUtils.kt deleted file mode 100644 index 7f2162dc..00000000 --- a/maintenance-tasks/src/main/java/utils/JsonUtils.kt +++ /dev/null @@ -1,92 +0,0 @@ -package utils - -/* -* JSON UTILS -* */ -fun String.escape(): String { - val output = StringBuilder() - for (element in this) { - val chx = element.toInt() - assert(chx != 0) - when { - element == '\n' -> { - output.append("\\n") - } - element == '\t' -> { - output.append("\\t") - } - element == '\r' -> { - output.append("\\r") - } - element == '\\' -> { - output.append("\\\\") - } - element == '"' -> { - output.append("\\\"") - } - element == '\b' -> { - output.append("\\b") - } - chx >= 0x10000 -> { - assert(false) { "Java stores as u16, so it should never give us a character that's bigger than 2 bytes. It literally can't." } - } - chx > 127 -> { - output.append(String.format("\\u%04x", chx)) - } - else -> { - output.append(element) - } - } - } - return output.toString() -} - -fun String.unescape(): String { - val builder = StringBuilder() - var i = 0 - while (i < this.length) { - val delimiter = this[i] - i++ // consume letter or backslash - if (delimiter == '\\' && i < this.length) { - - // consume first after backslash - val ch = this[i] - i++ - when (ch) { - '\\', '/', '"', '\'' -> { - builder.append(ch) - } - 'n' -> builder.append('\n') - 'r' -> builder.append('\r') - 't' -> builder.append( - '\t' - ) - 'b' -> builder.append('\b') - 'f' -> builder.append("\\f") - 'u' -> { - val hex = StringBuilder() - - // expect 4 digits - if (i + 4 > this.length) { - throw RuntimeException("Not enough unicode digits! ") - } - for (x in this.substring(i, i + 4).toCharArray()) { - if (!Character.isLetterOrDigit(x)) { - throw RuntimeException("Bad character in unicode escape.") - } - hex.append(Character.toLowerCase(x)) - } - i += 4 // consume those four digits. - val code = hex.toString().toInt(16) - builder.append(code.toChar()) - } - else -> { - throw RuntimeException("Illegal escape sequence: \\$ch") - } - } - } else { // it's not a backslash, or it's the last character. - builder.append(delimiter) - } - } - return builder.toString() -} diff --git a/maintenance-tasks/src/main/java/utils/TestClass.kt b/maintenance-tasks/src/main/java/utils/TestClass.kt index a516fa00..3ffb46b9 100644 --- a/maintenance-tasks/src/main/java/utils/TestClass.kt +++ b/maintenance-tasks/src/main/java/utils/TestClass.kt @@ -3,5 +3,4 @@ package utils import kotlinx.coroutines.runBlocking // Test Class- at development Time -fun main(): Unit = runBlocking { -} +fun main(): Unit = runBlocking {} diff --git a/web-app/build.gradle.kts b/web-app/build.gradle.kts index ffe2580c..7d5205d3 100644 --- a/web-app/build.gradle.kts +++ b/web-app/build.gradle.kts @@ -49,9 +49,9 @@ dependencies { implementation(project(":common:dependency-injection")) implementation("co.touchlab:stately-common:1.1.7") implementation("dev.icerock.moko:parcelize:0.6.1") -// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1") + // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") { -// https://youtrack.jetbrains.com/issue/KTOR-2670 + // https://youtrack.jetbrains.com/issue/KTOR-2670 isForce = true } implementation("org.jetbrains:kotlin-react:17.0.1-pre.148-kotlin-1.4.30") @@ -77,6 +77,5 @@ kotlin { } } } - binaries.executable() } } \ No newline at end of file