Remove extensions redirecting to other extensions (#8076)

* Remove Apolltoons (replaced by Mundo Manhwa)

* Remove Arctic Scan (replaced by Yushuke Mangas)

* chore: update comment bacakomik.co -> bacakomik.one

* Remove Black Scans (replaced by Yugen Mangás)

* Remove KomikIndo.info (replaced by Mangasusu)

* Remove Snow Scans (replaced by Galaxy)

* Remove Vex Manga (replaced by Vortex Scans)

* Remove MangaSaki (replaced by Mangasail)

* Remove Xmanhwa (replaced by ManhwaDen)
This commit is contained in:
Vetle Ledaal 2025-03-15 06:56:12 +01:00 committed by Draff
parent f5aecd2ad4
commit 5508ced068
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
60 changed files with 1 additions and 752 deletions

View File

@ -1,10 +0,0 @@
ext {
extName = 'Vex Manga'
extClass = '.VexManga'
themePkg = 'mangathemesia'
baseUrl = 'https://vexmanga.com'
overrideVersionCode = 3
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,74 +0,0 @@
package eu.kanade.tachiyomi.extension.ar.vexmanga
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.lang.IllegalArgumentException
import java.util.Calendar
class VexManga : MangaThemesia(
"فيكس مانجا",
"https://vexmanga.com",
"ar",
) {
override fun searchMangaSelector() = ".listarchives .latest-recom, .listupd .latest-series, ${super.searchMangaSelector()}"
override val sendViewCount = false
override fun chapterListSelector() = ".ulChapterList > a, ${super.chapterListSelector()}"
override val seriesArtistSelector =
".tsinfo .imptdt:contains(الرسام) i, ${super.seriesArtistSelector}"
override val seriesAuthorSelector =
".tsinfo .imptdt:contains(المؤلف) i, ${super.seriesAuthorSelector}"
override val seriesStatusSelector =
".tsinfo .imptdt:contains(الحالة) i, ${super.seriesStatusSelector}"
override val seriesTypeSelector =
".tsinfo .imptdt:contains(النوع) i, ${super.seriesTypeSelector}"
override fun String?.parseStatus() = when {
this == null -> SManga.UNKNOWN
this.contains("مستمر", ignoreCase = true) -> SManga.ONGOING
this.contains("مكتمل", ignoreCase = true) -> SManga.COMPLETED
this.contains("متوقف", ignoreCase = true) -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.attr("href"))
name = element.select(".chapternum").text()
date_upload = element.select(".chapterdate").text().parseRelativeDate()
}
private fun String.parseRelativeDate(): Long {
val number = Regex("""(\d+)""").find(this)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
return when {
this.contains("أيام", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
this.contains("ساعة", true) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
this.contains("دقائق", true) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
this.contains("أسبوعين", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis
this.contains("أشهر", true) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
else -> 0
}
}
override fun pageListParse(document: Document): List<Page> {
val docString = document.toString()
val imageListJson = JSON_IMAGE_LIST_REGEX.find(docString)?.destructured?.toList()?.get(0).orEmpty()
val imageList = try {
json.parseToJsonElement(imageListJson).jsonArray
} catch (_: IllegalArgumentException) {
emptyList()
}
val scriptPages = imageList.mapIndexed { i, jsonEl ->
Page(i, document.location(), jsonEl.jsonPrimitive.content)
}
return scriptPages
}
}

View File

@ -1,8 +0,0 @@
ext {
extName = 'MangaSaki'
extClass = '.MangaSaki'
extVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,265 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangasaki
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
class MangaSaki : ParsedHttpSource() {
override val name = "MangaSaki"
override val baseUrl = "https://www.mangasaki.org"
override val lang = "en"
override val supportsLatest = true
// popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/directory/hot?page=${page - 1}", headers)
}
override fun popularMangaSelector() = ".directory_list tbody tr"
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
val titleElement = element.selectFirst("td a img")!!
manga.title = titleElement.attr("title")
manga.setUrlWithoutDomain(element.selectFirst("td a")!!.attr("href"))
manga.thumbnail_url = titleElement.attr("src")
return manga
}
override fun popularMangaNextPageSelector() = "li.pager-next a"
// latest
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/directory/new?page=${page - 1}", headers)
}
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// search
private var searchMode: Boolean = false
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return if (query.isNotEmpty()) {
searchMode = true
GET("$baseUrl/search/node/$query?page=${page - 1}", headers)
} else {
searchMode = false
var url = "$baseUrl/tags/"
filters.forEach { filter ->
when (filter) {
is GenreFilter -> {
url += "${filter.toUriPart()}?page=${page - 1}"
}
else -> {}
}
}
GET(url, headers)
}
}
override fun searchMangaSelector(): String {
return if (!searchMode) {
"div.view-content div.views-row"
} else {
"ol.search-results li.search-result"
}
}
override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create()
if (!searchMode) {
manga.title = element.select("div.views-field-title a").text()
manga.setUrlWithoutDomain(element.select("div.views-field-title a").attr("href"))
manga.thumbnail_url = element.select("div.views-field-field-image2 img").attr("src")
} else {
// The site doesn't show thumbnails when using search
val titleElement = element.select("h3.title a")
manga.title = titleElement.text()
manga.setUrlWithoutDomain(titleElement.attr("href"))
}
return manga
}
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// manga details
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
manga.author = document.selectFirst("div.field-name-field-author div.field-item")?.text()
manga.genre = document.select("div.field-name-field-genres ul li a").joinToString { it.text() }
manga.description = document.select("div.field-name-body div.field-item p").text()
manga.thumbnail_url = document.select("div.field-name-field-image2 div.field-item img").attr("src")
val statusText = document.select("div.field-name-field-status div.field-item").text()
manga.status = when {
statusText.contains("Ongoing", true) -> SManga.ONGOING
statusText.contains("Complete", true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
return manga
}
// chapters
override fun chapterListRequest(manga: SManga) = chapterListRequest(manga.url, 1)
private fun chapterListRequest(url: String, page: Int): Request {
return GET("$baseUrl$url?page=${page - 1}", headers)
}
override fun chapterListParse(response: Response): List<SChapter> {
var document = response.asJsoup()
val chapters = document.select(chapterListSelector()).map(::chapterFromElement).toMutableList()
var nextPage = 2
while (document.select(latestUpdatesNextPageSelector()).isNotEmpty()) {
val dirtyPage = document.select("div#block-search-form form#search-block-form").attr("action")
val cleaningIndex = dirtyPage.lastIndexOf("?")
val cleanPage = dirtyPage.substring(0, cleaningIndex)
document = client.newCall(chapterListRequest(cleanPage, nextPage)).execute().asJsoup()
chapters.addAll(document.select(chapterListSelector()).map(::chapterFromElement))
nextPage++
}
return chapters
}
override fun chapterListSelector() = ".chlist tbody tr"
private val dateFormat = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(element.select("a").attr("href"))
chapter.name = element.select("a").text()
chapter.date_upload = try {
element.select("td").last()?.text()?.let {
dateFormat.parse(it)?.time ?: 0L
} ?: 0L
} catch (_: ParseException) {
0L
}
return chapter
}
// pages
override fun pageListParse(document: Document): List<Page> {
val jsonString = document.select("script:containsData(showmanga)").first()!!.data()
.substringAfter("(Drupal.settings, ")
.substringBeforeLast(");")
return parseJSON(jsonString).mapIndexed { i, it ->
Page(i, imageUrl = it)
}
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
override fun getFilterList() = FilterList(
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(),
GenreFilter(),
)
private class GenreFilter : UriPartFilter(
"Category",
arrayOf(
Pair("Action", "action"),
Pair("Adult", "adult"),
Pair("Adventure", "adventure"),
Pair("Comedy", "comedy"),
Pair("Crime", "crime"),
Pair("Drama", "drama"),
Pair("Dungeons", "dungeons"),
Pair("Ecchi", "ecchi"),
Pair("Fantasy", "fantasy"),
Pair("GenderBender", "genderbender"),
Pair("Gender Bender", "gender-bender"),
Pair("Harem", "harem"),
Pair("Hentai", "hentai"),
Pair("Historical", "historical"),
Pair("Horror", "horror"),
Pair("Isekai", "isekai"),
Pair("Josei", "josei"),
Pair("Lolicon", "lolicon"),
Pair("Magical Girls", "magical-girls"),
Pair("MartialArts", "martialarts"),
Pair("Martial Arts", "martial-arts"),
Pair("Mature", "mature"),
Pair("Mecha", "mecha"),
Pair("Medical", "medical"),
Pair("N/A", "na"),
Pair("Philosophical", "philosophical"),
Pair("Psychological", "psychological"),
Pair("SchoolLife", "schoollife"),
Pair("School Life", "school-life"),
Pair("Sci-fi", "sci-fi"),
Pair("Sci-fi Shounen", "sci-fi-shounen"),
Pair("Seinen", "seinen"),
Pair("Shotacon", "shotacon"),
Pair("Shoujo", "shoujo"),
Pair("ShoujoAi", "shoujoai"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("ShounenAi", "shounenai"),
Pair("Shounen-Ai", "shounen-ai"),
Pair("SliceofLife", "slicelife"),
Pair("Slice of Life", "slice-life"),
Pair("Smut", "smut"),
Pair("Sports", "sports"),
Pair("Superhero", "superhero"),
Pair("Supernatural", "supernatural"),
Pair("System", "system"),
Pair("Thriller", "thriller"),
Pair("Tragedy", "tragedy"),
Pair("Webtoons", "webtoons"),
Pair("Wuxia", "wuxia"),
Pair("Yuri", "yuri"),
),
)
private val json: Json by injectLazy()
private fun parseJSON(jsonString: String): List<String> {
val jsonData = json.decodeFromString<JSONData>(jsonString)
return jsonData.showmanga.paths.filter { it.contains("mangasaki") }
}
private open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
@Serializable
class JSONData(val showmanga: ShowMangaData)
@Serializable
class ShowMangaData(val paths: List<String>)
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'Snow Scans'
extClass = '.SnowScans'
themePkg = 'mangathemesia'
baseUrl = 'https://snowscans.com'
overrideVersionCode = 0
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,10 +0,0 @@
package eu.kanade.tachiyomi.extension.en.snowscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
class SnowScans : MangaThemesia(
"Snow Scans",
"https://snowscans.com",
"en",
mangaUrlDirectory = "/series",
)

View File

@ -1,10 +0,0 @@
ext {
extName = 'Xmanhwa'
extClass = '.Xmanhwa'
themePkg = 'madara'
baseUrl = 'https://www.xmanhwa.me'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@ -1,14 +0,0 @@
package eu.kanade.tachiyomi.extension.en.xmanhwa
import eu.kanade.tachiyomi.multisrc.madara.Madara
class Xmanhwa : Madara(
"Xmanhwa",
"https://www.xmanhwa.me",
"en",
) {
override val useLoadMoreRequest = LoadMoreStrategy.Never
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'Apolltoons'
extClass = '.Apolltoons'
themePkg = 'madara'
baseUrl = 'https://apolltoons.xyz'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.extension.es.apolltoons
import eu.kanade.tachiyomi.multisrc.madara.Madara
import java.text.SimpleDateFormat
import java.util.Locale
class Apolltoons : Madara("Apolltoons", "https://apolltoons.xyz", "es", SimpleDateFormat("dd MMMMM, yyyy", Locale("es")))

View File

@ -24,7 +24,7 @@ class KomikIndoID : ParsedHttpSource() {
override val client: OkHttpClient = network.cloudflareClient
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US)
// similar/modified theme of "https://bacakomik.co"
// similar/modified theme of "https://bacakomik.one"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/daftar-manga/page/$page/?order=popular", headers)
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'KomikIndo.info'
extClass = '.KomikIndoInfo'
themePkg = 'zmanga'
baseUrl = 'https://komikindo.info'
overrideVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@ -1,10 +0,0 @@
package eu.kanade.tachiyomi.extension.id.komikindoinfo
import eu.kanade.tachiyomi.multisrc.zmanga.ZManga
import java.text.SimpleDateFormat
import java.util.Locale
class KomikIndoInfo : ZManga("KomikIndo.info", "https://komikindo.info", "id", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("id"))) {
override val hasProjectPage = true
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'Arctic Scan'
extClass = '.ArcticScan'
themePkg = 'madara'
baseUrl = 'https://arcticscan.top'
overrideVersionCode = 0
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -1,14 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.arcticscan
import eu.kanade.tachiyomi.multisrc.madara.Madara
import java.text.SimpleDateFormat
import java.util.Locale
class ArcticScan : Madara(
"Arctic Scan",
"https://arcticscan.top",
"pt-BR",
dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ROOT),
) {
override val useNewChapterEndpoint = true
}

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".pt.blackscans.BlackScansUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="blackscans.site"
android:pathPattern="/series/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,7 +0,0 @@
ext {
extName = 'Black Scans'
extClass = '.BlackScans'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -1,179 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.blackscans
import android.annotation.SuppressLint
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okio.Buffer
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
class BlackScans : HttpSource() {
override val name = "Black Scans"
override val baseUrl = "https://blackscans.site"
override val lang = "pt-BR"
override val supportsLatest = true
override val client = network.cloudflareClient.newBuilder()
.rateLimitHost(API_URL.toHttpUrl(), 2)
.build()
private val json: Json by injectLazy()
// ============================== Popular ==============================
override fun popularMangaRequest(page: Int) = GET("$API_URL/api/series/", headers)
override fun popularMangaParse(response: Response): MangasPage {
val mangas = response.parseAs<List<MangaDto>>().map { manga ->
SManga.create().apply {
title = manga.title
thumbnail_url = "$API_URL/media/${manga.cover}"
url = "/series/${manga.code}"
}
}
return MangasPage(mangas, false)
}
// ============================== Latest ==============================
override fun latestUpdatesRequest(page: Int) = GET("$API_URL/api/series/updates/", headers)
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
// ============================== Search ==============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = popularMangaRequest(page)
override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(PREFIX_SEARCH)) {
val mangaCode = query.substringAfter(PREFIX_SEARCH)
return fetchMangaDetails(SManga.create().apply { url = "/series/$mangaCode" })
.map { manga -> MangasPage(listOf(manga), false) }
}
return super.fetchSearchManga(page, query, filters).map { mangasPage ->
val mangas = mangasPage.mangas.filter { manga -> manga.title.contains(query, true) }
mangasPage.copy(mangas)
}
}
// ============================== Details =============================
override fun getMangaUrl(manga: SManga) = "$baseUrl${manga.url}"
override fun mangaDetailsRequest(manga: SManga) =
POST("$API_URL/api/serie/", headers, manga.createPostPayload())
override fun mangaDetailsParse(response: Response): SManga {
return response.parseAs<MangaDetailsDto>().let { dto ->
SManga.create().apply {
title = dto.title
description = dto.synopsis
thumbnail_url = "$API_URL/media/${dto.cover}"
author = dto.author
artist = dto.artist
genre = dto.genres.joinToString()
url = "/series/${dto.code}"
status = dto.status.toMangaStatus()
}
}
}
private fun String.toMangaStatus(): Int {
return when (this.lowercase()) {
"ongoing" -> SManga.ONGOING
"completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
// ============================== Chapters ============================
override fun getChapterUrl(chapter: SChapter) = "$baseUrl${chapter.url}"
override fun chapterListRequest(manga: SManga): Request {
val payload = manga.createPostPayload("series_code")
return POST("$API_URL/api/series/chapters/", headers, payload)
}
override fun chapterListParse(response: Response): List<SChapter> {
val series = response.request.body!!.parseAs<SeriesDto>()
return response.parseAs<ChapterList>().chapters.map { chapter ->
SChapter.create().apply {
name = chapter.name
date_upload = chapter.uploadAt.toDate()
url = "/series/${series.code}/${chapter.code}"
}
}
}
// ============================== Pages ===============================
override fun imageUrlParse(response: Response) = ""
override fun pageListRequest(chapter: SChapter): Request {
val chapterCode = chapter.url.substringAfterLast("/")
val payload = """{"chapter_code":"$chapterCode"}"""
.toRequestBody("application/json".toMediaType())
return POST("$API_URL/api/chapter/info/", headers, payload)
}
override fun pageListParse(response: Response): List<Page> {
return response.parseAs<PagesDto>().images.mapIndexed { index, imageUrl ->
Page(index, imageUrl = "$API_URL//media/$imageUrl")
}
}
// ============================== Utils ===============================
@Serializable
private class SeriesDto(@SerialName("series_code") val code: String)
private fun SManga.createPostPayload(field: String = "code"): RequestBody {
val mangaCode = url.substringAfterLast("/")
return """{"$field": "$mangaCode"}""".toRequestBody("application/json".toMediaType())
}
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromStream(it.body.byteStream())
}
private inline fun <reified T> RequestBody.parseAs(): T =
json.decodeFromString(Buffer().also { writeTo(it) }.readUtf8())
private fun String.toDate() =
try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0 }
companion object {
const val API_URL = "https://api.blackscans.site"
const val PREFIX_SEARCH = "id:"
@SuppressLint("SimpleDateFormat")
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'")
}
}

View File

@ -1,37 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.blackscans
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class BlackScansUrlActivity : Activity() {
private val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val item = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${BlackScans.PREFIX_SEARCH}$item")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e(tag, e.toString())
}
} else {
Log.e(tag, "could not parse uri from intent $intent")
}
finish()
exitProcess(0)
}
}

View File

@ -1,44 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.blackscans
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
@Serializable
class MangaDetailsDto(
val title: String,
val artist: String,
val author: String,
val code: String,
val genres: List<String>,
@SerialName("path_cover")
val cover: String,
val status: String,
val synopsis: String,
)
@Serializable
class MangaDto(
val code: String,
val title: String,
@JsonNames("path_cover")
val cover: String,
)
@Serializable
class ChapterList(
val chapters: List<Chapter>,
)
@Serializable
data class Chapter(
val code: String,
val name: String,
@SerialName("upload_date")
val uploadAt: String,
)
@Serializable
class PagesDto(
val images: List<String>,
)