diff --git a/src/en/fmovies/build.gradle b/src/en/fmovies/build.gradle index d76aba6320..44541ceb81 100644 --- a/src/en/fmovies/build.gradle +++ b/src/en/fmovies/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'FMovies' extClass = '.FMovies' - extVersionCode = 17 + extVersionCode = 18 } apply from: "$rootDir/common.gradle" diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt index 70f91970f4..6d82ae5287 100644 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt +++ b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FMovies.kt @@ -49,7 +49,7 @@ class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - private val utils by lazy { FmoviesUtils() } + private val utils by lazy { FmoviesUtils(client, headers) } // ============================== Popular =============================== @@ -107,15 +107,36 @@ class FMovies : ConfigurableAnimeSource, ParsedAnimeHttpSource() { val desc = descElement?.selectFirst("div[data-name=full]")?.ownText() ?: descElement?.ownText() ?: "" val extraInfo = detail?.select("> div")?.joinToString("\n") { it.text() } ?: "" + val mediaTitle = info.selectFirst("h1.name")!!.text() + val mediaDetail = utils.getDetail(mediaTitle) + return SAnime.create().apply { - title = info.selectFirst("h1.name")!!.text() + title = mediaTitle + status = when (mediaDetail?.status) { + "Ended", "Released" -> SAnime.COMPLETED + "In Production" -> SAnime.LICENSED + "Canceled" -> SAnime.CANCELLED + "Returning Series" -> { + mediaDetail.nextEpisode?.let { SAnime.ONGOING } ?: SAnime.ON_HIATUS + } + else -> SAnime.UNKNOWN + } thumbnail_url = document.selectFirst("section#w-info > div.poster img")!!.attr("src") - description = if (desc.isBlank()) extraInfo else "$desc\n\n$extraInfo" - genre = detail?.let { - it.select("> div:has(> div:contains(Genre:)) span").joinToString(", ") { it.text() } + description = buildString { + appendLine(desc.ifBlank { mediaDetail?.overview }) + appendLine() + mediaDetail?.nextEpisode?.let { + appendLine("Next: Ep ${it.epNumber} - ${it.name}") + appendLine("Air Date: ${it.airDate}") + appendLine() + } + appendLine(extraInfo) + } + genre = detail?.let { dtl -> + dtl.select("> div:has(> div:contains(Genre:)) span").joinToString { it.text() } } - author = detail?.let { - it.select("> div:has(> div:contains(Production:)) span").joinToString(", ") { it.text() } + author = detail?.let { dtl -> + dtl.select("> div:has(> div:contains(Production:)) span").joinToString { it.text() } } } } diff --git a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt index 97d5976040..2c04fa2b28 100644 --- a/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt +++ b/src/en/fmovies/src/eu/kanade/tachiyomi/animeextension/en/fmovies/FmoviesUtils.kt @@ -1,12 +1,66 @@ package eu.kanade.tachiyomi.animeextension.en.fmovies import android.util.Base64 +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parseAs +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient import java.net.URLDecoder import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec -class FmoviesUtils { +class FmoviesUtils(private val client: OkHttpClient, private val headers: Headers) { + // ===================== Media Detail ================================ + + private val tmdbURL = "https://api.themoviedb.org/3".toHttpUrl() + + private val seez = "https://seez.su" + + private val apiKey by lazy { + val jsUrl = client.newCall(GET(seez, headers)).execute().asJsoup() + .select("script[defer][src]")[1].attr("abs:src") + + val jsBody = client.newCall(GET(jsUrl, headers)).execute().use { it.body.string() } + Regex("""f="(\w{20,})"""").find(jsBody)!!.groupValues[1] + } + + private val apiHeaders = headers.newBuilder().apply { + add("Accept", "application/json, text/javascript, */*; q=0.01") + add("Host", "api.themoviedb.org") + add("Origin", seez) + add("Referer", "$seez/") + }.build() + + fun getDetail(mediaTitle: String): TmdbDetailsResponse? = + runCatching { + val searchUrl = tmdbURL.newBuilder().apply { + addPathSegment("search") + addPathSegment("multi") + addQueryParameter("query", mediaTitle) + addQueryParameter("api_key", apiKey) + }.build().toString() + val searchResp = client.newCall(GET(searchUrl, headers = apiHeaders)) + .execute() + .parseAs() + + val media = searchResp.results.first() + + val detailUrl = tmdbURL.newBuilder().apply { + addPathSegment(media.mediaType) + addPathSegment(media.id.toString()) + addQueryParameter("api_key", apiKey) + }.build().toString() + client.newCall(GET(detailUrl, headers = apiHeaders)) + .execute() + .parseAs() + }.getOrNull() + + // ===================== Encryption ================================ fun vrfEncrypt(input: String): String { val rc4Key = SecretKeySpec("FWsfu0KQd9vxYGNB".toByteArray(), "RC4") val cipher = Cipher.getInstance("RC4") @@ -53,3 +107,32 @@ class FmoviesUtils { return vrf } } + +@Serializable +data class TmdbResponse( + val results: List, +) { + @Serializable + data class TmdbResult( + val id: Int, + @SerialName("media_type") + val mediaType: String = "tv", + ) +} + +@Serializable +data class TmdbDetailsResponse( + val status: String, + val overview: String? = null, + @SerialName("next_episode_to_air") + val nextEpisode: NextEpisode? = null, +) { + @Serializable + data class NextEpisode( + val name: String? = "", + @SerialName("episode_number") + val epNumber: Int, + @SerialName("air_date") + val airDate: String, + ) +}