diff --git a/src/de/serienstream/build.gradle b/src/de/serienstream/build.gradle
index dc4557e5..4267a35e 100644
--- a/src/de/serienstream/build.gradle
+++ b/src/de/serienstream/build.gradle
@@ -1,7 +1,7 @@
ext {
extName = 'Serienstream'
extClass = '.Serienstream'
- extVersionCode = 19
+ extVersionCode = 20
}
apply from: "$rootDir/common.gradle"
@@ -10,4 +10,4 @@ dependencies {
implementation(project(':lib:voe-extractor'))
implementation(project(':lib:streamtape-extractor'))
implementation(project(':lib:dood-extractor'))
-}
\ No newline at end of file
+}
diff --git a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt
index 5a74c6e3..fcc6e65d 100644
--- a/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt
+++ b/src/de/serienstream/src/eu/kanade/tachiyomi/animeextension/de/serienstream/Serienstream.kt
@@ -37,7 +37,7 @@ class Serienstream : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Serienstream"
- override val baseUrl = "http://186.2.175.5"
+ override val baseUrl = "https://s.to"
override val lang = "de"
@@ -91,7 +91,7 @@ class Serienstream : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val headers = Headers.Builder()
- .add("Referer", "http://186.2.175.5/search")
+ .add("Referer", "https://s.to/search")
.add("origin", baseUrl)
.add("connection", "keep-alive")
.add("user-agent", "Mozilla/5.0 (Linux; Android 12; Pixel 5 Build/SP2A.220405.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Safari/537.36")
diff --git a/src/en/rule34video/build.gradle b/src/en/rule34video/build.gradle
index fa562806..1ab31bdb 100644
--- a/src/en/rule34video/build.gradle
+++ b/src/en/rule34video/build.gradle
@@ -1,7 +1,7 @@
ext {
extName = 'Rule34Video'
extClass = '.Rule34Video'
- extVersionCode = 9
+ extVersionCode = 8
isNsfw = true
}
diff --git a/src/en/rule34video/src/eu/kanade/tachiyomi/animeextension/en/rule34video/Rule34Video.kt b/src/en/rule34video/src/eu/kanade/tachiyomi/animeextension/en/rule34video/Rule34Video.kt
index cbf1632b..52a84082 100644
--- a/src/en/rule34video/src/eu/kanade/tachiyomi/animeextension/en/rule34video/Rule34Video.kt
+++ b/src/en/rule34video/src/eu/kanade/tachiyomi/animeextension/en/rule34video/Rule34Video.kt
@@ -1,10 +1,8 @@
package eu.kanade.tachiyomi.animeextension.en.rule34video
+
import android.app.Application
-import android.util.Log
-import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
-import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
@@ -43,21 +41,7 @@ class Rule34Video : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
// ============================== Popular ===============================
- override fun popularAnimeRequest(page: Int): Request {
- return if (preferences.getBoolean(PREF_UPLOADER_FILTER_ENABLED_KEY, false)) {
- val uploaderId = preferences.getString(PREF_UPLOADER_ID_KEY, "") ?: ""
- if (uploaderId.isNotBlank()) {
- val url = "$baseUrl/members/$uploaderId/videos/?mode=async&function=get_block&block_id=list_videos_uploaded_videos&sort_by=&from_videos=$page"
- Log.e("Rule34Video", "Loading popular videos from uploader ID: $uploaderId, page: $page, URL: $url")
- GET(url)
- } else {
- Log.e("Rule34Video", "Uploader filter enabled but ID is blank, loading latest updates.")
- GET("$baseUrl/latest-updates/$page/")
- }
- } else {
- GET("$baseUrl/latest-updates/$page/")
- }
- }
+ override fun popularAnimeRequest(page: Int) = GET("$baseUrl/latest-updates/$page/")
override fun popularAnimeSelector() = "div.item.thumb"
@@ -118,60 +102,21 @@ class Rule34Video : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document) = SAnime.create().apply {
- title = document.selectFirst("h1.title_video")?.text().toString()
-
- val infoRow = document.selectFirst("div.info.row")
- val detailRows = document.select("div.row")
-
- val artistElement = detailRows.select("div.col:has(div.label:contains(Artist)) a.item span.name").firstOrNull()
- author = artistElement?.text().orEmpty()
-
+ title = document.selectFirst("h1.title_video")!!.text()
+ val info = document.selectFirst("#tab_video_info")!!
+ author = info.select("div.label:contains(Artist:) + a").eachText().joinToString()
description = buildString {
- detailRows.select("div.row:has(div.label > em) > div.label > em").html()
- .replace("
", "\n") // Ensure single
tags are followed by a newline
- .let { text ->
- append(text)
- }
- append("\n\n") // Add extra spacing
-
- infoRow?.selectFirst("div.item_info:nth-child(1) > span")?.text()?.let {
- append("Uploaded: $it\n")
- }
-
- val artist = detailRows.select("div.col:has(div.label:contains(Artist)) a.item span.name")
- .eachText()
- .joinToString()
- if (artist.isNotEmpty()) {
- append("Artists: $artist\n")
- }
-
- val categories = detailRows.select("div.col:has(div.label:contains(Categories)) a.item span")
- .eachText()
- .joinToString()
- if (categories.isNotEmpty()) {
- append("Categories: $categories\n")
- }
-
- val uploader = detailRows.select("div.col:has(div.label:contains(Uploaded by)) a.item").text()
- if (uploader.isNotEmpty()) {
- append("Uploader: $uploader\n")
- }
-
- infoRow?.select("div.item_info:nth-child(2) > span")?.text()?.let {
- val views = it.substringBefore(" ").replace(",", "")
- append("Views: $views\n")
- }
- infoRow?.select("div.item_info:nth-child(3) > span")?.text()?.let { append("Duration: $it\n") }
- document.select("div.row:has(div.label:contains(Download)) a.tag_item")
+ info.selectFirst("div.label:contains(Description:) > em")?.text()?.also { append("$it\n") }
+ info.selectFirst("i.icon-eye + span")?.text()?.also { append("\nViews : ${it.replace(" ", ",")}") }
+ info.selectFirst("i.icon-clock + span")?.text()?.also { append("\nDuration : $it") }
+ document.select("div.label:contains(Download) ~ a.tag_item")
.eachText()
.joinToString { it.substringAfter(" ") }
- .also { append("Quality: $it") }
+ .also { append("\nQuality : $it") }
}
-
- genre = document.select("div.row_spacer:has(div.label:contains(Tags)) a.tag_item:not(:contains(Suggest))")
+ genre = document.select("div.label:contains(Tags) ~ a.tag_item:not(:contains(Suggest))")
.eachText()
.joinToString()
-
status = SAnime.COMPLETED
}
@@ -241,24 +186,6 @@ class Rule34Video : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
- SwitchPreferenceCompat(screen.context).apply {
- key = PREF_UPLOADER_FILTER_ENABLED_KEY
- title = "Filter by Uploader"
- summary = "Load videos only from the specified uploader ID."
- setDefaultValue(false)
- }.also(screen::addPreference)
-
- EditTextPreference(screen.context).apply {
- key = PREF_UPLOADER_ID_KEY
- title = "Uploader ID"
- summary = "Enter the ID of the uploader (e.g., 98965). Requires \"Filter by Uploader\" to be enabled."
- dialogTitle = "Enter Uploader ID"
- var dependency = PREF_UPLOADER_FILTER_ENABLED_KEY
- setOnPreferenceChangeListener { _, newValue ->
- newValue?.toString().isNullOrBlank().not()
- }
- }.also(screen::addPreference)
-
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = PREF_QUALITY_TITLE
@@ -291,20 +218,14 @@ class Rule34Video : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return tagList.toTypedArray()
}
- override fun getFilterList(): AnimeFilterList = if (preferences.getBoolean(PREF_UPLOADER_FILTER_ENABLED_KEY, false) &&
- preferences.getString(PREF_UPLOADER_ID_KEY, "")?.isNotBlank() == true
- ) {
- AnimeFilterList() // If uploader filter is enabled and ID is set, show no other filters
- } else {
- AnimeFilterList(
- OrderFilter(),
- CategoryBy(),
- AnimeFilter.Separator(),
- AnimeFilter.Header("Entered a \"tag\", click on \"filter\" then Click \"reset\" to load tags."),
- TagFilter(),
- TagSearch(tagsResults(tagDocument)),
- )
- }
+ override fun getFilterList(): AnimeFilterList = AnimeFilterList(
+ OrderFilter(),
+ CategoryBy(),
+ AnimeFilter.Separator(),
+ AnimeFilter.Header("Entered a \"tag\", click on \"filter\" then Click \"reset\" to load tags."),
+ TagFilter(),
+ TagSearch(tagsResults(tagDocument)),
+ )
private class TagFilter : AnimeFilter.Text("Click \"reset\" without any text to load all A-Z tags.", "")
@@ -346,8 +267,5 @@ class Rule34Video : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private const val PREF_QUALITY_TITLE = "Preferred quality"
private const val PREF_QUALITY_DEFAULT = "1080p"
private val PREF_QUALITY_ENTRIES = arrayOf("2160p", "1080p", "720p", "480p", "360p")
-
- private const val PREF_UPLOADER_FILTER_ENABLED_KEY = "uploader_filter_enabled"
- private const val PREF_UPLOADER_ID_KEY = "uploader_id"
}
}
diff --git a/src/pt/tomato/build.gradle b/src/pt/tomato/build.gradle
deleted file mode 100644
index 6b964445..00000000
--- a/src/pt/tomato/build.gradle
+++ /dev/null
@@ -1,7 +0,0 @@
-ext {
- extName = 'Tomato'
- extClass = '.Tomato'
- extVersionCode = 1
-}
-
-apply from: "$rootDir/common.gradle"
diff --git a/src/pt/tomato/res/mipmap-hdpi/ic_launcher.png b/src/pt/tomato/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index a9e39ecc..00000000
Binary files a/src/pt/tomato/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/tomato/res/mipmap-mdpi/ic_launcher.png b/src/pt/tomato/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 2adff7e0..00000000
Binary files a/src/pt/tomato/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/tomato/res/mipmap-xhdpi/ic_launcher.png b/src/pt/tomato/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 31fa1a88..00000000
Binary files a/src/pt/tomato/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/tomato/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/tomato/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index cc673719..00000000
Binary files a/src/pt/tomato/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/tomato/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/tomato/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index d2f201dc..00000000
Binary files a/src/pt/tomato/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/tomato/src/eu/kanade/tachiyomi/animeextension/pt/tomato/Tomato.kt b/src/pt/tomato/src/eu/kanade/tachiyomi/animeextension/pt/tomato/Tomato.kt
deleted file mode 100644
index a8b31601..00000000
--- a/src/pt/tomato/src/eu/kanade/tachiyomi/animeextension/pt/tomato/Tomato.kt
+++ /dev/null
@@ -1,336 +0,0 @@
-package eu.kanade.tachiyomi.animeextension.pt.tomato
-
-import android.app.Application
-import android.util.Log
-import androidx.preference.ListPreference
-import androidx.preference.PreferenceScreen
-import eu.kanade.tachiyomi.animeextension.pt.tomato.dto.AnimeResultDto
-import eu.kanade.tachiyomi.animeextension.pt.tomato.dto.EpisodeInfoDto
-import eu.kanade.tachiyomi.animeextension.pt.tomato.dto.EpisodesResultDto
-import eu.kanade.tachiyomi.animeextension.pt.tomato.dto.SearchAnimeItemDto
-import eu.kanade.tachiyomi.animeextension.pt.tomato.dto.SearchResultDto
-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.AnimeHttpSource
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.POST
-import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
-import eu.kanade.tachiyomi.util.parallelMapBlocking
-import eu.kanade.tachiyomi.util.parseAs
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonObject
-import kotlinx.serialization.json.add
-import kotlinx.serialization.json.buildJsonObject
-import kotlinx.serialization.json.int
-import kotlinx.serialization.json.jsonArray
-import kotlinx.serialization.json.jsonObject
-import kotlinx.serialization.json.jsonPrimitive
-import kotlinx.serialization.json.put
-import kotlinx.serialization.json.putJsonArray
-import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.MediaType.Companion.toMediaType
-import okhttp3.Request
-import okhttp3.RequestBody.Companion.toRequestBody
-import okhttp3.Response
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
-import kotlin.time.Duration.Companion.seconds
-
-class Tomato : ConfigurableAnimeSource, AnimeHttpSource() {
-
- override val name = "Tomato"
-
- override val baseUrl = "https://beta-api.tomatoanimes.com"
-
- override val lang = "pt-BR"
-
- override val supportsLatest = true
-
- private val preferences by lazy {
- Injekt.get().getSharedPreferences("source_$id", 0x0000)
- }
-
- private val json: Json by injectLazy()
-
- override fun headersBuilder() = super.headersBuilder().add(
- "Authorization",
- "Bearer $TOKEN",
- )
-
- private val episodesClient by lazy {
- client.newBuilder().rateLimitHost(baseUrl.toHttpUrl(), 1, 0.5.seconds).build()
- }
-
- // ============================== Popular ===============================
- override fun popularAnimeRequest(page: Int) =
- GET("$baseUrl/v2/animes/feed", headers = headers)
-
- override fun popularAnimeParse(response: Response): AnimesPage {
- val responseJson = response.parseAs()
-
- val emAlta = responseJson["data"]?.jsonArray?.find {
- it.jsonObject["title"]?.jsonPrimitive?.content?.contains("curtidos") == true
- }
-
- val animes = emAlta?.jsonObject?.get("data")?.jsonArray?.parallelMapBlocking {
- animeFromId(it.jsonObject["anime_id"]!!.jsonPrimitive.int)
- }
- ?: emptyList()
-
- return AnimesPage(animes, false)
- }
-
- // =============================== Latest ===============================
- override fun latestUpdatesRequest(page: Int) =
- GET("$baseUrl/v2/animes/feed", headers = headers)
-
- override fun latestUpdatesParse(response: Response): AnimesPage {
- val responseJson = response.parseAs()
-
- val emAlta = responseJson["data"]?.jsonArray?.find {
- it.jsonObject["type"]!!.jsonPrimitive.int == 7
- }
-
- val animes = emAlta?.jsonObject?.get("data")?.jsonArray?.parallelMapBlocking {
- animeFromId(it.jsonObject["ep_anime_id"]!!.jsonPrimitive.int)
- }
- ?: emptyList()
-
- return AnimesPage(animes, false)
- }
-
- // =============================== Search ===============================
- override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
- val params = TomatoFilters.getSearchParameters(filters)
-
- val data = buildJsonObject {
- put("token", TOKEN)
- put("search", query)
- put("content_type", "anime")
- put("page", page - 1)
-
- if (params.genres.isNotEmpty()) {
- putJsonArray("tags") {
- params.genres.forEach { add(it) }
- }
- }
- }
-
- val body = json.encodeToString(JsonObject.serializer(), data)
- .toRequestBody("application/json".toMediaType())
-
- return POST("$baseUrl/v2/content/search", headers = headers, body = body)
- }
-
- override fun searchAnimeParse(response: Response): AnimesPage {
- val searchResult = response.parseAs().result
- val results = searchResult.map { it.toSAnime() }
- return AnimesPage(results, false)
- }
-
- private fun SearchAnimeItemDto.toSAnime(): SAnime {
- return SAnime.create().apply {
- setUrlWithoutDomain("$baseUrl/v2/anime/$id")
- title = name
- thumbnail_url = image
- }
- }
-
- override fun getFilterList() = TomatoFilters.FILTER_LIST
-
- // =========================== Anime Details ============================
- private fun animeFromId(id: Int): SAnime {
- val response = client.newCall(
- GET("$baseUrl/v2/anime/$id", headers = headers),
- ).execute()
-
- return animeDetailsParse(response)
- }
-
- override fun animeDetailsParse(response: Response): SAnime {
- val anime = response.parseAs()
- return SAnime.create().apply {
- setUrlWithoutDomain("$baseUrl/v2/anime/${anime.animeDetails.animeId}")
- title = anime.animeDetails.animeName
- description = anime.animeDetails.animeDescription
- genre = anime.animeDetails.animeGenre
- thumbnail_url = anime.animeDetails.animeCoverUrl
- }
- }
-
- // ============================== Episodes ==============================
- override fun episodeListParse(response: Response): List {
- val anime = response.parseAs()
-
- val seasons = anime.animeSeasons
-
- val episodeList = mutableListOf()
-
- seasons.forEach { season ->
- var nextPage = 0
- do {
- val data = buildJsonObject {
- put("token", TOKEN)
- put("page", nextPage)
- put("order", "ASC")
- }
-
- val body = json.encodeToString(JsonObject.serializer(), data)
- .toRequestBody("application/json".toMediaType())
-
- val request = POST(
- "$baseUrl/season/${season.seasonId}/episodes",
- headers = headers,
- body = body,
- )
- val episodes =
- episodesClient.newCall(request).execute().parseAs().data
-
- episodes.forEach { episode ->
- val partName = "Temporada ${season.seasonNumber} x ${episode.epNumber}"
- val fullName = "$partName - ${episode.epName}"
-
- val prev = episodeList.find { it.name.contains(partName) }
-
- val newUrl = "&episode[${season.seasonDubbed}]=${episode.epId}"
- if (prev != null) {
- prev.url += newUrl
- } else {
- episodeList.add(
- SEpisode.create().apply {
- episode_number = episode.epNumber
- name = fullName
- url = "http://localhost?season=${season.seasonNumber}$newUrl"
- },
- )
- }
- }
-
- if (episodes.size == 25) nextPage += 1 else nextPage = -1
- } while (nextPage != -1)
- }
-
- return episodeList.reversed()
- }
-
- // ============================ Video Links =============================
- override suspend fun getVideoList(episode: SEpisode): List