Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix(pt/Anitube): Update source #104

Merged
merged 5 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/pt/anitube/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ext {
extName = 'Anitube'
extClass = '.Anitube'
extVersionCode = 17
extVersionCode = 18
}

apply from: "$rootDir/common.gradle"
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package eu.kanade.tachiyomi.animeextension.pt.anitube

import android.app.Application
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.pt.anitube.extractors.AnitubeExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale

Expand All @@ -38,8 +43,10 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}

private val json: Json by injectLazy()

override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
.add("Referer", baseUrl)
.add("Accept-Language", ACCEPT_LANGUAGE)

// ============================== Popular ===============================
Expand Down Expand Up @@ -79,30 +86,6 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()

// =============================== Search ===============================
override suspend fun getSearchAnime(
page: Int,
query: String,
filters: AnimeFilterList,
): AnimesPage {
return if (query.startsWith(PREFIX_SEARCH)) {
val path = query.removePrefix(PREFIX_SEARCH)
client.newCall(GET("$baseUrl/$path"))
.awaitSuccess()
.use(::searchAnimeByIdParse)
} else {
super.getSearchAnime(page, query, filters)
}
}

private fun searchAnimeByIdParse(response: Response): AnimesPage {
val details = animeDetailsParse(response).apply {
setUrlWithoutDomain(response.request.url.toString())
initialized = true
}

return AnimesPage(listOf(details), false)
}

override fun getFilterList(): AnimeFilterList = AnitubeFilters.FILTER_LIST

override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
Expand All @@ -114,14 +97,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val char = params.initialChar
when {
season.isNotBlank() -> "$baseUrl/temporada/$season/$year"
genre.isNotBlank() ->
"$baseUrl/genero/$genre/page/$page/${
char.replace(
"todos",
"",
)
}"

genre.isNotBlank() -> "$baseUrl/genero/$genre/page/$page/${char.replace("todos", "")}"
else -> "$baseUrl/anime/page/$page/letra/$char"
}
} else {
Expand All @@ -143,7 +119,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val infos = content.selectFirst("div.anime_infos")!!

title = doc.selectFirst("div.anime_container_titulo")!!.text()
thumbnail_url = content.selectFirst("img")?.attr("src")
thumbnail_url = content.selectFirst("img")?.imgAttr()
genre = infos.getInfo("Gêneros")
author = infos.getInfo("Autor")
artist = infos.getInfo("Estúdio")
Expand All @@ -159,6 +135,14 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
}

fun Element.imgAttr(): String = when {
hasAttr("data-cfsrc") -> absUrl("data-cfsrc")
hasAttr("data-lazy-src") -> absUrl("data-lazy-src")
hasAttr("data-src") -> absUrl("data-src").substringBefore(" ")
hasAttr("srcset") -> absUrl("srcset").substringBefore(" ")
else -> absUrl("src")
}

// ============================== Episodes ==============================
override fun episodeListSelector() = "div.animepag_episodios_item > a"

Expand All @@ -179,19 +163,81 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun episodeFromElement(element: Element) = SEpisode.create().apply {
setUrlWithoutDomain(element.attr("href"))
episode_number = element.selectFirst("div.animepag_episodios_item_views")!!
.text()
.text().trim()
.substringAfter(" ")
.toFloatOrNull() ?: 0F
name = element.selectFirst("div.animepag_episodios_item_nome")!!.text()
name = element.selectFirst("div[class*='animepag_episodios_item_views']")!!.text()
date_upload = element.selectFirst("div.animepag_episodios_item_date")!!
.text()
.toDate()
}

// ============================ Video Links =============================
private val extractor by lazy { AnitubeExtractor(headers, client, preferences) }
override fun videoListParse(response: Response) = AnitubeExtractor.getVideoList(response, headers).let {
val auth = getToken()

it.map { video ->
Video(
url = video.url,
quality = video.quality,
videoUrl = "${video.videoUrl}${auth.value}",
headers = video.headers,
subtitleTracks = video.subtitleTracks,
audioTracks = video.audioTracks,
)
}
}

private fun getToken(): AnitubeToken {
val headers = Headers.Builder()
.set("Accept", "*/*")
.set("Accept-Encoding", "br, zstd")
.set("Accept-Language", "pt-BR,en-US;q=0.7,en;q=0.3")
.set("Cache-Control", "no-cache")
.set("Connection", "keep-alive")
.set("Pragma", "no-cache")
.set("Referer", "$baseUrl/")
.set("Sec-Fetch-Dest", "empty")
.set("Sec-Fetch-Mode", "cors")
.set("Sec-Fetch-Site", "same-site")
.apply {
headers["User-Agent"]?.let {
set("User-Agent", it)
}
}
.build()

val client = OkHttpClient()

val ads = client.newCall(GET("https://widgets.outbrain.com/outbrain.js", headers))
.execute()

val form = FormBody.Builder()
.add("category", "client")
.add("type", "premium")
.add("ad", ads.body.string())
.build()

val response = client
.newCall(POST(url = "https://ads.anitube.vip", headers = headers, body = form))
.execute()

val token = response.parseAs<List<AnitubeToken>>().first()

val tokenUrl = "https://ads.anitube.vip".toHttpUrl().newBuilder()
.addQueryParameter("token", token.value)
.build()

return client.newCall(GET(tokenUrl, headers))
.execute()
.parseAs<List<AnitubeToken>>()
.first()
}

private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromStream(it.body.byteStream())
}

override fun videoListParse(response: Response) = extractor.getVideoList(response)
override fun videoListSelector() = throw UnsupportedOperationException()
override fun videoFromElement(element: Element) = throw UnsupportedOperationException()
override fun videoUrlParse(document: Document) = throw UnsupportedOperationException()
Expand All @@ -211,22 +257,7 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)

// Auth Code
EditTextPreference(screen.context).apply {
key = PREF_AUTHCODE_KEY
title = "Auth Code"
setDefaultValue(PREF_AUTHCODE_DEFAULT)
summary = PREF_AUTHCODE_SUMMARY

setOnPreferenceChangeListener { _, newValue ->
runCatching {
val value = (newValue as String).trim().ifBlank { PREF_AUTHCODE_DEFAULT }
preferences.edit().putString(key, value).commit()
}.getOrDefault(false)
}
}.also(screen::addPreference)
}.let(screen::addPreference)
}

// ============================= Utilities ==============================
Expand Down Expand Up @@ -275,14 +306,10 @@ class Anitube : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}

companion object {
const val PREFIX_SEARCH = "id:"
private val DATE_FORMATTER by lazy { SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH) }

private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"

private const val PREF_AUTHCODE_KEY = "authcode"
private const val PREF_AUTHCODE_SUMMARY = "Código de Autenticação"
private const val PREF_AUTHCODE_DEFAULT = ""
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_TITLE = "Qualidade preferida"
private const val PREF_QUALITY_DEFAULT = "HD"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.animeextension.pt.anitube

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
class AnitubeToken(
@SerialName("publicidade")
val value: String,
)

This file was deleted.

Loading