diff --git a/src/all/newgrounds/build.gradle b/src/all/newgrounds/build.gradle deleted file mode 100644 index b835c961..00000000 --- a/src/all/newgrounds/build.gradle +++ /dev/null @@ -1,8 +0,0 @@ -ext { - extName = 'Newgrounds' - extClass = '.NewGrounds' - extVersionCode = 1 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/newgrounds/res/mipmap-hdpi/ic_launcher.png b/src/all/newgrounds/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 430ff8b3..00000000 Binary files a/src/all/newgrounds/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/newgrounds/res/mipmap-mdpi/ic_launcher.png b/src/all/newgrounds/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ada390ae..00000000 Binary files a/src/all/newgrounds/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/newgrounds/res/mipmap-xhdpi/ic_launcher.png b/src/all/newgrounds/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 90138a7a..00000000 Binary files a/src/all/newgrounds/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/newgrounds/res/mipmap-xxhdpi/ic_launcher.png b/src/all/newgrounds/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ddd9259b..00000000 Binary files a/src/all/newgrounds/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/newgrounds/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/newgrounds/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index de547fdd..00000000 Binary files a/src/all/newgrounds/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/newgrounds/src/eu/kanade/tachiyomi/animeextension/all/newgrounds/DateUtils.kt b/src/all/newgrounds/src/eu/kanade/tachiyomi/animeextension/all/newgrounds/DateUtils.kt deleted file mode 100644 index 1da32290..00000000 --- a/src/all/newgrounds/src/eu/kanade/tachiyomi/animeextension/all/newgrounds/DateUtils.kt +++ /dev/null @@ -1,13 +0,0 @@ -import java.text.ParseException -import java.text.SimpleDateFormat - -@Suppress("NOTHING_TO_INLINE") -inline fun SimpleDateFormat.tryParse(date: String?): Long { - date ?: return 0L - - return try { - parse(date)?.time ?: 0L - } catch (_: ParseException) { - 0L - } -} diff --git a/src/all/newgrounds/src/eu/kanade/tachiyomi/animeextension/all/newgrounds/NewGrounds.kt b/src/all/newgrounds/src/eu/kanade/tachiyomi/animeextension/all/newgrounds/NewGrounds.kt deleted file mode 100644 index 500c0406..00000000 --- a/src/all/newgrounds/src/eu/kanade/tachiyomi/animeextension/all/newgrounds/NewGrounds.kt +++ /dev/null @@ -1,545 +0,0 @@ -package eu.kanade.tachiyomi.animeextension.all.newgrounds - -import android.app.Application -import android.os.Handler -import android.os.Looper -import android.widget.Toast -import androidx.preference.CheckBoxPreference -import androidx.preference.ListPreference -import androidx.preference.MultiSelectListPreference -import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource -import eu.kanade.tachiyomi.animesource.model.AnimeFilter -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.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Request -import okhttp3.Response -import org.json.JSONObject -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import tryParse -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale - -private const val PAGE_SIZE = 20 - -class NewGrounds : ParsedAnimeHttpSource(), ConfigurableAnimeSource { - - override val lang = "all" - override val baseUrl = "https://www.newgrounds.com" - override val name = "Newgrounds" - override val supportsLatest = true - - private val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH) - - private val preferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - private val context = Injekt.get() - private val handler by lazy { Handler(Looper.getMainLooper()) } - - private val videoListHeaders by lazy { - headers.newBuilder() - .add("Accept", "application/json, text/javascript, */*; q=0.01") - .add("X-Requested-With", "XMLHttpRequest") - .add("Referer", baseUrl) - .build() - } - - // Latest - - private fun getLatestSection(): String { - return preferences.getString("LATEST", PREF_SECTIONS["Latest"])!! - } - - override fun latestUpdatesRequest(page: Int): Request { - val offset = (page - 1) * PAGE_SIZE - return GET("$baseUrl/${getLatestSection()}?offset=$offset", headers) - } - - override fun latestUpdatesNextPageSelector(): String = "#load-more-items a" - - override fun latestUpdatesParse(response: Response): AnimesPage { - checkAdultContentFiltered(response.headers) - return super.latestUpdatesParse(response) - } - - override fun latestUpdatesSelector(): String = animeSelector(getLatestSection()) - - override fun latestUpdatesFromElement(element: Element): SAnime { - return animeFromElement(element, getLatestSection()) - } - - // Browse - - private fun getPopularSection(): String { - return preferences.getString("POPULAR", PREF_SECTIONS["Popular"])!! - } - - override fun popularAnimeRequest(page: Int): Request { - val offset = (page - 1) * PAGE_SIZE - return GET("$baseUrl/${getPopularSection()}?offset=$offset", headers) - } - - override fun popularAnimeNextPageSelector(): String = "#load-more-items a" - - override fun popularAnimeParse(response: Response): AnimesPage { - checkAdultContentFiltered(response.headers) - return super.popularAnimeParse(response) - } - - override fun popularAnimeSelector(): String = animeSelector(getPopularSection()) - - override fun popularAnimeFromElement(element: Element): SAnime { - return animeFromElement(element, getPopularSection()) - } - - // Search - - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { - val searchUrl = "$baseUrl/search/conduct/movies".toHttpUrl().newBuilder() - .addQueryParameter("page", page.toString()) - - if (query.isNotEmpty()) searchUrl.addQueryParameter("terms", query) - - filters.findInstance().ifFilterSet { - searchUrl.addQueryParameter("match", MATCH_AGAINST.values.elementAt(it.state)) - } - filters.findInstance()?.state - ?.findInstance().ifFilterSet { - searchUrl.addQueryParameter("exact", "1") - } - filters.findInstance()?.state - ?.findInstance().ifFilterSet { - searchUrl.addQueryParameter("any", "1") - } - filters.findInstance().ifFilterSet { - searchUrl.addQueryParameter("user", it.state) - } - filters.findInstance().ifFilterSet { - searchUrl.addQueryParameter("genre", GENRE.values.elementAt(it.state)) - } - filters.findInstance()?.state - ?.findInstance().ifFilterSet { - searchUrl.addQueryParameter("min_length", it.state) - } - filters.findInstance()?.state - ?.findInstance().ifFilterSet { - searchUrl.addQueryParameter("max_length", it.state) - } - filters.findInstance().ifFilterSet { - searchUrl.addQueryParameter("frontpaged", "1") - } - filters.findInstance()?.state - ?.findInstance().ifFilterSet { - searchUrl.addQueryParameter("after", it.state) - } - filters.findInstance()?.state - ?.findInstance().ifFilterSet { - searchUrl.addQueryParameter("before", it.state) - } - filters.findInstance().ifFilterSet { - if (it.state?.index != 0) { - val sortOption = SORTING.values.elementAt(it.state?.index ?: return@ifFilterSet) - val direction = if (it.state?.ascending == true) "asc" else "desc" - searchUrl.addQueryParameter( - "sort", - "$sortOption-$direction", - ) - } - } - filters.findInstance().ifFilterSet { - searchUrl.addQueryParameter("tags", it.state) - } - - return GET(searchUrl.build(), headers) - } - - override fun searchAnimeNextPageSelector(): String = "#results-load-more" - - override fun searchAnimeParse(response: Response): AnimesPage { - checkAdultContentFiltered(response.headers) - return super.searchAnimeParse(response) - } - - override fun searchAnimeSelector(): String = "ul.itemlist li:not(#results-load-more) a" - - override fun searchAnimeFromElement(element: Element): SAnime = animeFromListElement(element) - - // Etc. - - override fun animeDetailsParse(document: Document): SAnime { - fun getStarRating(): String { - val score: Double = document.selectFirst("#score_number")?.text()?.toDouble() ?: 0.0 - val fullStars = score.toInt() - val hasHalfStar = (score % 1) >= 0.5 - val totalStars = if (hasHalfStar) fullStars + 1 else fullStars - val emptyStars = 5 - totalStars - - return "✪".repeat(fullStars) + (if (hasHalfStar) "✪" else "") + "⬤".repeat(emptyStars) + " ($score)" - } - - fun getAdultRating(): String { - val rating = document.selectFirst("#embed_header h2")!!.className().substringAfter("rated-") - return when (rating) { - "e" -> "🟩 Everyone" - "t" -> "🟦 Ages 13+" - "m" -> "🟪 Ages 17+" - "a" -> "🟥 Adults Only" - else -> "❓" - } - } - - fun getStats(): String { - val statsElement = document.selectFirst("#sidestats > dl:first-of-type") - val views = statsElement?.selectFirst("dd:first-of-type")?.text() ?: "?" - val faves = statsElement?.selectFirst("dd:nth-of-type(2)")?.text() ?: "?" - val votes = statsElement?.selectFirst("dd:nth-of-type(3)")?.text() ?: "?" - - return "👀 $views | ❤️ $faves | 🗳️ $votes" - } - - fun prepareDescription(): String { - val descriptionElements = preferences.getStringSet("DESCRIPTION_ELEMENTS", setOf("short")) - ?: return "" - - val shortDescription = document.selectFirst("meta[itemprop=\"description\"]")?.attr("content") - val longDescription = document.selectFirst("#author_comments")?.wholeText() - val statsSummary = "${getAdultRating()} | ${getStarRating()} | ${getStats()}" - - val description = StringBuilder() - - if (descriptionElements.contains("short")) { - description.append(shortDescription) - } - - if (descriptionElements.contains("long")) { - description.append("\n\n" + longDescription) - } - - if (descriptionElements.contains("stats") || preferences.getBoolean("STATS_SUMMARY", false)) { - description.append("\n\n" + statsSummary) - } - - return description.toString() - } - - val relatedPlaylistElement = document.selectFirst("div[id^=\"related_playlists\"] ") - val relatedPlaylistUrl = relatedPlaylistElement?.selectFirst("a:not([id^=\"related_playlists\"])")?.absUrl("href") - val relatedPlaylistName = relatedPlaylistElement?.selectFirst(".detail-title h4")?.text() - val isPartOfSeries = relatedPlaylistUrl?.startsWith("$baseUrl/series") ?: false - - return SAnime.create().apply { - title = relatedPlaylistName.takeIf { isPartOfSeries } - ?: document.selectFirst("h2[itemprop=\"name\"]")!!.text() - description = prepareDescription() - author = document.selectFirst(".authorlinks > div:first-of-type .item-details-main")?.text() - artist = document.select(".authorlinks > div:not(:first-of-type) .item-details-main").joinToString { - it.text() - } - thumbnail_url = document.selectFirst("meta[itemprop=\"thumbnailUrl\"]")?.absUrl("content") - genre = document.select(".tags li a").joinToString { it.text() } + document.selectFirst("div[id^=\"genre-view\"] dt")?.text() - status = SAnime.ONGOING.takeIf { isPartOfSeries } ?: SAnime.COMPLETED - } - } - - override fun episodeListSelector(): String = throw UnsupportedOperationException("Not Used") - - override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException("Not Used") - - override suspend fun getEpisodeList(anime: SAnime): List { - val response = client.newCall(GET("${baseUrl}${anime.url}", headers)).execute() - val document = response.asJsoup() - - val relatedPlaylistUrl = document.selectFirst("div[id^=\"related_playlists\"] a:not([id^=\"related_playlists\"])")?.absUrl("href") - val isPartOfSeries = relatedPlaylistUrl?.startsWith("$baseUrl/series") ?: false - - val episodes = if (isPartOfSeries) { - val response2 = client.newCall(GET(relatedPlaylistUrl!!, headers)).execute() - val document2 = response2.asJsoup() - parseEpisodeList(document2) - } else { - val dateString = document.selectFirst("#sidestats > dl:nth-of-type(2) > dd:first-of-type")?.text() - - return listOf( - SEpisode.create().apply { - episode_number = 1f - date_upload = dateFormat.tryParse(dateString) - name = document.selectFirst("meta[name=\"title\"]")!!.attr("content") - setUrlWithoutDomain("$baseUrl${anime.url.replace("/view/","/video/")}") - }, - ) - } - - return episodes - } - - override fun episodeListRequest(anime: SAnime): Request = throw UnsupportedOperationException() - - override fun episodeListParse(response: Response): List = throw UnsupportedOperationException() - - private fun parseEpisodeList(document: Document): List { - val ids = document.select("li.visual-link-container").map { it.attr("data-visual-link") } - val formBody = FormBody.Builder() - .add("ids", ids.toString()) - .add("component_params[include_author]", "1") - .add("include_all_suitabilities", "0") - .add("isAjaxRequest", "1") - .build() - - val request = Request.Builder() - .url("$baseUrl/visual-links-fetch") - .post(formBody) - .headers(headers) - .build() - - val response = client.newCall(request).execute() - - val jsonObject = JSONObject(response.body.string()) - val episodes = mutableListOf() - - val simples = jsonObject.getJSONObject("simples") - var index = 1 - for (key in simples.keys()) { - val subObject = simples.getJSONObject(key) - - for (episodeKey in subObject.keys()) { - val episodeData = subObject.getJSONObject(episodeKey) - val uploaderData = episodeData.getJSONObject("user") - - val episode = SEpisode.create().apply { - episode_number = index.toFloat() - name = episodeData.getString("title") - scanlator = uploaderData.getString("user_name") - setUrlWithoutDomain("$baseUrl/portal/video/${episodeData.getString("id")}") - } - - episodes.add(episode) - index++ - } - } - - return episodes.reversed() - } - - override fun videoListRequest(episode: SEpisode): Request = GET("$baseUrl${episode.url}", videoListHeaders) - - override fun videoListParse(response: Response): List