diff --git a/lib/bangumi-scraper/build.gradle.kts b/lib/bangumi-scraper/build.gradle.kts deleted file mode 100644 index c49026e4..00000000 --- a/lib/bangumi-scraper/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - id("lib-android") -} - -dependencies { - compileOnly(libs.aniyomi.lib) -} \ No newline at end of file diff --git a/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiDTO.kt b/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiDTO.kt deleted file mode 100644 index 1f130ad2..00000000 --- a/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiDTO.kt +++ /dev/null @@ -1,83 +0,0 @@ -@file:UseSerializers(BoxItemSerializer::class) -package eu.kanade.tachiyomi.lib.bangumiscraper - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Serializer -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive - -@Serializable -internal data class Images( - val large: String, - val common: String, - val medium: String, - val small: String, -) - -@Serializable -internal data class BoxItem( - val key: String, - val value: String, -) - -@OptIn(ExperimentalSerializationApi::class) -@Serializer(forClass = BoxItem::class) -internal object BoxItemSerializer : KSerializer { - override fun deserialize(decoder: Decoder): BoxItem { - val item = (decoder as JsonDecoder).decodeJsonElement().jsonObject - val key = item["key"]!!.jsonPrimitive.content - val value = (item["value"] as? JsonPrimitive)?.contentOrNull ?: "" - return BoxItem(key, value) - } -} - -@Serializable -internal data class Subject( - val name: String, - @SerialName("name_cn") - val nameCN: String, - val summary: String, - val images: Images, - @SerialName("meta_tags") - val metaTags: List, - @SerialName("infobox") - val infoBox: List, -) { - fun findAuthor(): String? { - return findInfo("导演", "原作") - } - - fun findArtist(): String? { - return findInfo("美术监督", "总作画监督", "动画制作") - } - - fun findInfo(vararg keys: String): String? { - keys.forEach { key -> - return infoBox.find { item -> - item.key == key - }?.value ?: return@forEach - } - return null - } -} - -@Serializable -internal data class SearchItem( - val id: Int, - val name: String, - @SerialName("name_cn") - val nameCN: String, - val summary: String, - val images: Images, -) - -@Serializable -internal data class SearchResponse(val results: Int, val list: List) diff --git a/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiScraper.kt b/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiScraper.kt deleted file mode 100644 index 3653314e..00000000 --- a/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiScraper.kt +++ /dev/null @@ -1,126 +0,0 @@ -package eu.kanade.tachiyomi.lib.bangumiscraper - -import eu.kanade.tachiyomi.animesource.model.SAnime -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.awaitSuccess -import eu.kanade.tachiyomi.util.parseAs -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response - -enum class BangumiSubjectType(val value: Int) { - BOOK(1), - ANIME(2), - MUSIC(3), - GAME(4), - REAL(6), -} - -enum class BangumiFetchType { - /** - * Give cover and summary info. - */ - SHORT, - - /** - * Give all require info include genre and author info. - */ - ALL, -} - -/** - * A helper class to fetch anime details from Bangumi - */ -object BangumiScraper { - private const val SEARCH_URL = "https://api.bgm.tv/search/subject" - private const val SUBJECTS_URL = "https://api.bgm.tv/v0/subjects" - - /** - * Fetch anime details info from Bangumi - * @param fetchType check [BangumiFetchType] to get detail - * @param subjectType check [BangumiSubjectType] to get detail - * @param requestProducer used to custom request - */ - suspend fun fetchDetail( - client: OkHttpClient, - keyword: String, - fetchType: BangumiFetchType = BangumiFetchType.SHORT, - subjectType: BangumiSubjectType = BangumiSubjectType.ANIME, - requestProducer: (url: HttpUrl) -> Request = { url -> GET(url) }, - ): SAnime { - val httpUrl = SEARCH_URL.toHttpUrl().newBuilder() - .addPathSegment(keyword) - .addQueryParameter( - "responseGroup", - if (fetchType == BangumiFetchType.ALL) { - "small" - } else { - "medium" - }, - ) - .addQueryParameter("type", "${subjectType.value}") - .addQueryParameter("start", "0") - .addQueryParameter("max_results", "1") - .build() - val searchResponse = client.newCall(requestProducer(httpUrl)).awaitSuccess() - .checkErrorMessage().parseAs() - return if (searchResponse.list.isEmpty()) { - SAnime.create() - } else { - val item = searchResponse.list[0] - if (fetchType == BangumiFetchType.ALL) { - fetchSubject(client, "${item.id}", requestProducer) - } else { - SAnime.create().apply { - thumbnail_url = item.images.large - description = item.summary - } - } - } - } - - private suspend fun fetchSubject( - client: OkHttpClient, - id: String, - requestProducer: (url: HttpUrl) -> Request, - ): SAnime { - val httpUrl = SUBJECTS_URL.toHttpUrl().newBuilder().addPathSegment(id).build() - val subject = client.newCall(requestProducer(httpUrl)).awaitSuccess() - .checkErrorMessage().parseAs() - return SAnime.create().apply { - thumbnail_url = subject.images.large - description = subject.summary - genre = buildList { - addAll(subject.metaTags) - subject.findInfo("动画制作")?.let { add(it) } - subject.findInfo("放送开始")?.let { add(it) } - }.joinToString() - author = subject.findAuthor() - artist = subject.findArtist() - if (subject.findInfo("播放结束") != null) { - status = SAnime.COMPLETED - } else if (subject.findInfo("放送开始") != null) { - status = SAnime.ONGOING - } - } - } - - private fun Response.checkErrorMessage(): String { - val responseStr = body.string() - val errorMessage = - responseStr.parseAs().jsonObject["error"]?.jsonPrimitive?.contentOrNull - if (errorMessage != null) { - throw BangumiScraperException(errorMessage) - } - return responseStr - } -} - - - diff --git a/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiScraperException.kt b/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiScraperException.kt deleted file mode 100644 index 08d1e308..00000000 --- a/lib/bangumi-scraper/src/main/java/eu/kanade/tachiyomi/lib/bangumiscraper/BangumiScraperException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package eu.kanade.tachiyomi.lib.bangumiscraper - -class BangumiScraperException(message: String) : Exception(message) diff --git a/src/all/jable/build.gradle b/src/all/jable/build.gradle new file mode 100644 index 00000000..f9304496 --- /dev/null +++ b/src/all/jable/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'Jable' + extClass = '.JableFactory' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/jable/res/mipmap-hdpi/ic_launcher.png b/src/all/jable/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..dc1e7326 Binary files /dev/null and b/src/all/jable/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/jable/res/mipmap-mdpi/ic_launcher.png b/src/all/jable/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..6d68c4e1 Binary files /dev/null and b/src/all/jable/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/jable/res/mipmap-xhdpi/ic_launcher.png b/src/all/jable/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..f46a5539 Binary files /dev/null and b/src/all/jable/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/jable/res/mipmap-xxhdpi/ic_launcher.png b/src/all/jable/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..78f0ad3b Binary files /dev/null and b/src/all/jable/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/jable/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/jable/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..5b55e575 Binary files /dev/null and b/src/all/jable/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/jable/src/eu/kanade/tachiyomi/animeextension/all/jable/Jable.kt b/src/all/jable/src/eu/kanade/tachiyomi/animeextension/all/jable/Jable.kt new file mode 100644 index 00000000..d8b3bc20 --- /dev/null +++ b/src/all/jable/src/eu/kanade/tachiyomi/animeextension/all/jable/Jable.kt @@ -0,0 +1,248 @@ +package eu.kanade.tachiyomi.animeextension.all.jable + +import android.app.Application +import android.content.SharedPreferences +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimeUpdateStrategy +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.util.asJsoup +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy + +class Jable(override val lang: String) : AnimeHttpSource() { + override val baseUrl: String + get() = "https://jable.tv" + override val name: String + get() = "Jable" + override val supportsLatest: Boolean + get() = true + + private val preferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + private val json by injectLazy() + private var tagsUpdated = false + + override fun animeDetailsRequest(anime: SAnime): Request { + return GET("$baseUrl${anime.url}?lang=$lang", headers) + } + + override fun animeDetailsParse(response: Response): SAnime { + val doc = response.asJsoup() + return SAnime.create().apply { + val info = doc.select(".info-header") + title = info.select(".header-left h4").text() + author = info.select(".header-left .model") + .joinToString { it.select("span[title]").attr("title") } + genre = doc.select(".tags a").joinToString { it.text() } + update_strategy = AnimeUpdateStrategy.ONLY_FETCH_ONCE + status = SAnime.COMPLETED + description = info.select(".header-right").text() + } + } + + override fun episodeListParse(response: Response) = throw UnsupportedOperationException() + + override suspend fun getEpisodeList(anime: SAnime): List { + return listOf( + SEpisode.create().apply { + name = "Episode" + url = anime.url + }, + ) + } + + override fun videoListParse(response: Response): List