2021-08-19 16:25:54 +00:00
package com.lagradost.cloudstream3.animeproviders
2022-02-05 22:21:45 +00:00
import com.fasterxml.jackson.annotation.JsonProperty
2021-08-19 16:25:54 +00:00
import com.lagradost.cloudstream3.*
2022-03-17 15:04:05 +00:00
import com.lagradost.cloudstream3.mvvm.safeApiCall
2022-03-04 12:47:36 +00:00
import com.lagradost.cloudstream3.utils.*
2021-08-19 16:25:54 +00:00
import org.jsoup.Jsoup
2022-03-17 15:04:05 +00:00
import java.net.URI
2021-08-19 16:25:54 +00:00
import java.util.*
2022-01-29 18:37:23 +00:00
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
2021-08-19 16:25:54 +00:00
class GogoanimeProvider : MainAPI ( ) {
companion object {
fun getType ( t : String ) : TvType {
2022-01-31 20:47:59 +00:00
return if ( t . contains ( " OVA " ) || t . contains ( " Special " ) ) TvType . OVA
2021-08-19 16:25:54 +00:00
else if ( t . contains ( " Movie " ) ) TvType . AnimeMovie
else TvType . Anime
}
2021-09-30 15:51:34 +00:00
2021-08-19 16:25:54 +00:00
fun getStatus ( t : String ) : ShowStatus {
return when ( t ) {
" Completed " -> ShowStatus . Completed
" Ongoing " -> ShowStatus . Ongoing
else -> ShowStatus . Completed
}
}
2021-09-30 15:51:34 +00:00
2021-08-19 16:25:54 +00:00
val qualityRegex = Regex ( " ( \\ d+)P " )
2022-01-29 18:37:23 +00:00
// https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt#L60
// No Licence on the function
private fun cryptoHandler (
string : String ,
iv : ByteArray ,
secretKeyString : ByteArray ,
encrypt : Boolean = true
) : String {
val ivParameterSpec = IvParameterSpec ( iv )
val secretKey = SecretKeySpec ( secretKeyString , " AES " )
val cipher = Cipher . getInstance ( " AES/CBC/PKCS5Padding " )
return if ( ! encrypt ) {
cipher . init ( Cipher . DECRYPT _MODE , secretKey , ivParameterSpec )
String ( cipher . doFinal ( base64DecodeArray ( string ) ) )
} else {
cipher . init ( Cipher . ENCRYPT _MODE , secretKey , ivParameterSpec )
base64Encode ( cipher . doFinal ( string . toByteArray ( ) ) )
}
}
2021-08-19 16:25:54 +00:00
2022-03-17 15:04:05 +00:00
private fun String . decodeHex ( ) : ByteArray {
check ( length % 2 == 0 ) { " Must have an even length " }
return chunked ( 2 )
. map { it . toInt ( 16 ) . toByte ( ) }
. toByteArray ( )
}
/ * *
* @param iframeUrl something like https : //gogoplay4.com/streaming.php?id=XXXXXX
* @param mainApiName used for ExtractorLink names and source
* @param iv secret iv from site , required non - null
* @param secretKey secret key for decryption from site , required non - null
* * /
suspend fun extractVidstream (
iframeUrl : String ,
mainApiName : String ,
callback : ( ExtractorLink ) -> Unit ,
iv : ByteArray ? ,
secretKey : ByteArray ?
) = safeApiCall {
// https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt
// No Licence on the following code
// Also modified of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/extractors/GogoCdnExtractor.kt
// License on the code above https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE
if ( iv == null || secretKey == null )
return @safeApiCall
val uri = URI ( iframeUrl )
val mainUrl = " https:// " + uri . host
val id = Regex ( " id=([^&]+) " ) . find ( iframeUrl ) !! . value . removePrefix ( " id= " )
val encryptedId = cryptoHandler ( id , iv , secretKey )
val jsonResponse =
app . get (
" $mainUrl /encrypt-ajax.php?id= $encryptedId " ,
headers = mapOf ( " X-Requested-With " to " XMLHttpRequest " )
)
val dataencrypted =
jsonResponse . text . substringAfter ( " { \" data \" : \" " ) . substringBefore ( " \" } " )
val datadecrypted = cryptoHandler ( dataencrypted , iv , secretKey , false )
val sources = AppUtils . parseJson < GogoSources > ( datadecrypted )
fun invokeGogoSource (
source : GogoSource ,
sourceCallback : ( ExtractorLink ) -> Unit
) {
when {
source . file . contains ( " m3u8 " ) -> {
M3u8Helper ( ) . m3u8Generation (
M3u8Helper . M3u8Stream (
source . file ,
headers = mapOf ( " Referer " to " https://gogoplay4.com " )
) , true
)
. map { stream ->
val qualityString =
if ( ( stream . quality ?: 0 ) == 0 ) " " else " ${stream.quality} p "
sourceCallback (
ExtractorLink (
mainApiName ,
" $mainApiName $qualityString " ,
stream . streamUrl ,
mainUrl ,
getQualityFromName ( stream . quality . toString ( ) ) ,
true
)
)
}
}
source . file . contains ( " vidstreaming " ) -> {
sourceCallback . invoke (
ExtractorLink (
mainApiName ,
" $mainApiName ${source.label?.replace("0 P", "0p") ?: ""} " ,
source . file ,
mainUrl ,
getQualityFromName ( source . label ?: " " ) ,
isM3u8 = source . type == " hls "
)
)
}
else -> {
sourceCallback . invoke (
ExtractorLink (
mainApiName ,
" $mainApiName ${source.label?.replace("0 P", "0p") ?: ""} " ,
source . file ,
mainUrl ,
getQualityFromName ( source . label ?: " " ) ,
isM3u8 = source . type == " hls "
)
)
}
}
}
sources . source ?. forEach {
invokeGogoSource ( it , callback )
}
sources . sourceBk ?. forEach {
invokeGogoSource ( it , callback )
}
}
2022-03-13 08:42:43 +00:00
}
2022-03-16 15:29:11 +00:00
override var mainUrl = " https://gogoanime.film "
override var name = " GogoAnime "
2021-12-08 23:39:30 +00:00
override val hasQuickSearch = false
override val hasMainPage = true
2021-08-19 16:25:54 +00:00
2021-12-08 23:39:30 +00:00
override val supportedTypes = setOf (
TvType . AnimeMovie ,
TvType . Anime ,
2022-01-31 20:47:59 +00:00
TvType . OVA
2021-12-08 23:39:30 +00:00
)
2021-08-19 16:25:54 +00:00
2022-01-16 22:31:42 +00:00
override suspend fun getMainPage ( ) : HomePageResponse {
2021-08-19 16:25:54 +00:00
val headers = mapOf (
" authority " to " ajax.gogo-load.com " ,
" sec-ch-ua " to " \" Google Chrome \" ;v= \" 89 \" , \" Chromium \" ;v= \" 89 \" , \" ;Not A Brand \" ;v= \" 99 \" " ,
" accept " to " text/html, */*; q=0.01 " ,
" dnt " to " 1 " ,
" sec-ch-ua-mobile " to " ?0 " ,
" user-agent " to " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 " ,
2021-08-19 20:05:18 +00:00
" origin " to mainUrl ,
2021-08-19 16:25:54 +00:00
" sec-fetch-site " to " cross-site " ,
" sec-fetch-mode " to " cors " ,
" sec-fetch-dest " to " empty " ,
2021-08-19 20:05:18 +00:00
" referer " to " $mainUrl / "
2021-08-19 16:25:54 +00:00
)
2021-09-30 15:51:34 +00:00
val parseRegex =
Regex ( """ <li>\s*\n.*\n.*<a\s*href=["'](.*?-episode-(\d+))["']\s*title=["'](.*?)["']>\n.*?img src="(.*?) """ " )
2021-08-19 16:25:54 +00:00
val urls = listOf (
Pair ( " 1 " , " Recent Release - Sub " ) ,
Pair ( " 2 " , " Recent Release - Dub " ) ,
Pair ( " 3 " , " Recent Release - Chinese " ) ,
)
val items = ArrayList < HomePageList > ( )
for ( i in urls ) {
try {
val params = mapOf ( " page " to " 1 " , " type " to i . first )
2021-12-05 16:22:30 +00:00
val html = app . get (
2021-09-30 15:51:34 +00:00
" https://ajax.gogo-load.com/ajax/page-recent-release.html " ,
headers = headers ,
params = params
2021-12-05 16:22:30 +00:00
)
items . add ( HomePageList ( i . second , ( parseRegex . findAll ( html . text ) . map {
2021-08-19 16:25:54 +00:00
val ( link , epNum , title , poster ) = it . destructured
2021-10-10 02:26:18 +00:00
val isSub = listOf ( 1 , 3 ) . contains ( i . first . toInt ( ) )
2021-08-19 16:25:54 +00:00
AnimeSearchResponse (
title ,
link ,
this . name ,
TvType . Anime ,
poster ,
null ,
2021-10-10 02:26:18 +00:00
if ( isSub ) EnumSet . of ( DubStatus . Subbed ) else EnumSet . of (
2021-09-30 15:51:34 +00:00
DubStatus . Dubbed
) ,
2021-08-19 16:25:54 +00:00
null ,
2021-10-10 02:26:18 +00:00
if ( !is Sub ) epNum . toIntOrNull ( ) else null ,
if ( isSub ) epNum . toIntOrNull ( ) else null ,
2021-08-19 16:25:54 +00:00
)
} ) . toList ( ) ) )
} catch ( e : Exception ) {
e . printStackTrace ( )
}
}
2021-09-30 15:51:34 +00:00
if ( items . size <= 0 ) throw ErrorLoadingException ( )
2021-08-19 16:25:54 +00:00
return HomePageResponse ( items )
}
2022-01-16 22:31:42 +00:00
override suspend fun search ( query : String ) : ArrayList < SearchResponse > {
2021-08-19 16:25:54 +00:00
val link = " $mainUrl /search.html?keyword= $query "
2021-12-05 16:22:30 +00:00
val html = app . get ( link ) . text
2021-08-19 16:25:54 +00:00
val doc = Jsoup . parse ( html )
val episodes = doc . select ( """ .last_episodes li """ ) . map {
AnimeSearchResponse (
it . selectFirst ( " .name " ) . text ( ) . replace ( " (Dub) " , " " ) ,
fixUrl ( it . selectFirst ( " .name > a " ) . attr ( " href " ) ) ,
this . name ,
TvType . Anime ,
it . selectFirst ( " img " ) . attr ( " src " ) ,
2022-01-07 19:27:25 +00:00
it . selectFirst ( " .released " ) ?. text ( ) ?. split ( " : " ) ?. getOrNull ( 1 ) ?. trim ( )
?. toIntOrNull ( ) ,
if ( it . selectFirst ( " .name " ) . text ( )
. contains ( " Dub " )
) EnumSet . of ( DubStatus . Dubbed ) else EnumSet . of (
2021-09-30 15:51:34 +00:00
DubStatus . Subbed
) ,
2021-08-19 16:25:54 +00:00
)
}
return ArrayList ( episodes )
}
private fun getProperAnimeLink ( uri : String ) : String {
if ( uri . contains ( " -episode " ) ) {
val split = uri . split ( " / " )
2021-09-30 15:51:34 +00:00
val slug = split [ split . size - 1 ] . split ( " -episode " ) [ 0 ]
2021-08-19 16:25:54 +00:00
return " $mainUrl /category/ $slug "
}
return uri
}
2022-01-16 22:31:42 +00:00
override suspend fun load ( url : String ) : LoadResponse {
2021-08-19 16:25:54 +00:00
val link = getProperAnimeLink ( url )
val episodeloadApi = " https://ajax.gogo-load.com/ajax/load-list-episode "
2021-12-05 16:22:30 +00:00
val html = app . get ( link ) . text
2021-08-19 16:25:54 +00:00
val doc = Jsoup . parse ( html )
val animeBody = doc . selectFirst ( " .anime_info_body_bg " )
val title = animeBody . selectFirst ( " h1 " ) . text ( )
val poster = animeBody . selectFirst ( " img " ) . attr ( " src " )
var description : String ? = null
val genre = ArrayList < String > ( )
var year : Int ? = null
var status : String ? = null
var nativeName : String ? = null
var type : String ? = null
2021-10-19 20:17:06 +00:00
animeBody . select ( " p.type " ) . forEach { pType ->
when ( pType . selectFirst ( " span " ) . text ( ) . trim ( ) ) {
2021-08-19 16:25:54 +00:00
" Plot Summary: " -> {
2021-10-19 20:17:06 +00:00
description = pType . text ( ) . replace ( " Plot Summary: " , " " ) . trim ( )
2021-08-19 16:25:54 +00:00
}
" Genre: " -> {
2021-10-19 20:17:06 +00:00
genre . addAll ( pType . select ( " a " ) . map {
2021-08-19 16:25:54 +00:00
it . attr ( " title " )
} )
}
" Released: " -> {
2021-10-19 20:17:06 +00:00
year = pType . text ( ) . replace ( " Released: " , " " ) . trim ( ) . toIntOrNull ( )
2021-08-19 16:25:54 +00:00
}
" Status: " -> {
2021-10-19 20:17:06 +00:00
status = pType . text ( ) . replace ( " Status: " , " " ) . trim ( )
2021-08-19 16:25:54 +00:00
}
" Other name: " -> {
2021-10-19 20:17:06 +00:00
nativeName = pType . text ( ) . replace ( " Other name: " , " " ) . trim ( )
2021-08-19 16:25:54 +00:00
}
" Type: " -> {
2021-10-19 20:17:06 +00:00
type = pType . text ( ) . replace ( " type: " , " " ) . trim ( )
2021-08-19 16:25:54 +00:00
}
}
}
val animeId = doc . selectFirst ( " #movie_id " ) . attr ( " value " )
val params = mapOf ( " ep_start " to " 0 " , " ep_end " to " 2000 " , " id " to animeId )
2022-01-14 18:14:24 +00:00
val episodes = app . get ( episodeloadApi , params = params ) . document . select ( " a " ) . map {
2022-04-09 14:50:04 +00:00
Episode (
2021-08-19 16:25:54 +00:00
fixUrl ( it . attr ( " href " ) . trim ( ) ) ,
" Episode " + it . selectFirst ( " .name " ) . text ( ) . replace ( " EP " , " " ) . trim ( )
)
} . reversed ( )
2021-11-02 15:09:29 +00:00
return newAnimeLoadResponse ( title , link , getType ( type . toString ( ) ) ) {
japName = nativeName
engName = title
posterUrl = poster
this . year = year
addEpisodes ( DubStatus . Subbed , episodes ) // TODO CHECK
plot = description
tags = genre
showStatus = getStatus ( status . toString ( ) )
}
2021-08-19 16:25:54 +00:00
}
2022-01-29 18:37:23 +00:00
data class GogoSources (
2022-02-05 22:21:45 +00:00
@JsonProperty ( " source " ) val source : List < GogoSource > ? ,
@JsonProperty ( " sourceBk " ) val sourceBk : List < GogoSource > ? ,
2022-01-29 18:37:23 +00:00
//val track: List<Any?>,
//val advertising: List<Any?>,
//val linkiframe: String
)
data class GogoSource (
2022-02-05 22:21:45 +00:00
@JsonProperty ( " file " ) val file : String ,
@JsonProperty ( " label " ) val label : String ? ,
@JsonProperty ( " type " ) val type : String ? ,
@JsonProperty ( " default " ) val default : String ? = null
2022-01-29 18:37:23 +00:00
)
2022-01-29 21:25:12 +00:00
private suspend fun extractVideos ( uri : String , callback : ( ExtractorLink ) -> Unit ) {
2022-01-07 19:27:25 +00:00
val doc = app . get ( uri ) . document
2021-08-19 16:25:54 +00:00
2022-01-07 19:27:25 +00:00
val iframe = fixUrlNull ( doc . selectFirst ( " div.play-video > iframe " ) . attr ( " src " ) ) ?: return
2021-08-19 16:25:54 +00:00
2022-01-29 21:25:12 +00:00
argamap (
2022-01-29 18:37:23 +00:00
{
val link = iframe . replace ( " streaming.php " , " download " )
val page = app . get ( link , headers = mapOf ( " Referer " to iframe ) )
2022-01-29 21:25:12 +00:00
page . document . select ( " .dowload > a " ) . apmap {
2022-01-29 18:37:23 +00:00
if ( it . hasAttr ( " download " ) ) {
val qual = if ( it . text ( )
. contains ( " HDP " )
) " 1080 " else qualityRegex . find ( it . text ( ) ) ?. destructured ?. component1 ( )
. toString ( )
callback (
ExtractorLink (
" Gogoanime " ,
if ( qual == " null " ) " Gogoanime " else " Gogoanime - " + qual + " p " ,
it . attr ( " href " ) ,
page . url ,
getQualityFromName ( qual ) ,
it . attr ( " href " ) . contains ( " .m3u8 " )
)
)
} else {
val url = it . attr ( " href " )
loadExtractor ( url , null , callback )
}
}
} , {
val streamingResponse = app . get ( iframe , headers = mapOf ( " Referer " to iframe ) )
val streamingDocument = streamingResponse . document
2022-01-29 21:25:12 +00:00
argamap ( {
2022-01-29 18:37:23 +00:00
streamingDocument . select ( " .list-server-items > .linkserver " )
?. forEach { element ->
val status = element . attr ( " data-status " ) ?: return @forEach
if ( status != " 1 " ) return @forEach
val data = element . attr ( " data-video " ) ?: return @forEach
loadExtractor ( data , streamingResponse . url , callback )
}
} , {
2022-03-29 18:34:34 +00:00
val iv = " 4770478969418267 " . toByteArray ( )
2022-03-17 15:04:05 +00:00
val secretKey =
2022-03-29 18:34:34 +00:00
" 63976882873559819639988080820907 " . toByteArray ( )
2022-03-17 15:04:05 +00:00
extractVidstream ( iframe , this . name , callback , iv , secretKey )
2022-01-29 18:37:23 +00:00
} )
2021-09-29 16:32:44 +00:00
}
2022-01-29 18:37:23 +00:00
)
2021-08-19 16:25:54 +00:00
}
2022-01-16 22:31:42 +00:00
override suspend fun loadLinks (
2021-08-19 16:25:54 +00:00
data : String ,
isCasting : Boolean ,
subtitleCallback : ( SubtitleFile ) -> Unit ,
callback : ( ExtractorLink ) -> Unit
) : Boolean {
2022-01-07 19:27:25 +00:00
extractVideos ( data , callback )
2021-08-19 16:25:54 +00:00
return true
}
}