Initial commit

This commit is contained in:
almightyhak 2024-06-20 11:54:12 +07:00
commit 98ed7e8839
2263 changed files with 108711 additions and 0 deletions

View file

@ -0,0 +1,7 @@
plugins {
id("lib-android")
}
dependencies {
implementation(project(":lib:playlist-utils"))
}

View file

@ -0,0 +1,61 @@
package eu.kanade.tachiyomi.lib.dailymotionextractor
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonTransformingSerializer
@Serializable
data class DailyQuality(
val qualities: Auto? = null,
val subtitles: Subtitle? = null,
val error: Error? = null,
val id: String? = null,
) {
@Serializable
data class Error(val type: String)
}
@Serializable
data class Auto(val auto: List<Item>) {
@Serializable
data class Item(val type: String, val url: String)
}
@Serializable
data class Subtitle(
@Serializable(with = SubtitleListSerializer::class)
val data: List<SubtitleDto>,
)
@Serializable
data class SubtitleDto(val label: String, val urls: List<String>)
object SubtitleListSerializer :
JsonTransformingSerializer<List<SubtitleDto>>(ListSerializer(SubtitleDto.serializer())) {
override fun transformDeserialize(element: JsonElement): JsonElement =
when (element) {
is JsonObject -> JsonArray(element.values.toList())
else -> JsonArray(emptyList())
}
}
@Serializable
data class TokenResponse(
val access_token: String,
val token_type: String,
)
@Serializable
data class ProtectedResponse(val data: DataObject) {
@Serializable
data class DataObject(val video: VideoObject) {
@Serializable
data class VideoObject(
val id: String,
val xid: String,
)
}
}

View file

@ -0,0 +1,135 @@
package eu.kanade.tachiyomi.lib.dailymotionextractor
import eu.kanade.tachiyomi.animesource.model.Track
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.parseAs
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import uy.kohesive.injekt.injectLazy
class DailymotionExtractor(private val client: OkHttpClient, private val headers: Headers) {
companion object {
private const val DAILYMOTION_URL = "https://www.dailymotion.com"
private const val GRAPHQL_URL = "https://graphql.api.dailymotion.com"
}
private fun headersBuilder(block: Headers.Builder.() -> Unit = {}) = headers.newBuilder()
.add("Accept", "*/*")
.set("Referer", "$DAILYMOTION_URL/")
.set("Origin", DAILYMOTION_URL)
.apply { block() }
.build()
private val json: Json by injectLazy()
private val playlistUtils by lazy { PlaylistUtils(client, headers) }
fun videosFromUrl(url: String, prefix: String = "Dailymotion - ", baseUrl: String = "", password: String? = null): List<Video> {
val htmlString = client.newCall(GET(url)).execute().body.string()
val internalData = htmlString.substringAfter("\"dmInternalData\":").substringBefore("</script>")
val ts = internalData.substringAfter("\"ts\":").substringBefore(",")
val v1st = internalData.substringAfter("\"v1st\":\"").substringBefore("\",")
val videoQuery = url.toHttpUrl().run {
queryParameter("video") ?: pathSegments.last()
}
val jsonUrl = "$DAILYMOTION_URL/player/metadata/video/$videoQuery?locale=en-US&dmV1st=$v1st&dmTs=$ts&is_native_app=0"
val parsed = client.newCall(GET(jsonUrl)).execute().parseAs<DailyQuality>()
return when {
parsed.qualities != null && parsed.error == null -> videosFromDailyResponse(parsed, prefix)
parsed.error?.type == "password_protected" && parsed.id != null -> {
videosFromProtectedUrl(url, prefix, parsed.id, htmlString, ts, v1st, baseUrl, password)
}
else -> emptyList()
}
}
private fun videosFromProtectedUrl(
url: String,
prefix: String,
videoId: String,
htmlString: String,
ts: String,
v1st: String,
baseUrl: String,
password: String?,
): List<Video> {
val postUrl = "$GRAPHQL_URL/oauth/token"
val clientId = htmlString.substringAfter("client_id\":\"").substringBefore('"')
val clientSecret = htmlString.substringAfter("client_secret\":\"").substringBefore('"')
val scope = htmlString.substringAfter("client_scope\":\"").substringBefore('"')
val tokenBody = FormBody.Builder()
.add("client_id", clientId)
.add("client_secret", clientSecret)
.add("traffic_segment", ts)
.add("visitor_id", v1st)
.add("grant_type", "client_credentials")
.add("scope", scope)
.build()
val tokenResponse = client.newCall(POST(postUrl, headersBuilder(), tokenBody)).execute()
val tokenParsed = tokenResponse.parseAs<TokenResponse>()
val idUrl = "$GRAPHQL_URL/"
val idHeaders = headersBuilder {
set("Accept", "application/json, text/plain, */*")
add("Authorization", "${tokenParsed.token_type} ${tokenParsed.access_token}")
}
val idData = """
{
"query":"query playerPasswordQuery(${'$'}videoId:String!,${'$'}password:String!){video(xid:${'$'}videoId,password:${'$'}password){id xid}}",
"variables":{
"videoId":"$videoId",
"password":"$password"
}
}
""".trimIndent().toRequestBody("application/json".toMediaType())
val idResponse = client.newCall(POST(idUrl, idHeaders, idData)).execute()
val idParsed = idResponse.parseAs<ProtectedResponse>().data.video
val dmvk = htmlString.substringAfter("\"dmvk\":\"").substringBefore('"')
val getVideoIdUrl = "$DAILYMOTION_URL/player/metadata/video/${idParsed.xid}?embedder=${"$baseUrl/"}&locale=en-US&dmV1st=$v1st&dmTs=$ts&is_native_app=0"
val getVideoIdHeaders = headersBuilder {
add("Cookie", "dmvk=$dmvk; ts=$ts; v1st=$v1st; usprivacy=1---; client_token=${tokenParsed.access_token}")
set("Referer", url)
}
val parsed = client.newCall(GET(getVideoIdUrl, getVideoIdHeaders)).execute()
.parseAs<DailyQuality>()
return videosFromDailyResponse(parsed, prefix, getVideoIdHeaders)
}
private fun videosFromDailyResponse(parsed: DailyQuality, prefix: String, playlistHeaders: Headers? = null): List<Video> {
val masterUrl = parsed.qualities?.auto?.firstOrNull()?.url
?: return emptyList<Video>()
val subtitleList = parsed.subtitles?.data?.map {
Track(it.urls.first(), it.label)
} ?: emptyList<Track>()
val masterHeaders = playlistHeaders ?: headersBuilder()
return playlistUtils.extractFromHls(
masterUrl,
masterHeadersGen = { _, _ -> masterHeaders },
subtitleList = subtitleList,
videoNameGen = { "$prefix$it" },
)
}
}