Skip to content

Commit

Permalink
Fix #375
Browse files Browse the repository at this point in the history
  • Loading branch information
z-huang committed Oct 31, 2022
1 parent 722f8cf commit 30de5ef
Show file tree
Hide file tree
Showing 10 changed files with 47 additions and 94 deletions.
3 changes: 0 additions & 3 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault

# Protobuf
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }

-dontwarn javax.servlet.ServletContainerInitializer
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
Expand Down
16 changes: 12 additions & 4 deletions app/src/main/java/com/zionhuang/music/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import com.zionhuang.innertube.YouTube
import com.zionhuang.innertube.models.YouTubeClient
import com.zionhuang.innertube.models.YouTubeLocale
import com.zionhuang.kugou.KuGou
import com.zionhuang.music.extensions.getEnum
import com.zionhuang.music.extensions.sharedPreferences
import com.zionhuang.music.extensions.toInetSocketAddress
import com.zionhuang.music.ui.fragments.settings.StorageSettingsFragment.Companion.VALUE_TO_MB
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.net.Proxy
import java.util.*

class App : Application(), ImageLoaderFactory {
@OptIn(DelicateCoroutinesApi::class)
override fun onCreate() {
super.onCreate()
INSTANCE = this
Expand Down Expand Up @@ -57,9 +60,14 @@ class App : Application(), ImageLoaderFactory {
}
}

YouTube.visitorData = sharedPreferences.getString(getString(R.string.pref_visitor_data), null) ?: YouTubeClient.generateVisitorData().also {
sharedPreferences.edit {
putString(getString(R.string.pref_visitor_data), it)
GlobalScope.launch {
val visitorData = sharedPreferences.getString(getString(R.string.pref_visitor_data), null) ?: YouTube.generateVisitorData().getOrNull()?.also {
sharedPreferences.edit {
putString(getString(R.string.pref_visitor_data), it)
}
}
visitorData?.let {
YouTube.visitorData = it
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
package com.zionhuang.music.viewmodels

import android.app.Application
import androidx.core.content.edit
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.zionhuang.innertube.YouTube
import com.zionhuang.innertube.models.SuggestionTextItem
import com.zionhuang.innertube.models.SuggestionTextItem.SuggestionSource.LOCAL
import com.zionhuang.innertube.models.YTBaseItem
import com.zionhuang.music.R
import com.zionhuang.music.extensions.sharedPreferences
import com.zionhuang.music.repos.SongRepository
import com.zionhuang.music.repos.YouTubeRepository
import kotlinx.coroutines.launch
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.MissingFieldException

class SuggestionViewModel(application: Application) : AndroidViewModel(application) {
private val songRepository = SongRepository(application)
private val youTubeRepository = YouTubeRepository(application)
val suggestions = MutableLiveData<List<YTBaseItem>>(emptyList())

@OptIn(ExperimentalSerializationApi::class)
fun fetchSuggestions(query: String?) = viewModelScope.launch {
if (query.isNullOrEmpty()) {
suggestions.postValue(songRepository.getAllSearchHistory().map { SuggestionTextItem(it.query, LOCAL) })
} else {
try {
val history = songRepository.getSearchHistory(query).map { SuggestionTextItem(it.query, LOCAL) }
val history = songRepository.getSearchHistory(query).map {
SuggestionTextItem(it.query, LOCAL)
}
suggestions.postValue(history + youTubeRepository.getSuggestions(query).filter { item ->
item !is SuggestionTextItem || history.find { it.query == item.query } == null
})
} catch (e: Exception) {
e.printStackTrace()
// Remove in future
if (e is MissingFieldException) {
// Reset visitorData
YouTube.generateVisitorData().getOrNull()?.let {
getApplication<Application>().sharedPreferences.edit {
putString(getApplication<Application>().getString(R.string.pref_visitor_data), it)
}
YouTube.visitorData = it
}
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ buildscript {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlin_version")
classpath("dev.rikka.tools.materialthemebuilder:gradle-plugin:1.3.2")
classpath("com.google.protobuf:protobuf-gradle-plugin:0.8.19")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
Expand Down
39 changes: 0 additions & 39 deletions innertube/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import com.android.build.api.dsl.AndroidSourceSet
import com.google.protobuf.gradle.builtins
import com.google.protobuf.gradle.generateProtoTasks
import com.google.protobuf.gradle.protobuf
import com.google.protobuf.gradle.protoc

plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("kotlinx-serialization")
id("kotlin-parcelize")
id("com.google.protobuf")
}

android {
Expand All @@ -34,29 +27,6 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
sourceSets {
getByName("main") {
proto {
srcDir("src/main/proto")
}
}
}
}


protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.21.7"
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
create("java") {
option("lite")
}
}
}
}
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
Expand All @@ -75,14 +45,5 @@ dependencies {
implementation("ch.qos.logback:logback-classic:$logback_version")
implementation("io.ktor:ktor-client-logging-jvm:$ktor_version")
implementation("org.brotli:dec:0.1.2")
implementation("com.google.protobuf:protobuf-javalite:3.21.7")
testImplementation("junit:junit:4.13.2")
}

fun AndroidSourceSet.proto(action: SourceDirectorySet.() -> Unit) {
(this as? ExtensionAware)
?.extensions
?.getByName("proto")
?.let { it as? SourceDirectorySet }
?.apply(action)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import java.util.*
* For making HTTP requests, not parsing response.
*/
class InnerTube {
private var httpClient = createClient()
var httpClient = createClient()

var locale = YouTubeLocale(
gl = Locale.getDefault().country,
Expand Down
13 changes: 13 additions & 0 deletions innertube/src/main/java/com/zionhuang/innertube/YouTube.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import com.zionhuang.innertube.models.YouTubeClient.Companion.WEB_REMIX
import com.zionhuang.innertube.models.response.*
import com.zionhuang.innertube.utils.insertSeparator
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import java.net.Proxy

/**
Expand Down Expand Up @@ -150,6 +155,14 @@ object YouTube {
.mapNotNull { it.content.playlistPanelVideoRenderer?.toSongItem() }
}

suspend fun generateVisitorData() = runCatching {
Json.parseToJsonElement(innerTube.httpClient.get("https://music.youtube.com/sw.js_data").bodyAsText().substring(5))
.jsonArray[0]
.jsonArray[2]
.jsonArray[6]
.jsonPrimitive.content
}

@JvmInline
value class SearchFilter(val value: String) {
companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.zionhuang.innertube.models

import android.util.Base64
import kotlinx.serialization.Serializable

@Serializable
Expand All @@ -22,27 +21,6 @@ data class YouTubeClient(
)

companion object {
fun generateVisitorData(): String {
val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toList()
var id = ""
for (i in 1..11) {
id += alphabet.random()
}
val timestamp = System.currentTimeMillis() / 1000
return generateVisitorData(id, timestamp.toInt())
}

fun generateVisitorData(id: String, timestamp: Int): String {
val visitorData = VisitorData.visitorData.newBuilder()
.setId(id)
.setTimestamp(timestamp)
.build()
return Base64.encodeToString(visitorData.toByteArray(), Base64.NO_WRAP)
.replace("==", "%3D%3D")
.replace("+", "-")
.replace("/", "_")
}

private const val REFERER_YOUTUBE_MUSIC = "https://music.youtube.com/"

private const val USER_AGENT_WEB = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36"
Expand Down
9 changes: 0 additions & 9 deletions innertube/src/main/proto/visitorData.proto

This file was deleted.

15 changes: 1 addition & 14 deletions innertube/src/test/java/com/zionhuang/innertube/YouTubeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.zionhuang.innertube.YouTube.SearchFilter.Companion.FILTER_VIDEO
import com.zionhuang.innertube.models.BrowseEndpoint
import com.zionhuang.innertube.models.WatchEndpoint
import com.zionhuang.innertube.models.YTItem
import com.zionhuang.innertube.models.YouTubeClient
import com.zionhuang.innertube.utils.browseAll
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
Expand All @@ -20,10 +19,9 @@ import io.ktor.http.*
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test

@Ignore("IDK Why GitHub Action always runs the test with error")
//@Ignore("IDK Why GitHub Action always runs the test with error")
class YouTubeTest {
private val youTube = YouTube

Expand Down Expand Up @@ -174,17 +172,6 @@ class YouTubeTest {
assertTrue(browseResult.lyrics != null)
}

@Test
fun visitorData() {
assertEquals("CgtsZG1ySnZiQWtSbyiMjuGSBg%3D%3D", YouTubeClient.generateVisitorData("ldmrJvbAkRo", 1649952524))
assertEquals("CgtrLTlNQlQ2SWs1OCjZlu-ZBg%3D%3D", YouTubeClient.generateVisitorData("k-9MBT6Ik58", 1664863065))
assertEquals("Cgs1bFZLWXJWVk5MRSisnO-ZBg%3D%3D", YouTubeClient.generateVisitorData("5lVKYrVVNLE", 1664863788))
assertEquals("CgtrLTlNQlQ2SWs1OCjN_fSZBg%3D%3D", YouTubeClient.generateVisitorData("k-9MBT6Ik58", 1664958157))
assertEquals("CgtXSHBibzJXSm1layj-ifWZBg%3D%3D", YouTubeClient.generateVisitorData("WHpbo2WJmek", 1664959742))
assertEquals("CgtlcWs4cjFPYUpyZyj_ifWZBg%3D%3D", YouTubeClient.generateVisitorData("eqk8r1OaJrg", 1664959743))
assertEquals("Cgs2eHNHQ3FTVkJDbyj-i_WZBg%3D%3D", YouTubeClient.generateVisitorData("6xsGCqSVBCo", 1664959998))
}

companion object {
private val VIDEO_IDS = listOf(
"4H-N260cPCg",
Expand Down

0 comments on commit 30de5ef

Please sign in to comment.