diff --git a/common-dependencies.gradle b/common-dependencies.gradle index f66431fd2..59cf86eca 100644 --- a/common-dependencies.gradle +++ b/common-dependencies.gradle @@ -1,7 +1,7 @@ // used both in common.gradle and themesources library dependencies { // Lib 1.3, but using specific commit so we don't need to bump up the version - compileOnly "com.github.jmir1:extensions-lib:92b69d1" + compileOnly "com.github.jmir1:extensions-lib:6467320" // These are provided by the app itself compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" diff --git a/src/en/twodgirlstech/AndroidManifest.xml b/src/en/gogoanime/AndroidManifest.xml similarity index 100% rename from src/en/twodgirlstech/AndroidManifest.xml rename to src/en/gogoanime/AndroidManifest.xml diff --git a/src/en/twodgirlstech/build.gradle b/src/en/gogoanime/build.gradle similarity index 74% rename from src/en/twodgirlstech/build.gradle rename to src/en/gogoanime/build.gradle index b34c35f58..ed9a0afe0 100644 --- a/src/en/twodgirlstech/build.gradle +++ b/src/en/gogoanime/build.gradle @@ -2,10 +2,10 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' ext { - extName = '2dgirls.tech' - pkgNameSuffix = 'en.twodgirlstech' - extClass = '.TwoDGirlsTech' - extVersionCode = 12 + extName = 'Gogoanime' + pkgNameSuffix = 'en.gogoanime' + extClass = '.GogoAnime' + extVersionCode = 1 libVersion = '11' } dependencies { diff --git a/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt b/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt new file mode 100644 index 000000000..c626c201f --- /dev/null +++ b/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt @@ -0,0 +1,144 @@ +package eu.kanade.tachiyomi.animeextension.en.gogoanime + +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.Link +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.await +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.coroutines.runBlocking +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable + +class GogoAnime : ParsedAnimeHttpSource() { + + override val name = "Gogoanime" + + override val baseUrl = "https://www1.gogoanime.ai" + + override val lang = "en" + + override val supportsLatest = true + + override fun popularAnimeSelector(): String = "div.img a" + + override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/popular.html?page=$page") + + override fun popularAnimeFromElement(element: Element): SAnime { + val anime = SAnime.create() + anime.setUrlWithoutDomain(element.attr("href")) + anime.thumbnail_url = element.select("img").first().attr("src") + anime.title = element.attr("title") + return anime + } + + override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)" + + override fun episodeListSelector() = "ul#episode_page li a" + + override fun episodeListParse(response: Response): List { + val document = response.asJsoup() + val totalEpisodes = document.select(episodeListSelector()).last().attr("ep_end") + val id = document.select("input#movie_id").attr("value") + return runBlocking { episodesRequest(totalEpisodes, id) } + } + + private suspend fun episodesRequest(totalEpisodes: String, id: String): List { + val request = GET("https://ajax.gogo-load.com/ajax/load-list-episode?ep_start=0&ep_end=$totalEpisodes&id=$id") + val epResponse = client.newCall(request) + .await() + val document = epResponse.asJsoup() + return document.select("a").map { episodeFromElement(it) } + } + + override fun fetchEpisodeLink(episode: SEpisode): Observable> { + return client.newCall(GET(baseUrl + episode.url)) + .asObservableSuccess() + .map { response -> + runBlocking { linkRequest(response) } + } + } + + private suspend fun linkRequest(response: Response): List { + val elements = response.asJsoup() + val link = elements.select("li.dowloads a").attr("href") + val dlResponse = client.newCall(GET(link)) + .await() + val document = dlResponse.asJsoup() + return linksFromElement(document.select(episodeLinkSelector()).first()) + } + + override fun episodeFromElement(element: Element): SEpisode { + val episode = SEpisode.create() + episode.setUrlWithoutDomain(baseUrl + element.attr("href").substringAfter(" ")) + val ep = element.selectFirst("div.name").ownText().substringAfter(" ") + episode.episode_number = ep.toFloat() + episode.name = "Episode $ep" + episode.date_upload = System.currentTimeMillis() + return episode + } + + override fun episodeLinkSelector() = "div.mirror_link:has(a[download])" + + override fun linksFromElement(element: Element): List { + val links = mutableListOf() + val linkElements = element.select("a[download]") + for (e in linkElements) { + val quality = e.text().substringAfter("Download (").replace("P - mp4)", "p") + links.add(Link(e.attr("href"), quality)) + } + return links + } + + override fun searchAnimeFromElement(element: Element): SAnime { + val anime = SAnime.create() + anime.setUrlWithoutDomain(element.attr("href")) + anime.thumbnail_url = element.select("img").first().attr("src") + anime.title = element.attr("title") + return anime + } + + override fun searchAnimeNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)" + + override fun searchAnimeSelector(): String = "div.img a" + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/search.html?keyword=$query&page=$page") + + override fun animeDetailsParse(document: Document): SAnime { + val anime = SAnime.create() + anime.title = document.select("div.anime_info_body_bg h1").text() + anime.genre = document.select("p.type:eq(5) a").joinToString("") { it.text() } + anime.description = document.select("p.type:eq(4)").first().ownText() + anime.status = parseStatus(document.select("p.type:eq(7) a").text()) + return anime + } + + private fun parseStatus(statusString: String): Int { + return when (statusString) { + "Ongoing" -> SAnime.ONGOING + "Completed" -> SAnime.COMPLETED + else -> SAnime.UNKNOWN + } + } + + override fun latestUpdatesNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)" + + override fun latestUpdatesFromElement(element: Element): SAnime { + val anime = SAnime.create() + anime.setUrlWithoutDomain(baseUrl + element.attr("href")) + val style = element.select("div.thumbnail-popular").attr("style") + anime.thumbnail_url = style.substringAfter("background: url('").substringBefore("');") + anime.title = element.attr("title") + return anime + } + + override fun latestUpdatesRequest(page: Int): Request = GET("https://ajax.gogo-load.com/ajax/page-recent-release-ongoing.html?page=$page&type=1") + + override fun latestUpdatesSelector(): String = "div.added_series_body.popular li a:has(div)" +} diff --git a/src/en/twodgirlstech/res/mipmap-hdpi/ic_launcher.png b/src/en/twodgirlstech/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 037243b77..000000000 Binary files a/src/en/twodgirlstech/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/twodgirlstech/res/mipmap-mdpi/ic_launcher.png b/src/en/twodgirlstech/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0936c6906..000000000 Binary files a/src/en/twodgirlstech/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/twodgirlstech/res/mipmap-xhdpi/ic_launcher.png b/src/en/twodgirlstech/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e5d05a165..000000000 Binary files a/src/en/twodgirlstech/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/twodgirlstech/res/mipmap-xxhdpi/ic_launcher.png b/src/en/twodgirlstech/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 156bbf74b..000000000 Binary files a/src/en/twodgirlstech/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/twodgirlstech/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/twodgirlstech/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 83616c48a..000000000 Binary files a/src/en/twodgirlstech/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/twodgirlstech/res/web_hi_res_512.png b/src/en/twodgirlstech/res/web_hi_res_512.png deleted file mode 100644 index 904af5b37..000000000 Binary files a/src/en/twodgirlstech/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/twodgirlstech/src/eu/kanade/tachiyomi/animeextension/en/twodgirlstech/TwoDGirlsTech.kt b/src/en/twodgirlstech/src/eu/kanade/tachiyomi/animeextension/en/twodgirlstech/TwoDGirlsTech.kt deleted file mode 100644 index 67b68cb14..000000000 --- a/src/en/twodgirlstech/src/eu/kanade/tachiyomi/animeextension/en/twodgirlstech/TwoDGirlsTech.kt +++ /dev/null @@ -1,203 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.en.twodgirlstech - -import eu.kanade.tachiyomi.animesource.model.AnimeFilterList -import eu.kanade.tachiyomi.animesource.model.AnimesPage -import eu.kanade.tachiyomi.animesource.model.Link -import eu.kanade.tachiyomi.animesource.model.SAnime -import eu.kanade.tachiyomi.animesource.model.SEpisode -import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource -import eu.kanade.tachiyomi.network.GET -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext -import okhttp3.Call -import okhttp3.Callback -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.io.IOException -import java.net.URLEncoder -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -class TwoDGirlsTech : ParsedAnimeHttpSource() { - - override val name = "2dgirls.tech" - - override val baseUrl = "https://2dgirls.tech" - - override val lang = "en" - - override val supportsLatest = false - - override fun fetchPopularAnime(page: Int): Observable { - return Observable.just(runBlocking { get(page) }) - } - - suspend fun get(page: Int): AnimesPage { - var hasNextPage = true - val request = GET("$baseUrl/api/popular/$page") - val arrayListDetails: ArrayList = ArrayList() - return suspendCoroutine { continuation -> - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - throw e - } - override fun onResponse(call: Call, response: Response) { - val strResponse = response.body!!.string() - // creating json object - val json = JSONObject(strResponse) - // creating json array - val jsonArrayInfo: JSONArray = json.getJSONArray("results") - val size: Int = jsonArrayInfo.length() - for (i in 0 until size) { - val anime = SAnime.create() - val jsonObjectDetail: JSONObject = jsonArrayInfo.getJSONObject(i) - anime.title = jsonObjectDetail.getString("title") - anime.thumbnail_url = jsonObjectDetail.getString("image") - anime.setUrlWithoutDomain("/api/detailshtml/" + jsonObjectDetail.getString("id")) - arrayListDetails.add(anime) - } - hasNextPage = (arrayListDetails.isNotEmpty()) - continuation.resume(AnimesPage(arrayListDetails, hasNextPage)) - } - }) - } - } - - suspend fun setDetails(anime: SAnime): SAnime { - val request = GET("$baseUrl/api/details/${anime.url.split("/api/detailshtml/").last()}") - return suspendCoroutine { continuation -> - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - throw e - } - - override fun onResponse(call: Call, response: Response) { - val strResponse = response.body!!.string() - // creating json object - val json = JSONObject(strResponse) - // creating json array - val jsonArrayInfo: JSONArray = json.getJSONArray("results") - val size: Int = jsonArrayInfo.length() - for (i in 0..size - 1) { - val jsonObjectDetail: JSONObject = jsonArrayInfo.getJSONObject(i) - anime.genre = jsonObjectDetail.getString("genres") - when (jsonObjectDetail.getString("status").replace("\\s".toRegex(), "")) { - "Ongoing" -> anime.status = SAnime.ONGOING - "Completed" -> anime.status = SAnime.COMPLETED - } - anime.description = jsonObjectDetail.getString("summary") - } - continuation.resume(anime) - } - }) - } - } - - override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { - return Observable.just(runBlocking { getSearch(page, query) }) - } - - suspend fun getSearch(page: Int, query: String): AnimesPage { - var hasNextPage: Boolean - val queryEncoded = withContext(Dispatchers.IO) { URLEncoder.encode(query, "utf-8") } - val request = GET("$baseUrl/api/search/$queryEncoded/$page") - val arrayListDetails: ArrayList = ArrayList() - return suspendCoroutine { continuation -> - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - throw e - } - override fun onResponse(call: Call, response: Response) { - val strResponse = response.body!!.string() - // creating json object - val json = JSONObject(strResponse) - // creating json array - val jsonArrayInfo: JSONArray = json.getJSONArray("results") - val size: Int = jsonArrayInfo.length() - for (i in 0..size - 1) { - val anime = SAnime.create() - val jsonObjectDetail: JSONObject = jsonArrayInfo.getJSONObject(i) - anime.title = jsonObjectDetail.getString("title") - anime.thumbnail_url = jsonObjectDetail.getString("image") - anime.setUrlWithoutDomain("/api/detailshtml/" + jsonObjectDetail.getString("id")) - arrayListDetails.add(anime) - } - hasNextPage = json.getBoolean("nextpage") - continuation.resume(AnimesPage(arrayListDetails, hasNextPage)) - } - }) - } - } - - override fun fetchAnimeDetails(anime: SAnime): Observable { - return Observable.just(runBlocking { setDetails(anime) }) - } - - override fun episodeListSelector() = "div[id^=episode-]" - - override fun fetchEpisodeList(anime: SAnime): Observable> { - return super.fetchEpisodeList(anime).flatMap { Observable.just(it.reversed()) } - } - - override fun episodeFromElement(element: Element): SEpisode { - val id = element.id().split(":").last() - val episodeNumber = element.id().split("episode-").last().split(":").first() - val episode = SEpisode.create() - episode.episode_number = episodeNumber.toFloat() - episode.name = "Episode $episodeNumber" - episode.url = "/api/watchinghtml/$id/$episodeNumber" - episode.date_upload = System.currentTimeMillis() - return episode - } - - override fun episodeLinkSelector() = "body" - - override fun linksFromElement(element: Element): List { - val json = JSONObject(element.text()) - val jsonArrayInfo: JSONArray = json.getJSONArray("links") - val size: Int = jsonArrayInfo.length() - val urls = mutableListOf() - for (i in 0..size - 1) { - val jsonObjectDetail: JSONObject = jsonArrayInfo.getJSONObject(i) - urls.add(Link(jsonObjectDetail.getString("src"), jsonObjectDetail.getString("size"))) - } - return urls - } - - override fun popularAnimeSelector(): String = throw Exception("Not used") - - override fun searchAnimeFromElement(element: Element): SAnime = throw Exception("Not used") - - override fun searchAnimeNextPageSelector(): String? = throw Exception("Not used") - - override fun searchAnimeSelector(): String = throw Exception("Not used") - - override fun popularAnimeRequest(page: Int): Request = throw Exception("Not used") - - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = throw Exception("Not used") - - override fun popularAnimeNextPageSelector(): String? = throw Exception("Not used") - - override fun popularAnimeFromElement(element: Element): SAnime = throw Exception("Not used") - - override fun animeDetailsParse(document: Document): SAnime = throw Exception("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") - - override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - override fun latestUpdatesSelector(): String = throw Exception("Not used") - - companion object { - const val baseAltTextUrl = "https://fakeimg.pl/1500x2126/ffffff/000000/?text=" - const val baseAltTextPostUrl = "&font_size=42&font=museo" - } -}