Compare commits

...

36 Commits

Author SHA1 Message Date
Vetle Ledaal 480cb9d780 Remove Muctau (#2182)
CI / Prepare job (push) Successful in 5s Details
CI / Build individual modules (push) Successful in 6m9s Details
CI / Publish repo (push) Successful in 47s Details
2024-03-30 22:04:18 +00:00
Vetle Ledaal 96a3e3c8e4 HentaiMode: update browse selector (#2181) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 2a2d4ed46d Remove One Piece Ex (#2179) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 0bbc41ee11 Remove MangaRosie (#2178) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 79bd3dbcca Plot Twist No Fansub: fix browse and chapter list (#2176) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 6a8ab8c12e MonarcaManga: update domain (#2174)
* MonarcaManga: update domain

* Rename to Visormonarca
2024-03-30 22:04:18 +00:00
Vetle Ledaal 908373a885 Hentai2Read: fix cover (#2175) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 57e3fdfe6a Mangago: support external chapters (#2171) 2024-03-30 22:04:18 +00:00
Secozzi ec8c080c75 Add edscanlation (#2162) 2024-03-30 22:04:18 +00:00
Secozzi 8f16b6c06d Add anteikuscan (#2161) 2024-03-30 22:04:18 +00:00
Secozzi 2455baa236 Add kewnscans (#2160) 2024-03-30 22:04:18 +00:00
Secozzi 6b63da2979 Move ezmanga to keyoapp theme (#2159) 2024-03-30 22:04:18 +00:00
Chopper 06b5579243 Add UnionMangas (#2091)
* Add latestUpdates, popularManga and mangaDetails to UnionMangas

* Add initial chapter request configuration

* Fix client headers request

* Add fetch chapter

* Fix chapter list parse

* Add search impl

* Fix chapter url

* Rename hash function

* Add utils functions

* Add pageList

* typo

* Cleanup.

* Add intent query

* Refactoring

* Remove password hardcode

* Replace Exception by RuntimeException

* throws message exception to user

* Add rateLimit

* Add Pageable class

* Cleanup

* Remove unicodes

* Remove nullable dto properties

* Rename variables

* Replace 'data class' with regular 'class'

* Remove try/catch. Let exceptions be thrown

* Fix search request

* Cleanup

---------

Co-authored-by: bapeey <90949336+bapeey@users.noreply.github.com>
2024-03-30 22:04:18 +00:00
renovate[bot] ddd978f27e Update dependency gradle to v8.7 (#2077)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-30 22:04:18 +00:00
bapeey 3dc97aaff8 Add EternalMangas and move MangaEsp to multisrc (#2127)
* I made this on termux

* Fix regex and move dateFormat to DTO

* Phase 1

* Phase 2: Prepare for intl

* Phase 3: Add intl
Builds are faster on my phone T.T

* Apply suggestions from code review

* bump
2024-03-30 22:04:18 +00:00
Cuong M. Tran 9637963a6c VyvyManga: add filters & update domain (#2150)
* Vyvymanga: implemented new filters for advanced search

# Conflicts:
#	src/en/vyvymanga/src/eu/kanade/tachiyomi/extension/en/vyvymanga/VyvyManga.kt

* Move filters to separated file

* Fetch online genres

* Update domain, bump version

* revert unnecessary change

---------

Co-authored-by: Ota Takushima <ota.takushima@tum.de>
2024-03-30 22:04:18 +00:00
Vetle Ledaal 02f70b000d Remove MangaJar (#2148) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 7172b0567e Zinmanga: update domain (#2147) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 613a2c5f50 Mangaku: update domain (#2146) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 243a6e8d3f StoneScape: hide premium chapters (#2140) 2024-03-30 22:04:18 +00:00
Coin 32a79b4a89 Happymh: Add referer header for image requests (#2135) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 937843c751 Mangahub (ru): fix popular, latest, pages, author (#2114)
* Mangahub (ru): fix popular, latest, pages, author

* only check form in text/html responses
2024-03-30 22:04:18 +00:00
Barrell Titor 01de950ce9 Comic Extra - Update to new URL and page selector (#2132)
* Update ComicExtra.kt

Changed base URL to new one

* Updated extVersionCode in build.gradle

* Updated page list selector to the new one

* ComicExtra - fix search

* Changed to addQueryParameter and added pagination

* Imports

* lint
2024-03-30 22:04:18 +00:00
KirinRaikage 471e4d3190 Cleanup dead sources (#2121) 2024-03-30 22:04:18 +00:00
bapeey 142b22b440 Mangas.in: Fix chapter decrypt (#2120)
Smh
2024-03-30 22:04:18 +00:00
Vetle Ledaal 3bb82737b7 Remove Colored Manga (#2112) 2024-03-30 22:04:18 +00:00
Vetle Ledaal da0b929bf3 Remove GMANGA (gmanga.org) (#2111) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 2d316661f4 Remove Manganelo related sites (#2110) 2024-03-30 22:04:18 +00:00
Vetle Ledaal d586621f6e Remove TruyenTranhLH (#2109) 2024-03-30 22:04:18 +00:00
Vetle Ledaal 31ac8a4156 Remove WayManga (#2108) 2024-03-30 22:04:18 +00:00
AwkwardPeak7 8fd440d838 PeachScans: Bump ImageDecoder and fix for new signature (#2078)
* Bump ImageDecoder and fix for new signature

* fix for empty svg files
2024-03-30 22:04:18 +00:00
Guuza 78f2c9c650 Hentai2Read: fix broken covers (#2085)
Hentai2Read: fix broken covers (#1776)
2024-03-30 22:04:18 +00:00
Vetle Ledaal 23815a1ee1 YaoiLib: fix UrlActivity (#2082) 2024-03-30 22:04:18 +00:00
Vetle Ledaal bc12176199 Mangasusu: fix page parser (#2080) 2024-03-30 22:04:18 +00:00
bapeey a176c34f73 HeanCMS: Add login preference (#2071)
* Add login

* Bump

* Remove authHeaders from imageRequest

* Make token nullable

* Use /login api endpoint

* Review changes

* Throw error

* Throw api error message

* Reduce one day to prevent timezone issues

* Fix no scheme found

* Double parenthesis
2024-03-30 22:04:18 +00:00
Cuong M. Tran 756f3c63ea NhatTruyen/NetTruyen: new advanced search (#2012)
* NhatTruyen/NetTruyen: new advanced search

* simplify filters
2024-03-30 22:04:18 +00:00
187 changed files with 1751 additions and 1497 deletions

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -13,5 +13,9 @@ pref_show_paid_chapter_title=Display paid chapters
pref_show_paid_chapter_summary_on=Paid chapters will appear.
pref_show_paid_chapter_summary_off=Only free chapters will be displayed.
url_changed_error=The URL of the series has changed. Migrate from %s to %s to update the URL
pref_username_title=Username/Email
pref_password_title=Password
pref_credentials_summary=Ignored if empty.
login_failed_unknown_error=Unknown error occurred while logging in
paid_chapter_error=Paid chapter unavailable.
id_not_found_error=Failed to get the ID for slug: %s

View File

@ -2,7 +2,7 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 21
baseVersionCode = 22
dependencies {
api(project(":lib:i18n"))

View File

@ -2,10 +2,12 @@ package eu.kanade.tachiyomi.multisrc.heancms
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -14,7 +16,9 @@ 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.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
@ -43,6 +47,8 @@ abstract class HeanCms(
protected open val useNewChapterEndpoint = false
protected open val enableLogin = false
/**
* Custom Json instance to make usage of `encodeDefaults`,
* which is not enabled on the injected instance of the app.
@ -70,6 +76,44 @@ abstract class HeanCms(
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
private fun authHeaders(): Headers {
val builder = headersBuilder()
if (enableLogin && preferences.user.isNotEmpty() && preferences.password.isNotEmpty()) {
val tokenData = preferences.tokenData
val token = if (tokenData.isExpired(tokenExpiredAtDateFormat)) {
getToken()
} else {
tokenData.token
}
if (token != null) {
builder.add("Authorization", "Bearer $token")
}
}
return builder.build()
}
private fun getToken(): String? {
val body = FormBody.Builder()
.add("email", preferences.user)
.add("password", preferences.password)
.build()
val response = client.newCall(POST("$apiUrl/login", headers, body)).execute()
if (!response.isSuccessful) {
val result = response.parseAs<HeanCmsErrorsDto>()
val message = result.errors?.firstOrNull()?.message ?: intl["login_failed_unknown_error"]
throw Exception(message)
}
val result = response.parseAs<HeanCmsTokenPayloadDto>()
preferences.tokenData = result
return result.token
}
override fun popularMangaRequest(page: Int): Request {
val url = "$apiUrl/query".toHttpUrl().newBuilder()
.addQueryParameter("query_string", "")
@ -277,24 +321,30 @@ abstract class HeanCms(
override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBeforeLast("#")
override fun pageListRequest(chapter: SChapter) =
GET(apiUrl + chapter.url.replace("/$mangaSubDirectory/", "/chapter/"), headers)
GET(apiUrl + chapter.url.replace("/$mangaSubDirectory/", "/chapter/"), authHeaders())
override fun pageListParse(response: Response): List<Page> {
val result = response.parseAs<HeanCmsPagePayloadDto>()
if (result.isPaywalled()) throw Exception(intl["paid_chapter_error"])
if (result.isPaywalled() && result.chapter.chapterData == null) {
throw Exception(intl["paid_chapter_error"])
}
return if (useNewChapterEndpoint) {
result.chapter.chapterData?.images.orEmpty().mapIndexed { i, img ->
Page(i, imageUrl = img)
Page(i, imageUrl = img.toAbsoluteUrl())
}
} else {
result.data.orEmpty().mapIndexed { i, img ->
Page(i, imageUrl = img)
Page(i, imageUrl = img.toAbsoluteUrl())
}
}
}
private fun String.toAbsoluteUrl(): String {
return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$this"
}
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
override fun imageUrlParse(response: Response): String = ""
@ -343,6 +393,32 @@ abstract class HeanCms(
summaryOff = intl["pref_show_paid_chapter_summary_off"]
setDefaultValue(SHOW_PAID_CHAPTERS_DEFAULT)
}.also(screen::addPreference)
if (enableLogin) {
EditTextPreference(screen.context).apply {
key = USER_PREF
title = intl["pref_username_title"]
summary = intl["pref_credentials_summary"]
setDefaultValue("")
setOnPreferenceChangeListener { _, _ ->
preferences.tokenData = HeanCmsTokenPayloadDto()
true
}
}.also(screen::addPreference)
EditTextPreference(screen.context).apply {
key = PASSWORD_PREF
title = intl["pref_password_title"]
summary = intl["pref_credentials_summary"]
setDefaultValue("")
setOnPreferenceChangeListener { _, _ ->
preferences.tokenData = HeanCmsTokenPayloadDto()
true
}
}.also(screen::addPreference)
}
}
protected inline fun <reified T> Response.parseAs(): T = use {
@ -357,6 +433,21 @@ abstract class HeanCms(
private val SharedPreferences.showPaidChapters: Boolean
get() = getBoolean(SHOW_PAID_CHAPTERS_PREF, SHOW_PAID_CHAPTERS_DEFAULT)
private val SharedPreferences.user: String
get() = getString(USER_PREF, "") ?: ""
private val SharedPreferences.password: String
get() = getString(PASSWORD_PREF, "") ?: ""
private var SharedPreferences.tokenData: HeanCmsTokenPayloadDto
get() {
val jsonString = getString(TOKEN_PREF, "{}")!!
return json.decodeFromString(jsonString)
}
set(data) {
edit().putString(TOKEN_PREF, json.encodeToString(data)).apply()
}
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, */*"
@ -367,5 +458,12 @@ abstract class HeanCms(
private const val SHOW_PAID_CHAPTERS_PREF = "pref_show_paid_chap"
private const val SHOW_PAID_CHAPTERS_DEFAULT = false
private const val USER_PREF = "pref_user"
private const val PASSWORD_PREF = "pref_password"
private const val TOKEN_PREF = "pref_token"
private val tokenExpiredAtDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
}
}

View File

@ -7,6 +7,33 @@ import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import java.text.SimpleDateFormat
@Serializable
class HeanCmsTokenPayloadDto(
val token: String? = null,
private val expiresAt: String? = null,
) {
fun isExpired(dateFormat: SimpleDateFormat): Boolean {
val expiredTime = try {
// Reduce one day to prevent timezone issues
expiresAt?.let { dateFormat.parse(it)?.time?.minus(1000 * 60 * 60 * 24) } ?: 0L
} catch (_: Exception) {
0L
}
return System.currentTimeMillis() > expiredTime
}
}
@Serializable
class HeanCmsErrorsDto(
val errors: List<HeanCmsErrorMessageDto>? = emptyList(),
)
@Serializable
class HeanCmsErrorMessageDto(
val message: String,
)
@Serializable
class HeanCmsQuerySearchDto(
val data: List<HeanCmsSeriesDto> = emptyList(),
@ -129,7 +156,7 @@ class HeanCmsPageDataDto(
)
private fun String.toAbsoluteThumbnailUrl(apiUrl: String, coverPath: String): String {
return if (startsWith("https://")) this else "$apiUrl/$coverPath$this"
return if (startsWith("https://") || startsWith("http://")) this else "$apiUrl/$coverPath$this"
}
fun String.toStatus(): Int = when (this) {

View File

@ -0,0 +1,13 @@
search_length_error=The query must have at least 2 characters
comics_list_error=Comics list not found
comic_data_error=Comic data not found
sort_by_filter_title=Sort by
sort_by_filter_name=Name
sort_by_filter_views=Views
sort_by_filter_updated=Updated
sort_by_filter_added=Added
status_filter_title=Status
status_filter_ongoing=Ongoing
status_filter_hiatus=Hiatus
status_filter_dropped=Dropped
status_filter_completed=Completed

View File

@ -0,0 +1,13 @@
search_length_error=La búsqueda debe tener al menos 2 caracteres
comics_list_error=No se pudo encontrar la lista de comics
comic_data_error=No se pudo encontrar los datos del comic
sort_by_filter_title=Ordenar por
sort_by_filter_name=Nombre
sort_by_filter_views=Vistas
sort_by_filter_updated=Actualización
sort_by_filter_added=Agregado
status_filter_title=Estado
status_filter_ongoing=En curso
status_filter_hiatus=En pausa
status_filter_dropped=Abandonado
status_filter_completed=Finalizado

View File

@ -0,0 +1,9 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 1
dependencies {
api(project(":lib:i18n"))
}

View File

@ -0,0 +1,248 @@
package eu.kanade.tachiyomi.multisrc.mangaesp
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.Filter
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 eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
import kotlin.math.min
abstract class MangaEsp(
override val name: String,
override val baseUrl: String,
override val lang: String,
protected val apiBaseUrl: String = baseUrl.replace("://", "://apis."),
) : HttpSource() {
override val supportsLatest = true
protected val json: Json by injectLazy()
protected val intl = Intl(
language = lang,
baseLanguage = "en",
availableLanguages = setOf("en", "es"),
classLoader = this::class.java.classLoader!!,
)
override val client: OkHttpClient = network.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 2)
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "$baseUrl/")
override fun popularMangaRequest(page: Int): Request = GET("$apiBaseUrl/api/topSerie", headers)
override fun popularMangaParse(response: Response): MangasPage {
val responseData = json.decodeFromString<TopSeriesDto>(response.body.string())
val topDaily = responseData.response.topDaily.flatten().map { it.data }
val topWeekly = responseData.response.topWeekly.flatten().map { it.data }
val topMonthly = responseData.response.topMonthly.flatten().map { it.data }
val mangas = (topDaily + topWeekly + topMonthly).distinctBy { it.slug }.map { it.toSManga() }
return MangasPage(mangas, false)
}
override fun latestUpdatesRequest(page: Int): Request = GET("$apiBaseUrl/api/lastUpdates", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val responseData = json.decodeFromString<LastUpdatesDto>(response.body.string())
val mangas = responseData.response.map { it.toSManga() }
return MangasPage(mangas, false)
}
private var comicsList = mutableListOf<SeriesDto>()
override fun fetchSearchManga(
page: Int,
query: String,
filters: FilterList,
): Observable<MangasPage> {
return if (comicsList.isEmpty()) {
client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { searchMangaParse(it, page, query, filters) }
} else {
Observable.just(parseComicsList(page, query, filters))
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comics", headers)
override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException()
private fun searchMangaParse(response: Response, page: Int, query: String, filters: FilterList): MangasPage {
val document = response.asJsoup()
val script = document.select("script:containsData(self.__next_f.push)").joinToString { it.data() }
val jsonString = MANGA_LIST_REGEX.find(script)?.groupValues?.get(1)
?: throw Exception(intl["comics_list_error"])
val unescapedJson = jsonString.unescape()
comicsList = json.decodeFromString<List<SeriesDto>>(unescapedJson).toMutableList()
return parseComicsList(page, query, filters)
}
private var filteredList = mutableListOf<SeriesDto>()
private fun parseComicsList(page: Int, query: String, filterList: FilterList): MangasPage {
if (page == 1) {
filteredList.clear()
if (query.isNotBlank()) {
if (query.length < 2) throw Exception(intl["search_length_error"])
filteredList.addAll(
comicsList.filter {
it.name.contains(query, ignoreCase = true) || it.alternativeName?.contains(query, ignoreCase = true) == true
},
)
} else {
filteredList.addAll(comicsList)
}
val statusFilter = filterList.firstInstanceOrNull<StatusFilter>()
if (statusFilter != null) {
filteredList = filteredList.filter { it.status == statusFilter.toUriPart() }.toMutableList()
}
val sortByFilter = filterList.firstInstanceOrNull<SortByFilter>()
if (sortByFilter != null) {
if (sortByFilter.state?.ascending == true) {
when (sortByFilter.selected) {
"name" -> filteredList.sortBy { it.name }
"views" -> filteredList.sortBy { it.trending?.views }
"updated_at" -> filteredList.sortBy { it.lastChapterDate }
"created_at" -> filteredList.sortBy { it.createdAt }
}
} else {
when (sortByFilter.selected) {
"name" -> filteredList.sortByDescending { it.name }
"views" -> filteredList.sortByDescending { it.trending?.views }
"updated_at" -> filteredList.sortByDescending { it.lastChapterDate }
"created_at" -> filteredList.sortByDescending { it.createdAt }
}
}
}
}
val hasNextPage = filteredList.size > page * MANGAS_PER_PAGE
return MangasPage(
filteredList.subList((page - 1) * MANGAS_PER_PAGE, min(page * MANGAS_PER_PAGE, filteredList.size))
.map { it.toSManga() },
hasNextPage,
)
}
override fun mangaDetailsParse(response: Response): SManga {
val responseBody = response.body.string()
val mangaDetailsJson = MANGA_DETAILS_REGEX.find(responseBody)?.groupValues?.get(1)
?: throw Exception(intl["comic_data_error"])
val unescapedJson = mangaDetailsJson.unescape()
return json.decodeFromString<SeriesDto>(unescapedJson).toSMangaDetails()
}
override fun chapterListParse(response: Response): List<SChapter> {
val responseBody = response.body.string()
val mangaDetailsJson = MANGA_DETAILS_REGEX.find(responseBody)?.groupValues?.get(1)
?: throw Exception(intl["comic_data_error"])
val unescapedJson = mangaDetailsJson.unescape()
val series = json.decodeFromString<SeriesDto>(unescapedJson)
return series.chapters.map { it.toSChapter(series.slug) }
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return document.select("main.contenedor.read img").mapIndexed { i, img ->
Page(i, imageUrl = img.imgAttr())
}
}
override fun getFilterList() = FilterList(
SortByFilter(intl["sort_by_filter_title"], getSortProperties()),
StatusFilter(intl["status_filter_title"], getStatusList()),
)
protected open fun getSortProperties(): List<SortProperty> = listOf(
SortProperty(intl["sort_by_filter_name"], "name"),
SortProperty(intl["sort_by_filter_views"], "views"),
SortProperty(intl["sort_by_filter_updated"], "updated_at"),
SortProperty(intl["sort_by_filter_added"], "created_at"),
)
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(2, ascending = false),
) {
val selected: String
get() = sortProperties[state!!.index].value
}
private class StatusFilter(title: String, statusList: Array<Pair<String, Int>>) : UriPartFilter(
title,
statusList,
)
protected open fun getStatusList() = arrayOf(
Pair(intl["status_filter_ongoing"], 1),
Pair(intl["status_filter_hiatus"], 2),
Pair(intl["status_filter_dropped"], 3),
Pair(intl["status_filter_completed"], 4),
)
private open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, Int>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
filterIsInstance<R>().firstOrNull()
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
private fun Element.imgAttr(): String = when {
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("data-src") -> attr("abs:data-src")
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
else -> attr("abs:src")
}
private fun String.unescape(): String {
return UNESCAPE_REGEX.replace(this, "$1")
}
companion object {
private val UNESCAPE_REGEX = """\\(.)""".toRegex()
private val MANGA_LIST_REGEX = """self\.__next_f\.push\(.*data\\":(\[.*trending.*])\}""".toRegex()
private val MANGA_DETAILS_REGEX = """self\.__next_f\.push\(.*data\\":(\{.*lastChapters.*\}).*\\"numFollow""".toRegex()
private const val MANGAS_PER_PAGE = 15
}
}

View File

@ -1,10 +1,11 @@
package eu.kanade.tachiyomi.extension.es.mangaesp
package eu.kanade.tachiyomi.multisrc.mangaesp
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.text.SimpleDateFormat
import java.util.Locale
@Serializable
class TopSeriesDto(
@ -101,18 +102,22 @@ class ChapterDto(
private val slug: String,
@SerialName("created_at") private val date: String,
) {
fun toSChapter(seriesSlug: String, dateFormat: SimpleDateFormat): SChapter {
fun toSChapter(seriesSlug: String): SChapter {
return SChapter.create().apply {
name = "Capítulo ${number.toString().removeSuffix(".0")}"
if (!this@ChapterDto.name.isNullOrBlank()) {
name += " - ${this@ChapterDto.name}"
}
date_upload = try {
dateFormat.parse(date)?.time ?: 0L
DATE_FORMATTER.parse(date)?.time ?: 0L
} catch (e: Exception) {
0L
}
url = "/ver/$seriesSlug/$slug"
}
}
companion object {
private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) }
}
}

View File

@ -2,8 +2,8 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 3
baseVersionCode = 4
dependencies {
compileOnly("com.github.tachiyomiorg:image-decoder:398d3c074f")
compileOnly("com.github.tachiyomiorg:image-decoder:e08e9be535")
}

View File

@ -32,7 +32,6 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
@ -183,7 +182,7 @@ abstract class PeachScan(
val zis = ZipInputStream(response.body.byteStream())
val images = generateSequence { zis.nextEntry }
.map {
.mapNotNull {
val entryName = it.name
val splitEntryName = entryName.split('.')
val entryIndex = splitEntryName.first().toInt()
@ -195,7 +194,7 @@ abstract class PeachScan(
val svgBytes = zis.readBytes()
val svgContent = svgBytes.toString(Charsets.UTF_8)
val b64 = dataUriRegex.find(svgContent)?.groupValues?.get(1)
?: throw IOException("Não foi possível corresponder a imagem no conteúdo SVG")
?: return@mapNotNull null
Base64.decode(b64, Base64.DEFAULT)
}

View File

@ -5,6 +5,7 @@ import android.graphics.Rect
import tachiyomi.decoder.ImageDecoder
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.lang.reflect.Method
/**
@ -18,40 +19,98 @@ import java.lang.reflect.Method
*/
object PeachScanUtils {
private var decodeMethod: Method
private var newInstanceMethod: Method
private var isNewDecodeMethod = false
private var classSignature = ClassSignature.Newest
private enum class ClassSignature {
Old, New, Newest
}
init {
val rectClass = Rect::class.java
val booleanClass = Boolean::class.java
val intClass = Int::class.java
val byteArrayClass = ByteArray::class.java
val inputStreamClass = InputStream::class.java
decodeMethod = try {
isNewDecodeMethod = true
try {
// Mihon Preview r6595+
classSignature = ClassSignature.Newest
// decode(region, sampleSize)
decodeMethod = ImageDecoder::class.java.getMethod(
"decode",
rectClass,
intClass,
)
// newInstance(stream, cropBorders, displayProfile)
newInstanceMethod = ImageDecoder.Companion::class.java.getMethod(
"newInstance",
inputStreamClass,
booleanClass,
byteArrayClass,
)
} catch (_: NoSuchMethodException) {
try {
// Mihon Stable & forks
classSignature = ClassSignature.New
// decode(region, rgb565, sampleSize, applyColorManagement, displayProfile)
ImageDecoder::class.java.getMethod("decode", rectClass, booleanClass, intClass, booleanClass, byteArrayClass)
} catch (e: NoSuchMethodException) {
isNewDecodeMethod = false
decodeMethod = ImageDecoder::class.java.getMethod(
"decode",
rectClass,
booleanClass,
intClass,
booleanClass,
byteArrayClass,
)
// newInstance(stream, cropBorders)
newInstanceMethod = ImageDecoder.Companion::class.java.getMethod(
"newInstance",
inputStreamClass,
booleanClass,
)
} catch (_: NoSuchMethodException) {
// Tachiyomi J2k
classSignature = ClassSignature.Old
// decode(region, rgb565, sampleSize)
ImageDecoder::class.java.getMethod("decode", rectClass, booleanClass, intClass)
decodeMethod =
ImageDecoder::class.java.getMethod(
"decode",
rectClass,
booleanClass,
intClass,
)
// newInstance(stream, cropBorders)
newInstanceMethod = ImageDecoder.Companion::class.java.getMethod(
"newInstance",
inputStreamClass,
booleanClass,
)
}
}
}
fun decodeImage(data: ByteArray, rgb565: Boolean, filename: String, entryName: String): Bitmap {
val decoder = ImageDecoder.newInstance(ByteArrayInputStream(data))
val decoder = when (classSignature) {
ClassSignature.Newest -> newInstanceMethod.invoke(ImageDecoder.Companion, ByteArrayInputStream(data), false, null)
else -> newInstanceMethod.invoke(ImageDecoder.Companion, ByteArrayInputStream(data), false)
} as ImageDecoder?
if (decoder == null || decoder.width <= 0 || decoder.height <= 0) {
throw IOException("Falha ao inicializar o decodificador de imagem")
}
val rect = Rect(0, 0, decoder.width, decoder.height)
val bitmap = if (isNewDecodeMethod) {
decodeMethod.invoke(decoder, rect, rgb565, 1, false, null)
} else {
decodeMethod.invoke(decoder, rect, rgb565, 1)
val bitmap = when (classSignature) {
ClassSignature.Newest -> decodeMethod.invoke(decoder, rect, 1)
ClassSignature.New -> decodeMethod.invoke(decoder, rect, rgb565, 1, false, null)
else -> decodeMethod.invoke(decoder, rect, rgb565, 1)
} as Bitmap?
decoder.recycle()

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".all.unionmangas.UnionMangasUrlActivity"
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="https://unionmangas.xyz" />
<data android:scheme="https"/>
<data android:pathPattern="/manga-br/..*"/>
<data android:scheme="https"/>
<data android:pathPattern="/italy/..*"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,12 @@
ext {
extName = 'Union Mangas'
extClass = '.UnionMangasFactory'
extVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:cryptoaes'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,238 @@
package eu.kanade.tachiyomi.extension.all.unionmangas
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
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 eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
override val lang = langOption.lang
override val name: String = "Union Mangas"
override val baseUrl: String = "https://unionmangas.xyz"
override val supportsLatest = true
private val json: Json by injectLazy()
val langApiInfix = when (lang) {
"it" -> langOption.infix
else -> "v3/po"
}
override val client = network.client.newBuilder()
.rateLimit(5, 2, TimeUnit.SECONDS)
.build()
private fun apiHeaders(url: String): Headers {
val date = apiDateFormat.format(Date())
val path = url.toUrlWithoutDomain()
return headersBuilder()
.add("_hash", authorization(apiSeed, domain, date))
.add("_tranId", authorization(apiSeed, domain, date, path))
.add("_date", date)
.add("_domain", domain)
.add("_path", path)
.add("Origin", baseUrl)
.add("Host", apiUrl.removeProtocol())
.add("Referer", "$baseUrl/")
.build()
}
private fun authorization(vararg payloads: String): String {
val md = MessageDigest.getInstance("MD5")
val bytes = payloads.joinToString("").toByteArray()
val digest = md.digest(bytes)
return digest
.fold("") { str, byte -> str + "%02x".format(byte) }
.padStart(32, '0')
}
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapters = mutableListOf<SChapter>()
var currentPage = 0
do {
val chaptersDto = fetchChapterListPageable(manga, currentPage)
chapters += chaptersDto.toSChapter(langOption)
currentPage++
} while (chaptersDto.hasNextPage())
return Observable.just(chapters.reversed())
}
private fun fetchChapterListPageable(manga: SManga, page: Int): ChapterPageDto {
val maxResult = 16
val url = "$apiUrl/api/$langApiInfix/GetChapterListFilter/${manga.slug()}/$maxResult/$page/all/ASC"
return client.newCall(GET(url, apiHeaders(url))).execute()
.parseAs<ChapterPageDto>()
}
override fun latestUpdatesParse(response: Response): MangasPage {
val nextData = response.parseNextData<LatestUpdateProps>()
val dto = nextData.data.latestUpdateDto
val mangas = dto.mangas.map { mangaParse(it, nextData.query) }
return MangasPage(
mangas = mangas,
hasNextPage = dto.hasNextPage(),
)
}
override fun latestUpdatesRequest(page: Int): Request {
val url = "$baseUrl/${langOption.infix}/latest-releases".toHttpUrl().newBuilder()
.addQueryParameter("page", "$page")
.build()
return GET(url, headers)
}
override fun mangaDetailsParse(response: Response): SManga {
val nextData = response.parseNextData<MangaDetailsProps>()
val dto = nextData.data.mangaDetailsDto
return SManga.create().apply {
title = dto.title
genre = dto.genres
thumbnail_url = dto.thumbnailUrl
url = mangaUrlParse(dto.slug, nextData.query.type)
status = dto.status
}
}
override fun pageListParse(response: Response): List<Page> {
val chaptersDto = decryptChapters(response)
return chaptersDto.images.mapIndexed { index, imageUrl ->
Page(index, imageUrl = imageUrl)
}
}
private fun decryptChapters(response: Response): ChaptersDto {
val document = response.asJsoup()
val password = findChapterPassword(document)
val pageListData = document.parseNextData<ChaptersProps>().data.pageListData
val decodedData = CryptoAES.decrypt(pageListData, password)
return ChaptersDto(
data = json.decodeFromString<ChaptersDto>(decodedData).data,
delimiter = langOption.pageDelimiter,
)
}
private fun findChapterPassword(document: Document): String {
val regxPasswordUrl = """\/pages\/%5Btype%5D\/%5Bidmanga%5D\/%5Biddetail%5D-.+\.js""".toRegex()
val regxFindPassword = """AES\.decrypt\(\w+,"(?<password>[^"]+)"\)""".toRegex(RegexOption.MULTILINE)
val jsDecryptUrl = document.select("script")
.map { it.absUrl("src") }
.first { regxPasswordUrl.find(it) != null }
val jsDecrypt = client.newCall(GET(jsDecryptUrl, headers)).execute().asJsoup().html()
return regxFindPassword.find(jsDecrypt)?.groups?.get("password")!!.value.trim()
}
override fun popularMangaParse(response: Response): MangasPage {
val dto = response.parseNextData<PopularMangaProps>()
val mangas = dto.data.mangas.map { it.details }.map { mangaParse(it, dto.query) }
return MangasPage(
mangas = mangas,
hasNextPage = false,
)
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/${langOption.infix}")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val maxResult = 6
val url = "$apiUrl/api/$langApiInfix/searchforms/$maxResult/".toHttpUrl().newBuilder()
.addPathSegment(query)
.addPathSegment("${page - 1}")
.build()
return GET(url, apiHeaders(url.toString()))
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(slugPrefix)) {
val mangaUrl = query.substringAfter(slugPrefix)
return client.newCall(GET("$baseUrl/${langOption.infix}/$mangaUrl", headers))
.asObservableSuccess().map { response ->
val manga = mangaDetailsParse(response).apply {
url = mangaUrl
}
MangasPage(listOf(manga), false)
}
}
return super.fetchSearchManga(page, query, filters)
}
override fun imageUrlParse(response: Response): String = ""
override fun searchMangaParse(response: Response): MangasPage {
val mangasDto = response.parseAs<MangaListDto>().apply {
currentPage = response.request.url.pathSegments.last()
}
return MangasPage(
mangas = mangasDto.toSManga(langOption.infix),
hasNextPage = mangasDto.hasNextPage(),
)
}
private inline fun <reified T> Response.parseNextData() = asJsoup().parseNextData<T>()
private inline fun <reified T> Document.parseNextData(): NextData<T> {
val jsonContent = selectFirst("script#__NEXT_DATA__")!!.html()
return json.decodeFromString<NextData<T>>(jsonContent)
}
private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromString(body.string())
}
private fun String.removeProtocol() = trim().replace("https://", "")
private fun SManga.slug() = this.url.split("/").last()
private fun String.toUrlWithoutDomain() = trim().replace(apiUrl, "")
private fun mangaParse(dto: MangaDto, query: QueryDto): SManga {
return SManga.create().apply {
title = dto.title
thumbnail_url = dto.thumbnailUrl
status = dto.status
url = mangaUrlParse(dto.slug, query.type)
genre = dto.genres
}
}
private fun mangaUrlParse(slug: String, pathSegment: String) = "/$pathSegment/$slug"
companion object {
val apiUrl = "https://api.unionmanga.xyz"
val apiSeed = "8e0550790c94d6abc71d738959a88d209690dc86"
val domain = "yaoi-chan.xyz"
val slugPrefix = "slug:"
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
val apiDateFormat = SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.ENGLISH)
.apply { timeZone = TimeZone.getTimeZone("GMT") }
}
}

View File

@ -0,0 +1,149 @@
package eu.kanade.tachiyomi.extension.all.unionmangas
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class NextData<T>(val props: Props<T>, val query: QueryDto) {
val data get() = props.pageProps
}
@Serializable
class Props<T>(val pageProps: T)
@Serializable
class PopularMangaProps(@SerialName("data_popular") val mangas: List<PopularMangaDto>)
@Serializable
class LatestUpdateProps(@SerialName("data_lastuppdate") val latestUpdateDto: MangaListDto)
@Serializable
class MangaDetailsProps(@SerialName("dataManga") val mangaDetailsDto: MangaDetailsDto)
@Serializable
class ChaptersProps(@SerialName("data") val pageListData: String)
@Serializable
abstract class Pageable {
abstract var currentPage: String?
abstract var totalPage: Int
fun hasNextPage() =
try { (currentPage!!.toInt() + 1) < totalPage } catch (_: Exception) { false }
}
@Serializable
class ChapterPageDto(
val totalRecode: Int = 0,
override var currentPage: String?,
override var totalPage: Int,
@SerialName("data") val chapters: List<ChapterDto> = emptyList(),
) : Pageable() {
fun toSChapter(langOption: LanguageOption): List<SChapter> =
chapters.map { chapter ->
SChapter.create().apply {
name = chapter.name
date_upload = chapter.date.toDate()
url = "/${langOption.infix}${chapter.toChapterUrl(langOption.chpPrefix)}"
}
}
private fun String.toDate(): Long =
try { UnionMangas.dateFormat.parse(trim())!!.time } catch (_: Exception) { 0L }
private fun ChapterDto.toChapterUrl(prefix: String) = "/${this.slugManga}/$prefix-${this.id}"
}
@Serializable
class ChapterDto(
val date: String,
val slug: String,
@SerialName("idDoc") val slugManga: String,
@SerialName("idDetail") val id: String,
@SerialName("nameChapter") val name: String,
)
@Serializable
class QueryDto(
val type: String,
)
@Serializable
class MangaListDto(
override var currentPage: String?,
override var totalPage: Int,
@SerialName("data") val mangas: List<MangaDto>,
) : Pageable() {
fun toSManga(siteLang: String) = mangas.map { dto ->
SManga.create().apply {
title = dto.title
thumbnail_url = dto.thumbnailUrl
status = dto.status
url = mangaUrlParse(dto.slug, siteLang)
genre = dto.genres
}
}
}
@Serializable
class PopularMangaDto(
@SerialName("document") val details: MangaDto,
)
@Serializable
class MangaDto(
@SerialName("name") val title: String,
@SerialName("image") private val _thumbnailUrl: String,
@SerialName("idDoc") val slug: String,
@SerialName("genres") private val _genres: String,
@SerialName("status") val _status: String,
) {
val thumbnailUrl get() = "${UnionMangas.apiUrl}$_thumbnailUrl"
val genres get() = _genres.split(",").joinToString { it.trim() }
val status get() = toSMangaStatus(_status)
}
@Serializable
class MangaDetailsDto(
@SerialName("name") val title: String,
@SerialName("image") private val _thumbnailUrl: String,
@SerialName("idDoc") val slug: String,
@SerialName("lsgenres") private val _genres: List<Prop>,
@SerialName("lsstatus") private val _status: List<Prop>,
) {
val thumbnailUrl get() = "${UnionMangas.apiUrl}$_thumbnailUrl"
val genres get() = _genres.joinToString { it.name }
val status get() = toSMangaStatus(_status.first().name)
@Serializable
class Prop(
val name: String,
)
}
@Serializable
class ChaptersDto(
@SerialName("dataManga") val data: PageDto,
private var delimiter: String = "",
) {
val images get() = data.getImages(delimiter)
}
@Serializable
class PageDto(
@SerialName("source") private val imgData: String,
) {
fun getImages(delimiter: String): List<String> = imgData.split(delimiter)
}
private fun mangaUrlParse(slug: String, pathSegment: String) = "/$pathSegment/$slug"
private fun toSMangaStatus(status: String) =
when (status.lowercase()) {
"ongoing" -> SManga.ONGOING
"completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}

View File

@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.extension.all.unionmangas
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class UnionMangasFactory : SourceFactory {
override fun createSources(): List<Source> = languages.map { UnionMangas(it) }
}
class LanguageOption(val lang: String, val infix: String = lang, val chpPrefix: String, val pageDelimiter: String)
val languages = listOf(
LanguageOption("it", "italy", "leer", ","),
LanguageOption("pt-BR", "manga-br", "cap", "#"),
)

View File

@ -0,0 +1,36 @@
package eu.kanade.tachiyomi.extension.all.unionmangas
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 UnionMangasUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val host = intent?.data?.host
val pathSegments = intent?.data?.pathSegments
if (host != null && pathSegments != null) {
val intent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", slug(pathSegments))
putExtra("filter", packageName)
}
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.e("UnionMangasUrlActivity", e.toString())
}
}
finish()
exitProcess(0)
}
private fun slug(pathSegments: List<String>) = "${UnionMangas.slugPrefix}${pathSegments.last()}"
}

View File

@ -1,8 +0,0 @@
ext {
extName = 'GMANGA'
extClass = '.Gmanga'
themePkg = 'gmanga'
overrideVersionCode = 13
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,33 +0,0 @@
package eu.kanade.tachiyomi.extension.ar.gmanga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonPrimitive
@Serializable
class ChapterListResponse(
val releases: List<ChapterRelease>,
val chapterizations: List<Chapterization>,
val teams: List<Team>,
)
@Serializable
class ChapterRelease(
val id: Int,
@SerialName("chapterization_id") val chapId: Int,
@SerialName("team_id") val teamId: Int,
val chapter: JsonPrimitive,
@SerialName("time_stamp") val timestamp: Long,
)
@Serializable
class Chapterization(
val id: Int,
val title: String,
)
@Serializable
class Team(
val id: Int,
val name: String,
)

View File

@ -1,90 +0,0 @@
package eu.kanade.tachiyomi.extension.ar.gmanga
import android.app.Application
import eu.kanade.tachiyomi.multisrc.gmanga.BrowseManga
import eu.kanade.tachiyomi.multisrc.gmanga.Gmanga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.float
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Gmanga : Gmanga(
"GMANGA",
"https://gmanga.org",
"ar",
"https://media.gmanga.me",
) {
override val client = super.client.newBuilder()
.rateLimit(4)
.build()
init {
// remove obsolete preferences
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000).run {
if (contains("gmanga_chapter_listing")) {
edit().remove("gmanga_chapter_listing").apply()
}
if (contains("gmanga_last_listing")) {
edit().remove("gmanga_last_listing").apply()
}
}
}
override fun latestUpdatesParse(response: Response): MangasPage {
val decMga = response.decryptAs<JsonObject>()
val selectedManga = decMga["rows"]!!.jsonArray[0].jsonObject["rows"]!!.jsonArray
val manags = selectedManga.map {
json.decodeFromJsonElement<BrowseManga>(it.jsonArray[17])
}
val entries = manags.map { it.toSManga(::createThumbnail) }
.distinctBy { it.url }
return MangasPage(
entries,
hasNextPage = (manags.size >= 30),
)
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return client.newCall(chapterListRequest(manga))
.asObservable() // sites returns false 302 code
.map(::chapterListParse)
}
override fun chaptersRequest(manga: SManga): Request {
val mangaId = manga.url.substringAfterLast("/")
return GET("https://api2.gmanga.me/api/mangas/$mangaId/releases", headers)
}
override fun chaptersParse(response: Response): List<SChapter> {
val chapterList = response.parseAs<ChapterListResponse>()
return chapterList.releases.map {
SChapter.create().apply {
val chapter = chapterList.chapterizations.first { chap -> chap.id == it.chapId }
val team = chapterList.teams.firstOrNull { team -> team.id == it.teamId }
url = "/r/${it.id}"
chapter_number = it.chapter.float
date_upload = it.timestamp * 1000
scanlator = team?.name
val chapterName = chapter.title.let { if (it.trim() != "") " - $it" else "" }
name = "${chapter_number.let { if (it % 1 > 0) it else it.toInt() }}$chapterName"
}
}
}
}

View File

@ -1,9 +0,0 @@
ext {
extName = 'Colored Manga'
extClass = '.ColoredManga'
themePkg = 'madara'
baseUrl = 'https://coloredmanga.com'
overrideVersionCode = 2
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,14 +0,0 @@
package eu.kanade.tachiyomi.extension.en.coloredmanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import java.text.SimpleDateFormat
import java.util.Locale
class ColoredManga : Madara(
"Colored Manga",
"https://coloredmanga.com",
"en",
dateFormat = SimpleDateFormat("dd-MMM", Locale.ENGLISH),
) {
override val useNewChapterEndpoint = true
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'ComicExtra'
extClass = '.ComicExtra'
extVersionCode = 14
extVersionCode = 15
}
apply from: "$rootDir/common.gradle"

View File

@ -8,6 +8,7 @@ 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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -22,7 +23,7 @@ class ComicExtra : ParsedHttpSource() {
override val name = "ComicExtra"
override val baseUrl = "https://comicextra.me"
override val baseUrl = "https://comicextra.org"
override val lang = "en"
@ -40,7 +41,11 @@ class ComicExtra : ParsedHttpSource() {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return if (query.isNotBlank()) {
GET("$baseUrl/comic-search?key=$query", headers)
val url = "$baseUrl/search".toHttpUrl().newBuilder().apply {
addQueryParameter("keyword", query)
if (page > 1) addQueryParameter("page", page.toString())
}.build()
GET(url, headers)
} else {
var url = baseUrl
filters.forEach { filter ->
@ -153,7 +158,7 @@ class ComicExtra : ParsedHttpSource() {
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
document.select("img.chapter_img").forEachIndexed { i, img ->
document.select("div.chapter-container img").forEachIndexed { i, img ->
pages.add(Page(i, "", img.attr("abs:src")))
}
return pages

View File

@ -1,9 +1,9 @@
ext {
extName = 'EZmanga'
extClass = '.EZmanga'
themePkg = 'madara'
themePkg = 'keyoapp'
baseUrl = 'https://ezmanga.net'
overrideVersionCode = 0
overrideVersionCode = 34
}
apply from: "$rootDir/common.gradle"

View File

@ -1,19 +1,12 @@
package eu.kanade.tachiyomi.extension.en.ezmanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import java.text.SimpleDateFormat
import java.util.Locale
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
class EZmanga : Madara(
class EZmanga : Keyoapp(
"EZmanga",
"https://ezmanga.net",
"en",
dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH),
) {
override val useNewChapterEndpoint = true
override val client = super.client.newBuilder()
.rateLimit(1)
.build()
// Migrated from Madara to Keyoapp
override val versionId = 2
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Hentai2Read'
extClass = '.Hentai2Read'
extVersionCode = 14
extVersionCode = 16
isNsfw = true
}

View File

@ -57,7 +57,7 @@ class Hentai2Read : ParsedHttpSource() {
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
thumbnail_url = element.select("img").attr("abs:data-src")
thumbnail_url = element.select("img").attr("abs:src")
element.select("div.overlay-title a").let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
@ -162,7 +162,7 @@ class Hentai2Read : ParsedHttpSource() {
manga.genre = infoElement.select("li:contains(Category) > a, li:contains(Content) > a").joinToString(", ") { it.text() }
manga.description = buildDescription(infoElement)
manga.status = infoElement.select("li:contains(Status) > a").text().orEmpty().let { parseStatus(it) }
manga.thumbnail_url = document.select("a#js-linkNext > img").attr("src")
manga.thumbnail_url = document.select("a#js-linkNext img").attr("src")
manga.title = document.select("h3.block-title > a").first()!!.ownText().trim()
return manga
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'Kewn Scans'
extClass = '.KewnScans'
themePkg = 'keyoapp'
baseUrl = 'https://kewnscans.org'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,5 @@
package eu.kanade.tachiyomi.extension.en.kewnscans
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
class KewnScans : Keyoapp("Kewn Scans", "https://kewnscans.org", "en")

View File

@ -1,7 +1,7 @@
ext {
extName = 'Mangago'
extClass = '.Mangago'
extVersionCode = 13
extVersionCode = 14
isNsfw = true
}

View File

@ -175,7 +175,8 @@ class Mangago : ParsedHttpSource() {
override fun chapterFromElement(element: Element) = SChapter.create().apply {
val link = element.select("a.chico")
setUrlWithoutDomain(link.attr("href"))
val urlOriginal = link.attr("href")
if (urlOriginal.startsWith("http")) url = urlOriginal else setUrlWithoutDomain(urlOriginal)
name = link.text().trim()
date_upload = runCatching {
dateFormat.parse(element.select("td:last-child").text().trim())?.time
@ -242,6 +243,13 @@ class Mangago : ParsedHttpSource() {
}
}
override fun pageListRequest(chapter: SChapter): Request {
if (chapter.url.startsWith("http")) {
return GET(chapter.url, headers)
}
return super.pageListRequest(chapter)
}
override fun imageUrlParse(document: Document): String =
throw UnsupportedOperationException()

View File

@ -1,8 +0,0 @@
ext {
extName = 'MangaJar'
extClass = '.MangaJar'
extVersionCode = 9
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: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,275 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangajar
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import rx.Single
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class MangaJar : ParsedHttpSource() {
override val name = "MangaJar"
override val baseUrl = "https://mangajar.com"
override val lang = "en"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
// Popular
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manga?sortBy=popular&page=$page")
override fun popularMangaSelector() = "article[class*=flex-item]"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
setUrlWithoutDomain(element.select("a").attr("href"))
title = element.select("img").attr("title")
thumbnail_url = element.select("img").let {
if (it.hasAttr("data-src")) {
it.attr("data-src")
} else {
it.attr("src")
}
}
}
override fun popularMangaNextPageSelector() = "a.page-link[rel=next]"
// Latest
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga?sortBy=-last_chapter_at&page=$page")
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.findInstance<GenreList>()
val genre = genreFilter?.let { f -> f.values[f.state] }
val url = (if (genre!!.isEmpty()) "$baseUrl/search" else "$baseUrl/genre/$genre").toHttpUrl().newBuilder()
url.addQueryParameter("q", query)
url.addQueryParameter("page", page.toString())
for (filter in filterList) {
when (filter) {
is OrderBy -> {
url.addQueryParameter("sortBy", filter.toUriPart())
}
is SortBy -> {
url.addQueryParameter("sortAscending", filter.toUriPart())
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
description = document.select("div.manga-description.entry").text()
thumbnail_url = document.select("div.row > div > img").attr("src")
genre = document.select("div.post-info > span > a[href*=genre]").joinToString { it.text() }
status = parseStatus(document.select("span:has(b)")[1].text())
}
private fun parseStatus(status: String) = when {
status.contains("Ongoing") -> SManga.ONGOING
status.contains("Ended") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
// Chapters
/** For the first page. Pagination is done in [findChapters] */
override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/chaptersList")
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return findChapters(chapterListRequest(manga)).toObservable()
}
private fun findChapters(request: Request): Single<List<SChapter>> {
return client.newCall(request).asObservableSuccess().toSingle().flatMap { response ->
val document = response.asJsoup()
val thisPage = document.select(chapterListSelector()).map { chapter ->
SChapter.create().apply {
val link = chapter.select("a")
url = link.attr("href")
name = link.text()
date_upload = parseChapterDate(chapter.select("span.chapter-date").text().trim())
}
}
val nextPageLink = document.select("a.page-link[rel=\"next\"]").firstOrNull()
if (nextPageLink == null) {
Single.just(thisPage)
} else {
findChapters(GET("$baseUrl${nextPageLink.attr("href")}")).map { remainingChapters ->
thisPage + remainingChapters
}
}
}
}
override fun chapterListSelector() = "li.list-group-item.chapter-item"
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException()
private fun parseChapterDate(string: String): Long {
return if ("ago" in string) {
parseRelativeDate(string)
} else {
dateFormat.parse(string)?.time ?: 0L
}
}
private fun parseRelativeDate(date: String): Long {
val trimmedDate = date.substringBefore(" ago").removeSuffix("s").split(" ")
val calendar = Calendar.getInstance()
when (trimmedDate[1]) {
"month" -> calendar.apply { add(Calendar.MONTH, -trimmedDate[0].toInt()) }
"week" -> calendar.apply { add(Calendar.WEEK_OF_MONTH, -trimmedDate[0].toInt()) }
"day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) }
"hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) }
"minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) }
"second" -> calendar.apply { add(Calendar.SECOND, 0) }
}
return calendar.timeInMillis
}
// Page List
override fun pageListParse(document: Document): List<Page> {
return document.select("img[data-page]").mapIndexed { i, element ->
Page(i, "", if (element.hasAttr("data-src")) element.attr("abs:data-src") else element.attr("abs:src"))
}
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
// Filters
override fun getFilterList() = FilterList(
OrderBy(),
SortBy(),
GenreList(),
)
private class SortBy : UriPartFilter(
"Sort By",
arrayOf(
Pair("Descending", "0"),
Pair("Ascending", "1"),
),
)
private class OrderBy : UriPartFilter(
"Order By",
arrayOf(
Pair("Popularity", "popular"),
Pair("Year", "year"),
Pair("Alphabet", "name"),
Pair("Date added", "published_at"),
Pair("Date updated", "last_chapter_at"),
),
)
private class GenreList : Filter.Select<String>(
"Select Genre",
arrayOf(
"",
"Fantasy",
"Adventure",
"Martial Arts",
"Action",
"Demons",
"Shounen",
"Drama",
"Isekai",
"School Life",
"Harem",
"Horror",
"Supernatural",
"Mystery",
"Sci-Fi",
"Webtoons",
"Romance",
"Magic",
"Slice of Life",
"Seinen",
"Historical",
"Ecchi",
"Comedy",
"Sports",
"Tragedy",
"Shounen Ai",
"Yaoi",
"Shoujo",
"Super Power",
"Food",
"Psychological",
"Gender Bender",
"Smut",
"Shoujo Ai",
"Yuri",
"4-koma",
"Mecha",
"Adult",
"Mature",
"Military",
"Vampire",
"Kids",
"Space",
"Police",
"Music",
"One Shot",
"Parody",
"Josei",
),
)
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
// The following date related code is taken directly from Genkan.kt
companion object {
val dateFormat by lazy {
SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
}
}
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'Manganelo.biz'
extClass = '.ManganeloBiz'
themePkg = 'madara'
baseUrl = 'https://manganelo.biz'
overrideVersionCode = 0
isNsfw = true
}
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.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.extension.en.manganelobiz
import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManganeloBiz : Madara("Manganelo.biz", "https://manganelo.biz", "en") {
override val useNewChapterEndpoint = false
}

View File

@ -1,9 +0,0 @@
ext {
extName = 'MangaNelos.com'
extClass = '.MangaNelosCom'
themePkg = 'paprika'
baseUrl = 'http://manganelos.com'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.en.manganeloscom
import eu.kanade.tachiyomi.multisrc.paprika.Paprika
class MangaNelosCom : Paprika("MangaNelos.com", "http://manganelos.com", "en")

View File

@ -1,10 +0,0 @@
ext {
extName = 'Manganelo.website (unoriginal)'
extClass = '.ManganeloWebsite'
themePkg = 'madara'
baseUrl = 'https://manganelo.website'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.extension.en.manganelowebsite
import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManganeloWebsite : Madara("Manganelo.website (unoriginal)", "https://manganelo.website", "en") {
override val useNewChapterEndpoint = false
}

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,8 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangarosie
import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaRosie : Madara("MangaRosie", "https://mangarosie.in", "en") {
override val useNewChapterEndpoint = false
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
}

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.en.muctau
import eu.kanade.tachiyomi.multisrc.madara.Madara
class Muctau : Madara("Muctau", "https://bibimanga.com", "en")

View File

@ -15,6 +15,7 @@ class OmegaScans : HeanCms("Omega Scans", "https://omegascans.org", "en") {
override val versionId = 2
override val useNewChapterEndpoint = true
override val enableLogin = true
override fun getGenreList() = listOf(
Genre("Romance", 1),

View File

@ -3,7 +3,8 @@ ext {
extClass = '.StoneScape'
themePkg = 'madara'
baseUrl = 'https://stonescape.xyz'
overrideVersionCode = 0
overrideVersionCode = 1
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Some files were not shown because too many files have changed in this diff Show More