Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3a80b5a6d | ||
|
|
66dc5a9597 | ||
|
|
cb6bf91dd3 | ||
|
|
fb815abad0 | ||
|
|
8ef7580097 |
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
versionCode 378
|
versionCode 379
|
||||||
versionName '2.1.2'
|
versionName '2.1.3'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|||||||
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
@@ -7,5 +7,6 @@
|
|||||||
public static void checkParameterIsNotNull(...);
|
public static void checkParameterIsNotNull(...);
|
||||||
public static void checkNotNullParameter(...);
|
public static void checkNotNullParameter(...);
|
||||||
}
|
}
|
||||||
|
-keep public class ** extends org.koitharu.kotatsu.base.ui.BaseFragment
|
||||||
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
|
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
|
||||||
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||||
@@ -53,8 +53,10 @@ abstract class RemoteMangaRepository(
|
|||||||
if (subdomain != null) {
|
if (subdomain != null) {
|
||||||
append(subdomain)
|
append(subdomain)
|
||||||
append('.')
|
append('.')
|
||||||
|
append(conf.getDomain(defaultDomain).removePrefix("www."))
|
||||||
|
} else {
|
||||||
|
append(conf.getDomain(defaultDomain))
|
||||||
}
|
}
|
||||||
append(conf.getDomain(defaultDomain))
|
|
||||||
append(this@withDomain)
|
append(this@withDomain)
|
||||||
}
|
}
|
||||||
else -> this
|
else -> this
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
|
|||||||
scanlator = null,
|
scanlator = null,
|
||||||
branch = null,
|
branch = null,
|
||||||
)
|
)
|
||||||
}
|
} ?: bypassLicensedChapters(manga)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +191,32 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
|
|||||||
map[SourceSettings.KEY_USE_SSL] = true
|
map[SourceSettings.KEY_USE_SSL] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun bypassLicensedChapters(manga: Manga): List<MangaChapter> {
|
||||||
|
val doc = loaderContext.httpGet(manga.url.withDomain("m")).parseHtml()
|
||||||
|
val list = doc.body().selectFirst("ul.detail-ch-list") ?: return emptyList()
|
||||||
|
val dateFormat = SimpleDateFormat("MMM dd,yyyy", Locale.US)
|
||||||
|
return list.select("li").asReversed().mapIndexedNotNull { i, li ->
|
||||||
|
val a = li.selectFirst("a") ?: return@mapIndexedNotNull null
|
||||||
|
val href = a.relUrl("href")
|
||||||
|
val name = a.selectFirst("span.vol")?.text().orEmpty().ifEmpty {
|
||||||
|
a.ownText()
|
||||||
|
}
|
||||||
|
MangaChapter(
|
||||||
|
id = generateUid(href),
|
||||||
|
url = href,
|
||||||
|
source = MangaSource.MANGATOWN,
|
||||||
|
number = i + 1,
|
||||||
|
uploadDate = parseChapterDate(
|
||||||
|
dateFormat,
|
||||||
|
li.selectFirst("span.time")?.text()
|
||||||
|
),
|
||||||
|
name = name.ifEmpty { "${manga.title} - ${i + 1}" },
|
||||||
|
scanlator = null,
|
||||||
|
branch = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun String.parseTagKey() = split('/').findLast { TAG_REGEX matches it }
|
private fun String.parseTagKey() = split('/').findLast { TAG_REGEX matches it }
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|||||||
@@ -151,8 +151,10 @@ class MangareadRepository(
|
|||||||
?.selectFirst("div.reading-content")
|
?.selectFirst("div.reading-content")
|
||||||
?: throw ParseException("Root not found")
|
?: throw ParseException("Root not found")
|
||||||
return root.select("div.page-break").map { div ->
|
return root.select("div.page-break").map { div ->
|
||||||
val img = div.selectFirst("img")
|
val img = div.selectFirst("img") ?: parseFailed("Page image not found")
|
||||||
val url = img?.relUrl("src") ?: parseFailed("Page image not found")
|
val url = img.relUrl("data-src").ifEmpty {
|
||||||
|
img.relUrl("src")
|
||||||
|
}
|
||||||
MangaPage(
|
MangaPage(
|
||||||
id = generateUid(url),
|
id = generateUid(url),
|
||||||
url = url,
|
url = url,
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class DownloadManager(
|
|||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
val response = call.clone().await()
|
val response = call.clone().await()
|
||||||
withContext(Dispatchers.IO) {
|
runInterruptible(Dispatchers.IO) {
|
||||||
file.outputStream().use { out ->
|
file.outputStream().use { out ->
|
||||||
checkNotNull(response.body).byteStream().copyTo(out)
|
checkNotNull(response.body).byteStream().copyTo(out)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class MangaZip(val file: File) {
|
|||||||
return writableCbz.flush()
|
return writableCbz.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCover(file: File, ext: String) {
|
suspend fun addCover(file: File, ext: String) {
|
||||||
val name = buildString {
|
val name = buildString {
|
||||||
append(FILENAME_PATTERN.format(0, 0))
|
append(FILENAME_PATTERN.format(0, 0))
|
||||||
if (ext.isNotEmpty() && ext.length <= 4) {
|
if (ext.isNotEmpty() && ext.length <= 4) {
|
||||||
@@ -39,11 +39,11 @@ class MangaZip(val file: File) {
|
|||||||
append(ext)
|
append(ext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writableCbz[name] = file
|
writableCbz.put(name, file)
|
||||||
index.setCoverEntry(name)
|
index.setCoverEntry(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) {
|
suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) {
|
||||||
val name = buildString {
|
val name = buildString {
|
||||||
append(FILENAME_PATTERN.format(chapter.number, pageNumber))
|
append(FILENAME_PATTERN.format(chapter.number, pageNumber))
|
||||||
if (ext.isNotEmpty() && ext.length <= 4) {
|
if (ext.isNotEmpty() && ext.length <= 4) {
|
||||||
@@ -51,7 +51,7 @@ class MangaZip(val file: File) {
|
|||||||
append(ext)
|
append(ext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writableCbz[name] = file
|
writableCbz.put(name, file)
|
||||||
index.addChapter(chapter)
|
index.addChapter(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.local.data
|
package org.koitharu.kotatsu.local.data
|
||||||
|
|
||||||
import androidx.annotation.CheckResult
|
import androidx.annotation.CheckResult
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@@ -27,11 +26,13 @@ class WritableCbzFile(private val file: File) {
|
|||||||
}
|
}
|
||||||
ZipInputStream(FileInputStream(file)).use { zip ->
|
ZipInputStream(FileInputStream(file)).use { zip ->
|
||||||
var entry = zip.nextEntry
|
var entry = zip.nextEntry
|
||||||
while (entry != null) {
|
while (entry != null && currentCoroutineContext().isActive) {
|
||||||
val target = File(dir.path + File.separator + entry.name)
|
val target = File(dir.path + File.separator + entry.name)
|
||||||
target.parentFile?.mkdirs()
|
runInterruptible {
|
||||||
target.outputStream().use { out ->
|
target.parentFile?.mkdirs()
|
||||||
zip.copyTo(out)
|
target.outputStream().use { out ->
|
||||||
|
zip.copyTo(out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
zip.closeEntry()
|
zip.closeEntry()
|
||||||
entry = zip.nextEntry
|
entry = zip.nextEntry
|
||||||
@@ -51,11 +52,13 @@ class WritableCbzFile(private val file: File) {
|
|||||||
tempFile.delete()
|
tempFile.delete()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ZipOutputStream(FileOutputStream(tempFile)).use { zip ->
|
runInterruptible {
|
||||||
dir.listFiles()?.forEach {
|
ZipOutputStream(FileOutputStream(tempFile)).use { zip ->
|
||||||
zipFile(it, it.name, zip)
|
dir.listFiles()?.forEach {
|
||||||
|
zipFile(it, it.name, zip)
|
||||||
|
}
|
||||||
|
zip.flush()
|
||||||
}
|
}
|
||||||
zip.flush()
|
|
||||||
}
|
}
|
||||||
tempFile.renameTo(file)
|
tempFile.renameTo(file)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -67,29 +70,26 @@ class WritableCbzFile(private val file: File) {
|
|||||||
|
|
||||||
operator fun get(name: String) = File(dir, name)
|
operator fun get(name: String) = File(dir, name)
|
||||||
|
|
||||||
operator fun set(name: String, file: File) {
|
suspend fun put(name: String, file: File) = runInterruptible(Dispatchers.IO) {
|
||||||
file.copyTo(this[name], overwrite = true)
|
file.copyTo(this[name], overwrite = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) {
|
||||||
|
if (fileToZip.isDirectory) {
|
||||||
private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) {
|
if (fileName.endsWith("/")) {
|
||||||
if (fileToZip.isDirectory) {
|
zipOut.putNextEntry(ZipEntry(fileName))
|
||||||
if (fileName.endsWith("/")) {
|
|
||||||
zipOut.putNextEntry(ZipEntry(fileName))
|
|
||||||
} else {
|
|
||||||
zipOut.putNextEntry(ZipEntry("$fileName/"))
|
|
||||||
}
|
|
||||||
zipOut.closeEntry()
|
|
||||||
fileToZip.listFiles()?.forEach { childFile ->
|
|
||||||
zipFile(childFile, "$fileName/${childFile.name}", zipOut)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
FileInputStream(fileToZip).use { fis ->
|
zipOut.putNextEntry(ZipEntry("$fileName/"))
|
||||||
val zipEntry = ZipEntry(fileName)
|
}
|
||||||
zipOut.putNextEntry(zipEntry)
|
zipOut.closeEntry()
|
||||||
fis.copyTo(zipOut)
|
fileToZip.listFiles()?.forEach { childFile ->
|
||||||
}
|
zipFile(childFile, "$fileName/${childFile.name}", zipOut)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FileInputStream(fileToZip).use { fis ->
|
||||||
|
val zipEntry = ZipEntry(fileName)
|
||||||
|
zipOut.putNextEntry(zipEntry)
|
||||||
|
fis.copyTo(zipOut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,10 @@ object PendingIntentCompat {
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_MUTABLE
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ class RecentWidgetProvider : AppWidgetProvider() {
|
|||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
views.setEmptyView(R.id.stackView, R.id.textView_holder)
|
views.setEmptyView(R.id.stackView, R.id.textView_holder)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class ShelfWidgetProvider : AppWidgetProvider() {
|
|||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
views.setEmptyView(R.id.gridView, R.id.textView_holder)
|
views.setEmptyView(R.id.gridView, R.id.textView_holder)
|
||||||
|
|||||||
Reference in New Issue
Block a user