Add new HeanCms as multisrc theme (#13713)

* Add new HeanCms as new multisrc theme.

* Add missing runner and fix wrong version of RS.
This commit is contained in:
Alessandro Jean 2022-10-04 15:12:30 -03:00 committed by GitHub
parent 370d8f1931
commit 2debfe8a43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 664 additions and 40 deletions

View File

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="HeanCmsGenerator" type="JetRunConfigurationType" nameIsGenerated="true">
<module name="tachiyomi-extensions.multisrc.main" />
<option name="MAIN_CLASS_NAME" value="eu.kanade.tachiyomi.multisrc.heancms.HeanCmsGenerator" />
<method v="2">
<option name="Make" enabled="true" />
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktFormat" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="-Ptheme=heancms" />
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktLint" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="-Ptheme=heancms" />
</method>
</configuration>
</component>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,48 @@
package eu.kanade.tachiyomi.extension.pt.reaperscans
import eu.kanade.tachiyomi.multisrc.heancms.Genre
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
class ReaperScans : HeanCms(
"Reaper Scans",
"https://reaperscans.com.br",
"pt-BR"
) {
override val client: OkHttpClient = super.client.newBuilder()
.rateLimitHost(apiUrl.toHttpUrl(), 1, 2)
.build()
// Site changed from Madara to HeanCms.
override val versionId = 2
override fun getGenreList(): List<Genre> = listOf(
Genre("Artes Marciais", 2),
Genre("Aventura", 10),
Genre("Ação", 9),
Genre("Comédia", 14),
Genre("Drama", 15),
Genre("Escolar", 7),
Genre("Fantasia", 11),
Genre("Ficção científica", 16),
Genre("Guerra", 17),
Genre("Isekai", 18),
Genre("Jogo", 12),
Genre("Mangá", 24),
Genre("Manhua", 23),
Genre("Manhwa", 22),
Genre("Mecha", 19),
Genre("Mistério", 20),
Genre("Nacional", 8),
Genre("Realidade Virtual", 21),
Genre("Retorno", 3),
Genre("Romance", 5),
Genre("Segunda vida", 4),
Genre("Seinen", 1),
Genre("Shounen", 13),
Genre("Terror", 6)
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,61 @@
package eu.kanade.tachiyomi.extension.es.yugenmangas
import eu.kanade.tachiyomi.multisrc.heancms.Genre
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
class YugenMangas : HeanCms("YugenMangas", "https://yugenmangas.com", "es") {
// Site changed from Madara to HeanCms.
override val versionId = 2
override fun getGenreList(): List<Genre> = listOf(
Genre("+18", 1),
Genre("Acción", 36),
Genre("Adulto", 38),
Genre("Apocalíptico", 3),
Genre("Artes marciales (1)", 16),
Genre("Artes marciales (2)", 37),
Genre("Aventura", 2),
Genre("Boys Love", 4),
Genre("Ciencia ficción", 39),
Genre("Comedia", 5),
Genre("Demonios", 6),
Genre("Deporte", 26),
Genre("Drama", 7),
Genre("Ecchi", 8),
Genre("Familia", 9),
Genre("Fantasía", 10),
Genre("Girls Love", 11),
Genre("Gore", 12),
Genre("Harem", 13),
Genre("Harem inverso", 14),
Genre("Histórico", 48),
Genre("Horror", 41),
Genre("Isekai", 40),
Genre("Josei", 15),
Genre("Maduro", 42),
Genre("Magia", 17),
Genre("MangoScan", 35),
Genre("Mecha", 18),
Genre("Militar", 19),
Genre("Misterio", 20),
Genre("Psicológico", 21),
Genre("Realidad virtual", 46),
Genre("Recuentos de la vida", 25),
Genre("Reencarnación", 22),
Genre("Regresion", 23),
Genre("Romance", 24),
Genre("Seinen", 27),
Genre("Shonen", 28),
Genre("Shoujo", 29),
Genre("Sistema", 45),
Genre("Smut", 30),
Genre("Supernatural", 31),
Genre("Supervivencia", 32),
Genre("Tragedia", 33),
Genre("Transmigración", 34),
Genre("Vida Escolar", 47),
Genre("Yaoi", 43),
Genre("Yuri", 44)
)
}

View File

@ -1,11 +1,10 @@
package eu.kanade.tachiyomi.extension.all.yugenmangas package eu.kanade.tachiyomi.extension.pt.yugenmangas
import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.decodeFromString
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -15,34 +14,8 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class YugenMangasFactory : SourceFactory { class YugenMangas : Madara(
override fun createSources() = listOf( "YugenMangas",
YugenMangasEs(),
YugenMangasBr()
)
}
abstract class YugenMangas(
override val baseUrl: String,
lang: String,
dateFormat: SimpleDateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale.US)
) : Madara("YugenMangas", baseUrl, lang, dateFormat) {
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
name = element.selectFirst("p.chapter-manhwa-title")!!.text()
date_upload = parseChapterDate(element.selectFirst("span.chapter-release-date i")?.text())
val chapterUrl = element.selectFirst("a")!!.attr("abs:href")
setUrlWithoutDomain(
chapterUrl.substringBefore("?style=paged") +
if (!chapterUrl.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
)
}
}
class YugenMangasEs : YugenMangas("https://yugenmangas.com", "es")
class YugenMangasBr : YugenMangas(
"https://yugenmangas.com.br", "https://yugenmangas.com.br",
"pt-BR", "pt-BR",
SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")) SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR"))
@ -64,19 +37,26 @@ class YugenMangasBr : YugenMangas(
override val useNewChapterEndpoint: Boolean = true override val useNewChapterEndpoint: Boolean = true
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
name = element.selectFirst("p.chapter-manhwa-title")!!.text()
date_upload = parseChapterDate(element.selectFirst("span.chapter-release-date i")?.text())
val chapterUrl = element.selectFirst("a")!!.attr("abs:href")
setUrlWithoutDomain(
chapterUrl.substringBefore("?style=paged") +
if (!chapterUrl.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
)
}
private var userAgent: String? = null private var userAgent: String? = null
private var checkedUa = false private var checkedUa = false
private fun uaIntercept(chain: Interceptor.Chain): Response { private fun uaIntercept(chain: Interceptor.Chain): Response {
if (userAgent == null && !checkedUa) { if (userAgent == null && !checkedUa) {
val browser = BROWSERS.random() val uaResponse = chain.proceed(GET(UA_DB_URL))
val uaResponse = chain.proceed(GET("$UA_DB_URL/$browser"))
if (uaResponse.isSuccessful) { if (uaResponse.isSuccessful) {
userAgent = uaResponse.asJsoup() userAgent = json.decodeFromString<List<String>>(uaResponse.body!!.string()).random()
.select(".listing-of-useragents span.code")
.firstOrNull()
?.text()
checkedUa = true checkedUa = true
} }
@ -95,7 +75,6 @@ class YugenMangasBr : YugenMangas(
} }
companion object { companion object {
private val BROWSERS = arrayOf("chrome", "firefox", "edge", "opera", "vivaldi") private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
private const val UA_DB_URL = "https://whatismybrowser.com/guides/the-latest-user-agent"
} }
} }

View File

@ -0,0 +1,289 @@
package eu.kanade.tachiyomi.multisrc.heancms
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
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.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
abstract class HeanCms(
override val name: String,
override val baseUrl: String,
override val lang: String,
protected val apiUrl: String = baseUrl.replace("://", "://api.")
) : HttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
protected val json: Json by injectLazy()
protected val intl by lazy { HeanCmsIntl(lang) }
private var seriesSlugMap: Map<String, String>? = null
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int): Request {
val payloadObj = HeanCmsSearchDto(
order = "desc",
orderBy = "total_views",
status = "Ongoing",
type = "Comic"
)
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.add("Content-Type", payload.contentType().toString())
.build()
return POST("$apiUrl/series/querysearch", apiHeaders, payload)
}
override fun popularMangaParse(response: Response): MangasPage {
val mangaList = response.parseAs<List<HeanCmsSeriesDto>>()
.map { it.toSManga(apiUrl) }
fetchAllTitles()
return MangasPage(mangaList, hasNextPage = false)
}
override fun latestUpdatesRequest(page: Int): Request {
val payloadObj = HeanCmsSearchDto(
order = "desc",
orderBy = "latest",
status = "Ongoing",
type = "Comic"
)
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.add("Content-Type", payload.contentType().toString())
.build()
return POST("$apiUrl/series/querysearch", apiHeaders, payload)
}
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val sortByFilter = filters.firstInstanceOrNull<SortByFilter>()
val payloadObj = HeanCmsSearchDto(
order = if (sortByFilter?.state?.ascending == true) "asc" else "desc",
orderBy = sortByFilter?.selected ?: "total_views",
status = filters.firstInstanceOrNull<StatusFilter>()?.selected?.value ?: "Ongoing",
type = "Comic",
tagIds = filters.firstInstanceOrNull<GenreFilter>()?.state
?.filter(Genre::state)
?.map(Genre::id)
.orEmpty()
)
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.add("Content-Type", payload.contentType().toString())
.build()
val apiUrl = "$apiUrl/series/querysearch".toHttpUrl().newBuilder()
.addQueryParameter("q", query)
.toString()
return POST(apiUrl, apiHeaders, payload)
}
override fun searchMangaParse(response: Response): MangasPage {
val query = response.request.url.queryParameter("q").orEmpty()
var mangaList = response.parseAs<List<HeanCmsSeriesDto>>()
.map { it.toSManga(apiUrl) }
if (query.isNotBlank()) {
mangaList = mangaList.filter { it.title.contains(query, ignoreCase = true) }
}
fetchAllTitles()
return MangasPage(mangaList, hasNextPage = false)
}
// Workaround to allow "Open in browser" use the real URL.
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(seriesDetailsRequest(manga))
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
private fun seriesDetailsRequest(manga: SManga): Request {
val seriesSlug = manga.url
.substringAfterLast("/")
.replace(TIMESTAMP_REGEX, "")
val currentSlug = seriesSlugMap?.get(seriesSlug) ?: seriesSlug
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.build()
return GET("$apiUrl/series/$currentSlug#${manga.status}", apiHeaders)
}
override fun mangaDetailsParse(response: Response): SManga {
val result = runCatching { response.parseAs<HeanCmsSeriesDto>() }
val seriesDetails = result.getOrNull()?.toSManga(apiUrl)
?: throw Exception(intl.urlChangedError(name))
return seriesDetails.apply {
status = response.request.url.fragment?.toIntOrNull() ?: SManga.UNKNOWN
}
}
override fun chapterListRequest(manga: SManga): Request = seriesDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<HeanCmsSeriesDto>()
val seriesSlug = response.request.url.pathSegments.last()
return result.chapters.orEmpty()
.map { it.toSChapter(seriesSlug) }
.reversed()
}
override fun pageListRequest(chapter: SChapter): Request {
val chapterId = chapter.url.substringAfterLast("#")
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.build()
return GET("$apiUrl/series/chapter/$chapterId", apiHeaders)
}
override fun pageListParse(response: Response): List<Page> {
return response.parseAs<HeanCmsReaderDto>().content?.images.orEmpty()
.mapIndexed { i, url -> Page(i, "", "$apiUrl/$url") }
}
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
override fun imageUrlParse(response: Response): String = ""
override fun imageRequest(page: Page): Request {
val imageHeaders = headersBuilder()
.add("Accept", ACCEPT_IMAGE)
.build()
return GET(page.imageUrl!!, imageHeaders)
}
protected open fun getStatusList(): List<Status> = listOf(
Status(intl.statusOngoing, "Ongoing"),
Status(intl.statusOnHiatus, "Hiatus"),
Status(intl.statusDropped, "Dropped"),
)
protected open fun getSortProperties(): List<SortProperty> = listOf(
SortProperty(intl.sortByTitle, "title"),
SortProperty(intl.sortByViews, "total_views"),
SortProperty(intl.sortByLatest, "latest"),
SortProperty(intl.sortByRecentlyAdded, "recently_added"),
)
protected open fun getGenreList(): List<Genre> = emptyList()
protected open fun fetchAllTitles() {
if (!seriesSlugMap.isNullOrEmpty()) {
return
}
val result = runCatching {
client.newCall(allTitlesRequest()).execute()
.let { parseAllTitles(it) }
}
seriesSlugMap = result.getOrNull()
}
protected open fun allTitlesRequest(): Request {
val payloadObj = HeanCmsSearchDto(
order = "desc",
orderBy = "total_views",
status = "",
type = "Comic"
)
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.add("Content-Type", payload.contentType().toString())
.build()
return POST("$apiUrl/series/querysearch", apiHeaders, payload)
}
protected open fun parseAllTitles(response: Response): Map<String, String> {
return response.parseAs<List<HeanCmsSeriesDto>>()
.filter { it.type == "Comic" }
.associateBy(
keySelector = { it.slug.replace(TIMESTAMP_REGEX, "") },
valueTransform = HeanCmsSeriesDto::slug
)
}
override fun getFilterList(): FilterList {
val genres = getGenreList()
val filters = listOfNotNull(
StatusFilter(intl.statusFilterTitle, getStatusList()),
SortByFilter(intl.sortByFilterTitle, getSortProperties()),
GenreFilter(intl.genreFilterTitle, genres).takeIf { genres.isNotEmpty() }
)
return FilterList(filters)
}
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromString(it.body?.string().orEmpty())
}
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
filterIsInstance<R>().firstOrNull()
companion object {
private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
private const val ACCEPT_JSON = "application/json, text/plain, */*"
private val JSON_MEDIA_TYPE = "application/json".toMediaType()
val TIMESTAMP_REGEX = "-\\d+$".toRegex()
}
}

View File

@ -0,0 +1,93 @@
package eu.kanade.tachiyomi.multisrc.heancms
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import java.text.SimpleDateFormat
import java.util.Locale
@Serializable
data class HeanCmsSeriesDto(
val id: Int,
@SerialName("series_slug") val slug: String,
@SerialName("series_type") val type: String = "Comic",
val author: String? = null,
val description: String? = null,
val studio: String? = null,
val status: String? = null,
val thumbnail: String,
val title: String,
val tags: List<HeanCmsTagDto>? = emptyList(),
val chapters: List<HeanCmsChapterDto>? = emptyList()
) {
fun toSManga(apiUrl: String): SManga = SManga.create().apply {
val descriptionBody = this@HeanCmsSeriesDto.description?.let(Jsoup::parseBodyFragment)
title = this@HeanCmsSeriesDto.title
author = this@HeanCmsSeriesDto.author?.trim()
artist = this@HeanCmsSeriesDto.studio?.trim()
description = descriptionBody?.select("p")
?.joinToString("\n\n") { it.text() }
?.ifEmpty { descriptionBody.text().replace("\n", "\n\n") }
genre = tags.orEmpty()
.sortedBy(HeanCmsTagDto::name)
.joinToString { it.name }
thumbnail_url = "$apiUrl/cover/$thumbnail"
status = when (this@HeanCmsSeriesDto.status) {
"Ongoing" -> SManga.ONGOING
"Hiatus" -> SManga.ON_HIATUS
"Dropped" -> SManga.CANCELLED
"Completed", "Finished" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
url = "/series/${slug.replace(HeanCms.TIMESTAMP_REGEX, "")}"
}
}
@Serializable
data class HeanCmsTagDto(val name: String)
@Serializable
data class HeanCmsChapterDto(
val id: Int,
@SerialName("chapter_name") val name: String,
@SerialName("chapter_slug") val slug: String,
val index: String,
@SerialName("created_at") val createdAt: String,
) {
fun toSChapter(seriesSlug: String): SChapter = SChapter.create().apply {
name = this@HeanCmsChapterDto.name.trim()
date_upload = runCatching { DATE_FORMAT.parse(createdAt.substringBefore("."))?.time }
.getOrNull() ?: 0L
url = "/series/$seriesSlug/$slug#$id"
}
companion object {
private val DATE_FORMAT by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
}
}
}
@Serializable
data class HeanCmsReaderDto(
val content: HeanCmsReaderContentDto? = null
)
@Serializable
data class HeanCmsReaderContentDto(
val images: List<String>? = emptyList()
)
@Serializable
data class HeanCmsSearchDto(
val order: String,
@SerialName("order_by") val orderBy: String,
@SerialName("series_status") val status: String,
@SerialName("series_type") val type: String,
@SerialName("tags_ids") val tagIds: List<Int> = emptyList()
)

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.multisrc.heancms
import eu.kanade.tachiyomi.source.model.Filter
class Genre(title: String, val id: Int) : Filter.CheckBox(title)
class GenreFilter(title: String, genres: List<Genre>) : Filter.Group<Genre>(title, genres)
open class EnhancedSelect<T>(name: String, values: Array<T>) : Filter.Select<T>(name, values) {
val selected: T
get() = values[state]
}
data class Status(val name: String, val value: String) {
override fun toString(): String = name
}
class StatusFilter(title: String, statuses: List<Status>) :
EnhancedSelect<Status>(title, statuses.toTypedArray())
data class SortProperty(val name: String, val value: String) {
override fun toString(): String = name
}
class SortByFilter(title: String, private val sortProperties: List<SortProperty>) : Filter.Sort(
title,
sortProperties.map { it.name }.toTypedArray(),
Selection(1, ascending = false)
) {
val selected: String
get() = sortProperties[state!!.index].value
}

View File

@ -0,0 +1,25 @@
package eu.kanade.tachiyomi.multisrc.heancms
import generator.ThemeSourceData.SingleLang
import generator.ThemeSourceGenerator
class HeanCmsGenerator : ThemeSourceGenerator {
override val themePkg = "heancms"
override val themeClass = "HeanCms"
override val baseVersionCode: Int = 1
override val sources = listOf(
SingleLang("Reaper Scans", "https://reaperscans.com.br", "pt-BR", overrideVersionCode = 33),
SingleLang("YugenMangas", "https://yugenmangas.com", "es", isNsfw = true),
)
companion object {
@JvmStatic
fun main(args: Array<String>) {
HeanCmsGenerator().createAll()
}
}
}

View File

@ -0,0 +1,86 @@
package eu.kanade.tachiyomi.multisrc.heancms
class HeanCmsIntl(lang: String) {
val availableLang: String = if (lang in AVAILABLE_LANGS) lang else ENGLISH
val genreFilterTitle: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Gêneros"
SPANISH -> "Géneros"
else -> "Genres"
}
val statusFilterTitle: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Estado"
SPANISH -> "Estado"
else -> "Status"
}
val statusOngoing: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Em andamento"
SPANISH -> "En curso"
else -> "Ongoing"
}
val statusOnHiatus: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Em hiato"
SPANISH -> "En hiatus"
else -> "Ongoing"
}
val statusDropped: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Cancelada"
SPANISH -> "Abandonada"
else -> "Dropped"
}
val sortByFilterTitle: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Ordenar por"
SPANISH -> "Ordenar por"
else -> "Sort by"
}
val sortByTitle: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Título"
SPANISH -> "Titulo"
else -> "Title"
}
val sortByViews: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Visualizações"
SPANISH -> "Número de vistas"
else -> "Views"
}
val sortByLatest: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Recentes"
SPANISH -> "Recientes"
else -> "Latest"
}
val sortByRecentlyAdded: String = when (availableLang) {
BRAZILIAN_PORTUGUESE -> "Data de criação"
SPANISH -> "Añadido recientemente"
else -> "Recently added"
}
fun urlChangedError(sourceName: String): String = when (availableLang) {
BRAZILIAN_PORTUGUESE ->
"A URL da série mudou. Migre de $sourceName " +
"para $sourceName para atualizar a URL."
SPANISH ->
"La URL de la serie ha cambiado. Migre de $sourceName a " +
"$sourceName para actualizar la URL."
else ->
"The URL of the series has changed. Migrate from $sourceName " +
"to $sourceName to update the URL."
}
companion object {
const val BRAZILIAN_PORTUGUESE = "pt-BR"
const val ENGLISH = "en"
const val SPANISH = "es"
val AVAILABLE_LANGS = arrayOf(BRAZILIAN_PORTUGUESE, ENGLISH, SPANISH)
}
}

View File

@ -19,7 +19,6 @@ class MadaraGenerator : ThemeSourceGenerator {
MultiLang("Olympus Scanlation", "https://olympusscanlation.com", listOf("es", "pt-BR")), MultiLang("Olympus Scanlation", "https://olympusscanlation.com", listOf("es", "pt-BR")),
MultiLang("Reaper Scans", "https://reaperscans.com", listOf("en", "fr", "id", "tr"), className = "ReaperScansFactory", pkgName = "reaperscans", overrideVersionCode = 7), MultiLang("Reaper Scans", "https://reaperscans.com", listOf("en", "fr", "id", "tr"), className = "ReaperScansFactory", pkgName = "reaperscans", overrideVersionCode = 7),
MultiLang("Seven King Scanlation", "https://sksubs.net", listOf("es", "en"), isNsfw = true), MultiLang("Seven King Scanlation", "https://sksubs.net", listOf("es", "en"), isNsfw = true),
MultiLang("YugenMangas", "https://yugenmangas.com", listOf("es", "pt-BR"), overrideVersionCode = 3),
SingleLang("1st Kiss Manga.love", "https://1stkissmanga.love", "en", className = "FirstKissMangaLove", overrideVersionCode = 1), SingleLang("1st Kiss Manga.love", "https://1stkissmanga.love", "en", className = "FirstKissMangaLove", overrideVersionCode = 1),
SingleLang("1st Kiss Manhua", "https://1stkissmanhua.com", "en", className = "FirstKissManhua", overrideVersionCode = 3), SingleLang("1st Kiss Manhua", "https://1stkissmanhua.com", "en", className = "FirstKissManhua", overrideVersionCode = 3),
SingleLang("1st Kiss", "https://1stkissmanga.io", "en", className = "FirstKissManga", pkgName = "firstkissmanga", overrideVersionCode = 7), SingleLang("1st Kiss", "https://1stkissmanga.io", "en", className = "FirstKissManga", pkgName = "firstkissmanga", overrideVersionCode = 7),
@ -486,6 +485,7 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("YaoiToon", "https://yaoitoon.com", "en", isNsfw = true), SingleLang("YaoiToon", "https://yaoitoon.com", "en", isNsfw = true),
SingleLang("Yetişkin Rüya Manga", "https://yetiskin.ruyamanga.com", "tr", isNsfw = true, className = "YetiskinRuyaManga"), SingleLang("Yetişkin Rüya Manga", "https://yetiskin.ruyamanga.com", "tr", isNsfw = true, className = "YetiskinRuyaManga"),
SingleLang("YonaBar", "https://yonabar.com", "ar", isNsfw = true, overrideVersionCode = 2), SingleLang("YonaBar", "https://yonabar.com", "ar", isNsfw = true, overrideVersionCode = 2),
SingleLang("YugenMangas", "https://yugenmangas.com.br", "pt-BR"),
SingleLang("Yuri Verso", "https://yuri.live", "pt-BR", overrideVersionCode = 3), SingleLang("Yuri Verso", "https://yuri.live", "pt-BR", overrideVersionCode = 3),
SingleLang("Zinmanga", "https://zinmanga.com", "en", overrideVersionCode = 1), SingleLang("Zinmanga", "https://zinmanga.com", "en", overrideVersionCode = 1),
SingleLang("Zinmanhwa", "https://zinmanhwa.com", "en"), SingleLang("Zinmanhwa", "https://zinmanhwa.com", "en"),