mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
Dep Updates, Maintenance Tasks Updated
This commit is contained in:
parent
6b10d63675
commit
4b18c099b6
@ -131,7 +131,7 @@ dependencies {
|
|||||||
//implementation("com.jakewharton.timber:timber:4.7.1")
|
//implementation("com.jakewharton.timber:timber:4.7.1")
|
||||||
implementation("dev.icerock.moko:parcelize:0.6.1")
|
implementation("dev.icerock.moko:parcelize:0.6.1")
|
||||||
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
|
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
|
// Test
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
@ -32,6 +32,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
useIR = true
|
useIR = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ object Versions {
|
|||||||
const val versionCode = 20
|
const val versionCode = 20
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
const val kotlinVersion = "1.4.32"
|
const val kotlinVersion = "1.5.10"
|
||||||
const val coroutinesVersion = "1.4.2"
|
const val coroutinesVersion = "1.4.2"
|
||||||
|
|
||||||
// Code Formatting
|
// Code Formatting
|
||||||
@ -51,6 +51,7 @@ object Versions {
|
|||||||
const val targetSdkVersion = 29
|
const val targetSdkVersion = 29
|
||||||
const val androidLifecycle = "2.3.0"
|
const val androidLifecycle = "2.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
object HostOS {
|
object HostOS {
|
||||||
// Host OS Properties
|
// Host OS Properties
|
||||||
private val hostOs = System.getProperty("os.name")
|
private val hostOs = System.getProperty("os.name")
|
||||||
@ -58,12 +59,14 @@ object HostOS {
|
|||||||
val isMac = hostOs.startsWith("Mac",true)
|
val isMac = hostOs.startsWith("Mac",true)
|
||||||
val isLinux = hostOs.startsWith("Linux",true)
|
val isLinux = hostOs.startsWith("Linux",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Koin {
|
object Koin {
|
||||||
val core = "io.insert-koin:koin-core:${Versions.koin}"
|
val core = "io.insert-koin:koin-core:${Versions.koin}"
|
||||||
val test = "io.insert-koin:koin-test:${Versions.koin}"
|
val test = "io.insert-koin:koin-test:${Versions.koin}"
|
||||||
val android = "io.insert-koin:koin-android:${Versions.koin}"
|
val android = "io.insert-koin:koin-android:${Versions.koin}"
|
||||||
val compose = "io.insert-koin:koin-androidx-compose:3.0.1"
|
val compose = "io.insert-koin:koin-androidx-compose:3.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Androidx {
|
object Androidx {
|
||||||
const val androidxActivity = "androidx.activity:activity-compose:1.3.0-alpha07"
|
const val androidxActivity = "androidx.activity:activity-compose:1.3.0-alpha07"
|
||||||
const val core = "androidx.core:core-ktx:1.3.2"
|
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}"
|
const val runtimeLiveData = "androidx.compose.runtime:runtime-livedata:${Versions.compose}"
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
object JetBrains {
|
object JetBrains {
|
||||||
object Kotlin {
|
object Kotlin {
|
||||||
const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlinVersion}"
|
const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlinVersion}"
|
||||||
@ -94,17 +98,19 @@ object JetBrains {
|
|||||||
|
|
||||||
object Compose {
|
object Compose {
|
||||||
// __LATEST_COMPOSE_RELEASE_VERSION__
|
// __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"
|
const val gradlePlugin = "org.jetbrains.compose:compose-gradle-plugin:$VERSION"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Decompose {
|
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 decompose = "com.arkivanov.decompose:decompose:$VERSION"
|
||||||
const val decomposeIosX64 = "com.arkivanov.decompose:decompose-iosx64:$VERSION"
|
const val decomposeIosX64 = "com.arkivanov.decompose:decompose-iosx64:$VERSION"
|
||||||
const val decomposeIosArm64 = "com.arkivanov.decompose:decompose-iosarm64:$VERSION"
|
const val decomposeIosArm64 = "com.arkivanov.decompose:decompose-iosarm64:$VERSION"
|
||||||
const val extensionsCompose = "com.arkivanov.decompose:extensions-compose-jetbrains:$VERSION"
|
const val extensionsCompose = "com.arkivanov.decompose:extensions-compose-jetbrains:$VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
object MVIKotlin {
|
object MVIKotlin {
|
||||||
private const val VERSION = "2.0.3"
|
private const val VERSION = "2.0.3"
|
||||||
const val rx = "com.arkivanov.mvikotlin:rx:$VERSION"
|
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 mvikotlinTimeTravel = "com.arkivanov.mvikotlin:mvikotlin-timetravel:$VERSION"
|
||||||
const val mvikotlinExtensionsReaktive = "com.arkivanov.mvikotlin:mvikotlin-extensions-reaktive:$VERSION"
|
const val mvikotlinExtensionsReaktive = "com.arkivanov.mvikotlin:mvikotlin-extensions-reaktive:$VERSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Ktor {
|
object Ktor {
|
||||||
val clientCore = "io.ktor:ktor-client-core:${Versions.ktor}"
|
val clientCore = "io.ktor:ktor-client-core:${Versions.ktor}"
|
||||||
val clientJson = "io.ktor:ktor-client-json:${Versions.ktor}"
|
val clientJson = "io.ktor:ktor-client-json:${Versions.ktor}"
|
||||||
|
@ -23,16 +23,8 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm("desktop").compilations.all {
|
jvm("desktop")
|
||||||
kotlinOptions {
|
android()
|
||||||
useIR = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
android().compilations.all {
|
|
||||||
kotlinOptions {
|
|
||||||
useIR = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
named("commonMain") {
|
named("commonMain") {
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -51,7 +43,7 @@ kotlin {
|
|||||||
|
|
||||||
implementation(Extras.kermit)
|
implementation(Extras.kermit)
|
||||||
implementation("dev.icerock.moko:parcelize:0.6.1")
|
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")
|
@Suppress("DEPRECATION")
|
||||||
isForce = true
|
isForce = true
|
||||||
}
|
}
|
||||||
@ -69,8 +61,4 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -31,25 +31,12 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jvm("desktop").compilations.all {
|
jvm("desktop")
|
||||||
kotlinOptions {
|
android()
|
||||||
useIR = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
android().compilations.all {
|
|
||||||
kotlinOptions {
|
|
||||||
useIR = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
js {
|
js(/*BOTH*/) {
|
||||||
/*
|
|
||||||
* TODO Enable JS IR Compiler
|
|
||||||
* waiting for Decompose & MVI Kotlin to support same
|
|
||||||
* */
|
|
||||||
browser()
|
browser()
|
||||||
// nodejs()
|
// nodejs()
|
||||||
binaries.executable()
|
|
||||||
}
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
named("commonTest") {
|
named("commonTest") {
|
||||||
@ -73,8 +60,4 @@ kotlin {
|
|||||||
dependencies {}
|
dependencies {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -34,24 +34,12 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jvm("desktop").compilations.all {
|
jvm("desktop")
|
||||||
kotlinOptions {
|
android()
|
||||||
useIR = true
|
|
||||||
}
|
js(/*BOTH*/) {
|
||||||
}
|
|
||||||
android().compilations.all {
|
|
||||||
kotlinOptions {
|
|
||||||
useIR = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
js {
|
|
||||||
/*
|
|
||||||
* TODO Enable JS IR Compiler
|
|
||||||
* waiting for Decompose & MVI Kotlin to support same
|
|
||||||
* */
|
|
||||||
browser()
|
browser()
|
||||||
// nodejs()
|
// nodejs()
|
||||||
binaries.executable()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -75,10 +63,10 @@ kotlin {
|
|||||||
|
|
||||||
// Extras
|
// Extras
|
||||||
implementation(Extras.kermit)
|
implementation(Extras.kermit)
|
||||||
|
implementation(Serialization.json)
|
||||||
implementation("co.touchlab:stately-common:1.1.7")
|
implementation("co.touchlab:stately-common:1.1.7")
|
||||||
implementation("dev.icerock.moko:parcelize:0.6.1")
|
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.5.0-native-mt") {
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt") {
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
isForce = true
|
isForce = true
|
||||||
}
|
}
|
||||||
@ -87,7 +75,7 @@ kotlin {
|
|||||||
|
|
||||||
named("androidMain") {
|
named("androidMain") {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.appcompat:appcompat:1.2.0")
|
implementation("androidx.appcompat:appcompat:1.3.0")
|
||||||
implementation(Androidx.core)
|
implementation(Androidx.core)
|
||||||
implementation(compose.runtime)
|
implementation(compose.runtime)
|
||||||
implementation(compose.material)
|
implementation(compose.material)
|
||||||
@ -127,8 +115,4 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=e2774e6fb77c43657decde25542dea710aafd78c4022d19b196e7e78d79d8c6c
|
distributionSha256Sum=7faa7198769f872826c8ef4f1450f839ec27f0b4d5d1e51bade63667cbccd205
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -18,14 +18,13 @@ application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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(Ktor.slf4j)
|
||||||
implementation("io.ktor:ktor-client-serialization:1.5.4")
|
implementation(Ktor.clientCore)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1")
|
implementation(Ktor.clientJson)
|
||||||
|
implementation(Ktor.clientApache)
|
||||||
|
implementation(Ktor.clientLogging)
|
||||||
|
implementation(Ktor.clientSerialization)
|
||||||
|
implementation(Serialization.json)
|
||||||
// testDeps
|
// testDeps
|
||||||
testImplementation(kotlin("test-junit"))
|
testImplementation(kotlin("test-junit"))
|
||||||
}
|
}
|
||||||
@ -33,10 +32,3 @@ dependencies {
|
|||||||
tasks.test {
|
tasks.test {
|
||||||
useJUnit()
|
useJUnit()
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
useIR = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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<HttpResponse>(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
|
|
||||||
}
|
|
@ -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`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String>(
|
|
||||||
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<HttpStatement>(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<String>("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("/")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
@file:Suppress("FunctionName")
|
@file:Suppress("FunctionName")
|
||||||
|
|
||||||
package analytics_html_img
|
package common
|
||||||
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.HttpTimeout
|
import io.ktor.client.features.HttpTimeout
|
||||||
@ -18,6 +18,7 @@ internal object Common {
|
|||||||
fun END_SECTION(tagName: String = "HTI") = "<!--END_SECTION:$tagName-->"
|
fun END_SECTION(tagName: String = "HTI") = "<!--END_SECTION:$tagName-->"
|
||||||
const val USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0"
|
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 {
|
internal val client = HttpClient {
|
||||||
install(HttpTimeout)
|
install(HttpTimeout)
|
||||||
install(JsonFeature) {
|
install(JsonFeature) {
|
||||||
@ -33,7 +34,3 @@ internal val client = HttpClient {
|
|||||||
level = LogLevel.INFO
|
level = LogLevel.INFO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal data class GithubFileContent(
|
|
||||||
val decryptedContent: String,
|
|
||||||
val sha: String
|
|
||||||
)
|
|
30
maintenance-tasks/src/main/java/common/ContentUpdation.kt
Normal file
30
maintenance-tasks/src/main/java/common/ContentUpdation.kt
Normal file
@ -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()
|
||||||
|
}
|
16
maintenance-tasks/src/main/java/common/Date.kt
Normal file
16
maintenance-tasks/src/main/java/common/Date.kt
Normal file
@ -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"
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package analytics_html_img
|
package common
|
||||||
|
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.request.header
|
import io.ktor.client.request.header
|
||||||
@ -8,15 +8,38 @@ import io.ktor.http.ContentType
|
|||||||
import io.ktor.http.contentType
|
import io.ktor.http.contentType
|
||||||
import io.ktor.util.InternalAPI
|
import io.ktor.util.InternalAPI
|
||||||
import io.ktor.util.encodeBase64
|
import io.ktor.util.encodeBase64
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
|
import models.github.GithubFileContent
|
||||||
|
import models.github.GithubReleasesInfo
|
||||||
|
|
||||||
internal object GithubService {
|
internal object GithubService {
|
||||||
|
|
||||||
private const val baseURL = Common.GITHUB_API
|
private const val baseURL = Common.GITHUB_API
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun getGithubRepoReleasesInfo(
|
||||||
|
ownerName: String,
|
||||||
|
repoName: String,
|
||||||
|
): GithubReleasesInfo {
|
||||||
|
return client.get<GithubReleasesInfo>("$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(
|
suspend fun getGithubFileContent(
|
||||||
token: String,
|
token: String,
|
||||||
ownerName: String,
|
ownerName: String,
|
@ -1,4 +1,4 @@
|
|||||||
package analytics_html_img
|
package common
|
||||||
|
|
||||||
import io.ktor.client.request.header
|
import io.ktor.client.request.header
|
||||||
import io.ktor.client.request.headers
|
import io.ktor.client.request.headers
|
@ -1,4 +1,4 @@
|
|||||||
package analytics_html_img
|
package common
|
||||||
|
|
||||||
import utils.byOptionalProperty
|
import utils.byOptionalProperty
|
||||||
import utils.byProperty
|
import utils.byProperty
|
@ -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>,
|
|
||||||
): 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<SaavnSearchResult> {
|
|
||||||
/*if (query.startsWith("http") && query.contains("saavn.com")) {
|
|
||||||
return listOf(getSong(query))
|
|
||||||
}*/
|
|
||||||
|
|
||||||
val searchURL = search_base_url + query
|
|
||||||
val results = mutableListOf<SaavnSearchResult>()
|
|
||||||
(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<String>(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<String>(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<String>(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<SaavnSearchResult>,
|
|
||||||
trackName: String,
|
|
||||||
trackArtists: List<String>,
|
|
||||||
): Map<String, Float> {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* "linksWithMatchValue" is map with Saavn VideoID and its rating/match with 100 as Max Value
|
|
||||||
**/
|
|
||||||
val linksWithMatchValue = mutableMapOf<String, Float>()
|
|
||||||
|
|
||||||
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<String>().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="
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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,
|
|
||||||
)
|
|
@ -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<SaavnSong>,
|
|
||||||
val title: String,
|
|
||||||
val year: String
|
|
||||||
)
|
|
@ -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<String>? = 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<SaavnSong>,
|
|
||||||
val sub_types: List<String>? = null,
|
|
||||||
val type: String = "", // chart,etc
|
|
||||||
val uid: String? = 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,
|
|
||||||
)
|
|
@ -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<String, String>,
|
|
||||||
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
|
|
||||||
)
|
|
@ -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
|
import utils.debug
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
debug("fun main: args -> ${args.joinToString(";")}")
|
debug("fun main: args -> ${args.joinToString(";")}")
|
||||||
|
val secrets = Secrets.initSecrets()
|
||||||
|
|
||||||
|
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
|
// TASK -> Update Analytics Image in Readme
|
||||||
updateAnalyticsImage()
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
maintenance-tasks/src/main/java/models/github/Asset.kt
Normal file
17
maintenance-tasks/src/main/java/models/github/Asset.kt
Normal file
@ -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
|
||||||
|
)
|
22
maintenance-tasks/src/main/java/models/github/Author.kt
Normal file
22
maintenance-tasks/src/main/java/models/github/Author.kt
Normal file
@ -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
|
||||||
|
)
|
@ -0,0 +1,6 @@
|
|||||||
|
package models.github
|
||||||
|
|
||||||
|
data class GithubFileContent(
|
||||||
|
val decryptedContent: String,
|
||||||
|
val sha: String
|
||||||
|
)
|
@ -0,0 +1,23 @@
|
|||||||
|
package models.github
|
||||||
|
|
||||||
|
data class GithubReleaseInfoItem(
|
||||||
|
val assets: List<Asset>,
|
||||||
|
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
|
||||||
|
)
|
@ -0,0 +1,3 @@
|
|||||||
|
package models.github
|
||||||
|
|
||||||
|
class GithubReleasesInfo : ArrayList<GithubReleaseInfoItem>()
|
16
maintenance-tasks/src/main/java/models/github/Reactions.kt
Normal file
16
maintenance-tasks/src/main/java/models/github/Reactions.kt
Normal file
@ -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
|
||||||
|
)
|
22
maintenance-tasks/src/main/java/models/github/Uploader.kt
Normal file
22
maintenance-tasks/src/main/java/models/github/Uploader.kt
Normal file
@ -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
|
||||||
|
)
|
@ -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<HttpResponse>(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
|
||||||
|
}
|
213
maintenance-tasks/src/main/java/scripts/UpdateDownloadCards.kt
Normal file
213
maintenance-tasks/src/main/java/scripts/UpdateDownloadCards.kt
Normal file
@ -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,
|
||||||
|
"""
|
||||||
|
<a href="https://github.com/Shabinder/SpotiFlyer/releases/latest">
|
||||||
|
<img src="${getDownloadCard(totalDownloads)}"
|
||||||
|
height="125" width="280" alt="Total Downloads">
|
||||||
|
</a>
|
||||||
|
""".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<HttpResponse>(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 """
|
||||||
|
<div class="card-container">
|
||||||
|
<div id="card" class="dark-bg">
|
||||||
|
<img id="profile-photo" src="https://www.lupusresearch.org/wp-content/uploads/2017/09/resource-downloads-icon.png">
|
||||||
|
<div class="text-wrapper">
|
||||||
|
<p id="title">Total Downloads</p>
|
||||||
|
<p id="source">Github & F-Droid</p>
|
||||||
|
<div class="contact-wrapper">
|
||||||
|
<a id="count" href="#">
|
||||||
|
$count
|
||||||
|
</a>
|
||||||
|
<a id="date" href="#">
|
||||||
|
Updated on: $date
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
""".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()
|
@ -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()
|
|
||||||
}
|
|
@ -3,5 +3,4 @@ package utils
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
// Test Class- at development Time
|
// Test Class- at development Time
|
||||||
fun main(): Unit = runBlocking {
|
fun main(): Unit = runBlocking {}
|
||||||
}
|
|
||||||
|
@ -77,6 +77,5 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binaries.executable()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user