Compare commits

..

7 Commits
v2.1 ... v2.1.2

Author SHA1 Message Date
Koitharu
197393fbd1 Fix webtoon scroll 2022-01-15 17:21:03 +02:00
J. Lavoie
51ef6e3c78 Translated using Weblate (French)
Currently translated at 100.0% (249 of 249 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
2022-01-15 08:37:11 +02:00
J. Lavoie
663277fe6f Translated using Weblate (Italian)
Currently translated at 100.0% (249 of 249 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
2022-01-15 08:37:11 +02:00
J. Lavoie
332a38d674 Translated using Weblate (German)
Currently translated at 100.0% (249 of 249 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
2022-01-15 08:37:11 +02:00
Zakhar Timoshenko
e9410a2f54 [MangaOwl] Fix missing pages 2022-01-15 08:20:17 +02:00
Koitharu
b5fa2bd660 Fix MangaDex pages extraction 2022-01-14 08:50:04 +02:00
Koitharu
e56c61d834 Update manga parsers 2022-01-10 18:36:52 +02:00
14 changed files with 76 additions and 43 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 31
versionCode 376
versionName '2.1'
versionCode 378
versionName '2.1.2'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -93,14 +93,14 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
description = json.getString("description"),
chapters = chaptersList.mapIndexed { i, it ->
val chid = it.getLong("id")
val volChap = "Том " + it.getString("vol") + ". " + "Глава " + it.getString("ch")
val title = if (it.getString("title") == "null") "" else it.getString("title")
val volChap = "Том " + it.optString("vol", "0") + ". " + "Глава " + it.optString("ch", "0")
val title = it.optString("title", "null").takeUnless { it == "null" }
MangaChapter(
id = generateUid(chid),
source = manga.source,
url = "$baseChapterUrl$chid",
uploadDate = it.getLong("date") * 1000,
name = if (title.isEmpty()) volChap else "$volChap: $title",
name = if (title.isNullOrEmpty()) volChap else "$volChap: $title",
number = totalChapters - i,
scanlator = null,
branch = null,

View File

@@ -148,7 +148,7 @@ class MangaDexRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
chapters = feed.mapNotNull { jo ->
val id = jo.getString("id")
val attrs = jo.getJSONObject("attributes")
if (attrs.optJSONArray("data").isNullOrEmpty()) {
if (!attrs.isNull("externalUrl")) {
return@mapNotNull null
}
val locale = Locale.forLanguageTag(attrs.getString("translatedLanguage"))
@@ -171,15 +171,14 @@ class MangaDexRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val domain = getDomain()
val attrs = loaderContext.httpGet("https://api.$domain/chapter/${chapter.url}")
val chapter = loaderContext.httpGet("https://api.$domain/at-home/server/${chapter.url}?forcePort443=false")
.parseJson()
.getJSONObject("data")
.getJSONObject("attributes")
val data = attrs.getJSONArray("data")
val prefix = "https://uploads.$domain/data/${attrs.getString("hash")}/"
.getJSONObject("chapter")
val pages = chapter.getJSONArray("data")
val prefix = "https://uploads.$domain/data/${chapter.getString("hash")}/"
val referer = "https://$domain/"
return List(data.length()) { i ->
val url = prefix + data.getString(i)
return List(pages.length()) { i ->
val url = prefix + pages.getString(i)
MangaPage(
id = generateUid(url),
url = url,

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.core.parser.site
import android.util.Base64
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
@@ -75,6 +76,10 @@ class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
val info = doc.body().selectFirst("div.single_detail") ?: parseFailed("An error occurred while parsing")
val table = doc.body().selectFirst("div.single-grid-right") ?: parseFailed("An error occurred while parsing")
val dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US)
val trRegex = "window\\['tr'] = '([^']*)';".toRegex(RegexOption.IGNORE_CASE)
val trElement = doc.getElementsByTag("script").find { trRegex.find(it.data()) != null } ?: parseFailed("Oops, tr not found")
val tr = trRegex.find(trElement.data())!!.groups[1]!!.value
val s = Base64.encodeToString(defaultDomain.toByteArray(), Base64.NO_PADDING)
return manga.copy(
description = info.selectFirst(".description")?.html(),
largeCoverUrl = info.select("img").first()?.let { img ->
@@ -100,7 +105,7 @@ class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
id = generateUid(href),
name = a.select("label").text(),
number = i + 1,
url = href,
url = "$href?tr=$tr&s=$s",
scanlator = null,
branch = null,
uploadDate = dateFormat.tryParse(li.selectFirst("small:last-of-type")?.text()),
@@ -120,7 +125,7 @@ class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
id = generateUid(url),
url = url,
preview = null,
referer = fullUrl,
referer = url,
source = MangaSource.MANGAOWL,
)
}

View File

@@ -55,7 +55,7 @@ class MangareadRepository(
id = generateUid(href),
url = href,
publicUrl = href.inContextOf(div),
coverUrl = div.selectFirst("img")?.absUrl("src").orEmpty(),
coverUrl = div.selectFirst("img")?.absUrl("data-src").orEmpty(),
title = summary?.selectFirst("h3")?.text().orEmpty(),
rating = div.selectFirst("span.total_votes")?.ownText()
?.toFloatOrNull()?.div(5f) ?: -1f,
@@ -107,16 +107,6 @@ class MangareadRepository(
val root2 = doc.body().selectFirst("div.content-area")
?.selectFirst("div.c-page")
?: throw ParseException("Root2 not found")
val mangaId = doc.getElementsByAttribute("data-post").firstOrNull()
?.attr("data-post")?.toLongOrNull()
?: throw ParseException("Cannot obtain manga id")
val doc2 = loaderContext.httpPost(
"https://${getDomain()}/wp-admin/admin-ajax.php",
mapOf(
"action" to "manga_get_chapters",
"manga" to mangaId.toString()
)
).parseHtml()
val dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US)
return manga.copy(
tags = root.selectFirst("div.genres-content")?.select("a")
@@ -132,7 +122,7 @@ class MangareadRepository(
?.select("p")
?.filterNot { it.ownText().startsWith("A brief description") }
?.joinToString { it.html() },
chapters = doc2.select("li").asReversed().mapIndexed { i, li ->
chapters = root2.select("li").asReversed().mapIndexed { i, li ->
val a = li.selectFirst("a")
val href = a?.relUrl("href").orEmpty().ifEmpty {
parseFailed("Link is missing")
@@ -144,7 +134,7 @@ class MangareadRepository(
url = href,
uploadDate = parseChapterDate(
dateFormat,
doc2.selectFirst("span.chapter-release-date i")?.text()
li.selectFirst("span.chapter-release-date i")?.text()
),
source = MangaSource.MANGAREAD,
scanlator = null,

View File

@@ -125,10 +125,10 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
number = chapters.length() - i,
name = buildString {
append("Том ")
append(jo.getString("tome"))
append(jo.optString("tome", "0"))
append(". ")
append("Глава ")
append(jo.getString("chapter"))
append(jo.optString("chapter", "0"))
if (name.isNotEmpty()) {
append(" - ")
append(name)

View File

@@ -1,16 +1,19 @@
package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.app.Activity
import android.content.Context
import android.graphics.PointF
import android.util.AttributeSet
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import org.koitharu.kotatsu.utils.ext.toIntUp
class WebtoonImageView @JvmOverloads constructor(context: Context, attr: AttributeSet? = null) :
SubsamplingScaleImageView(context, attr) {
class WebtoonImageView @JvmOverloads constructor(
context: Context,
attr: AttributeSet? = null,
) : SubsamplingScaleImageView(context, attr) {
private val ct = PointF()
private val displayHeight = resources.displayMetrics.heightPixels
private val displayHeight = (context as Activity).window.decorView.height
private var scrollPos = 0
private var scrollRange = SCROLL_UNKNOWN
@@ -55,6 +58,30 @@ class WebtoonImageView @JvmOverloads constructor(context: Context, attr: Attribu
return desiredHeight
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
val parentHeight = MeasureSpec.getSize(heightMeasureSpec)
val resizeWidth = widthSpecMode != MeasureSpec.EXACTLY
val resizeHeight = heightSpecMode != MeasureSpec.EXACTLY
var width = parentWidth
var height = parentHeight
if (sWidth > 0 && sHeight > 0) {
if (resizeWidth && resizeHeight) {
width = sWidth
height = sHeight
} else if (resizeHeight) {
height = (sHeight.toDouble() / sWidth.toDouble() * width).toInt()
} else if (resizeWidth) {
width = (sWidth.toDouble() / sHeight.toDouble() * height).toInt()
}
}
width = width.coerceAtLeast(suggestedMinimumWidth)
height = height.coerceIn(suggestedMinimumHeight, displayHeight)
setMeasuredDimension(width, height)
}
private fun scrollToInternal(pos: Int) {
scrollPos = pos
ct.set(sWidth / 2f, (height / 2f + pos.toFloat()) / minScale)

View File

@@ -34,7 +34,7 @@ class WebtoonRecyclerView @JvmOverloads constructor(
consumed[0] = 0
consumed[1] = consumedY
}
return consumedY != 0
return consumedY != 0 || dy == 0
}
private fun consumeVerticalScroll(dy: Int): Int {

View File

@@ -246,4 +246,7 @@
<string name="system_default">Standard</string>
<string name="exclude_nsfw_from_history">NSFW-Manga aus dem Verlauf ausschließen</string>
<string name="error_empty_name">Der Name sollte nicht leer sein</string>
<string name="show_pages_numbers">Seitenzahlen anzeigen</string>
<string name="enabled_sources">Freigegebene Quellen</string>
<string name="available_sources">Verfügbare Quellen</string>
</resources>

View File

@@ -246,4 +246,7 @@
<string name="system_default">Par défaut</string>
<string name="exclude_nsfw_from_history">Exclure les mangas osés de l\'historique</string>
<string name="error_empty_name">Le nom ne doit pas être vide</string>
<string name="show_pages_numbers">Afficher les numéros de pages</string>
<string name="enabled_sources">Sources activées</string>
<string name="available_sources">Sources disponibles</string>
</resources>

View File

@@ -246,4 +246,7 @@
<string name="date_format">Formato della data</string>
<string name="exclude_nsfw_from_history">Escludi i manga NSFW dalla storia</string>
<string name="error_empty_name">Il nome non dovrebbe essere vuoto</string>
<string name="show_pages_numbers">Mostra i numeri delle pagine</string>
<string name="enabled_sources">Fonti abilitate</string>
<string name="available_sources">Fonti disponibili</string>
</resources>

View File

@@ -6,6 +6,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.koin.core.component.inject
import org.koin.core.logger.Level
import org.koin.core.parameter.parametersOf
import org.koin.test.KoinTest
import org.koin.test.KoinTestRule
@@ -18,7 +19,7 @@ import org.koitharu.kotatsu.utils.TestResponse
import org.koitharu.kotatsu.utils.ext.mapToSet
import org.koitharu.kotatsu.utils.ext.medianOrNull
import org.koitharu.kotatsu.utils.isAbsoluteUrl
import org.koitharu.kotatsu.utils.isRelativeUrl
import org.koitharu.kotatsu.utils.isNotAbsoluteUrl
@RunWith(Parameterized::class)
class RemoteMangaRepositoryTest(private val source: MangaSource) : KoinTest {
@@ -29,7 +30,7 @@ class RemoteMangaRepositoryTest(private val source: MangaSource) : KoinTest {
@get:Rule
val koinTestRule = KoinTestRule.create {
printLogger()
printLogger(Level.ERROR)
modules(repositoryTestModule)
}
@@ -112,7 +113,7 @@ class RemoteMangaRepositoryTest(private val source: MangaSource) : KoinTest {
Truth.assertThat(list.map { it.id }).containsNoDuplicates()
for (item in list) {
Truth.assertThat(item.url).isNotEmpty()
Truth.assertThat(item.url).isRelativeUrl()
Truth.assertThat(item.url).isNotAbsoluteUrl()
Truth.assertThat(item.coverUrl).isAbsoluteUrl()
Truth.assertThat(item.title).isNotEmpty()
Truth.assertThat(item.publicUrl).isAbsoluteUrl()

View File

@@ -3,25 +3,25 @@ package org.koitharu.kotatsu.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description
class CoroutineTestRule(
private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher(),
private val testDispatcher: TestDispatcher = StandardTestDispatcher(),
) : TestWatcher() {
override fun starting(description: Description?) {
override fun starting(description: Description) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
override fun finished(description: Description) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
fun runBlockingTest(block: suspend CoroutineScope.() -> Unit) {

View File

@@ -8,4 +8,6 @@ private val PATTERN_URL_RELATIVE = Pattern.compile("^/[^\\s]+", Pattern.CASE_INS
fun StringSubject.isRelativeUrl() = matches(PATTERN_URL_RELATIVE)
fun StringSubject.isAbsoluteUrl() = matches(PATTERN_URL_ABSOLUTE)
fun StringSubject.isAbsoluteUrl() = matches(PATTERN_URL_ABSOLUTE)
fun StringSubject.isNotAbsoluteUrl() = doesNotMatch(PATTERN_URL_ABSOLUTE)