Convert :source-api and :source-local to android library module (#3636)
- There's really no plan to support other platforms on this code base - The "KMP" modules only had android target which is rather pointless - `i18n` involves more work but given it has no actual code keeping it as is
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
@@ -0,0 +1,386 @@
|
||||
package tachiyomi.source.local
|
||||
|
||||
import android.content.Context
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.UnmeteredSource
|
||||
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.model.SMangaUpdate
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import logcat.LogPriority
|
||||
import mihon.core.archive.archiveReader
|
||||
import mihon.core.archive.epubReader
|
||||
import nl.adaptivity.xmlutil.core.AndroidXmlReader
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.storage.extension
|
||||
import tachiyomi.core.common.storage.nameWithoutExtension
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.getComicInfo
|
||||
import tachiyomi.core.metadata.tachiyomi.MangaDetails
|
||||
import tachiyomi.domain.chapter.service.ChapterRecognition
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.source.local.filter.OrderBy
|
||||
import tachiyomi.source.local.image.LocalCoverManager
|
||||
import tachiyomi.source.local.io.Archive
|
||||
import tachiyomi.source.local.io.Format
|
||||
import tachiyomi.source.local.io.LocalSourceFileSystem
|
||||
import tachiyomi.source.local.metadata.fillMetadata
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.InputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import tachiyomi.domain.source.model.Source as DomainSource
|
||||
|
||||
class LocalSource(
|
||||
private val context: Context,
|
||||
private val fileSystem: LocalSourceFileSystem,
|
||||
private val coverManager: LocalCoverManager,
|
||||
) : Source, UnmeteredSource {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
private val xml: XML by injectLazy()
|
||||
|
||||
@Suppress("PrivatePropertyName")
|
||||
private val PopularFilters = FilterList(OrderBy.Popular(context))
|
||||
|
||||
@Suppress("PrivatePropertyName")
|
||||
private val LatestFilters = FilterList(OrderBy.Latest(context))
|
||||
|
||||
override val name: String = context.stringResource(MR.strings.local_source)
|
||||
|
||||
override val id: Long = ID
|
||||
|
||||
override val lang: String = "other"
|
||||
|
||||
override fun toString() = name
|
||||
|
||||
override val supportsLatest: Boolean = true
|
||||
|
||||
// Browse related
|
||||
override suspend fun getPopularManga(page: Int) = getSearchManga(page, "", PopularFilters)
|
||||
|
||||
override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LatestFilters)
|
||||
|
||||
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage = withIOContext {
|
||||
val lastModifiedLimit = if (filters === LatestFilters) {
|
||||
System.currentTimeMillis() - LATEST_THRESHOLD
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
|
||||
var mangaDirs = fileSystem.getFilesInBaseDirectory()
|
||||
// Filter out files that are hidden and is not a folder
|
||||
.filter { it.isDirectory && !it.name.orEmpty().startsWith('.') }
|
||||
.distinctBy { it.name }
|
||||
.filter {
|
||||
if (lastModifiedLimit == 0L && query.isBlank()) {
|
||||
true
|
||||
} else if (lastModifiedLimit == 0L) {
|
||||
it.name.orEmpty().contains(query, ignoreCase = true)
|
||||
} else {
|
||||
it.lastModified() >= lastModifiedLimit
|
||||
}
|
||||
}
|
||||
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is OrderBy.Popular -> {
|
||||
mangaDirs = if (filter.state!!.ascending) {
|
||||
mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name.orEmpty() })
|
||||
} else {
|
||||
mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name.orEmpty() })
|
||||
}
|
||||
}
|
||||
is OrderBy.Latest -> {
|
||||
mangaDirs = if (filter.state!!.ascending) {
|
||||
mangaDirs.sortedBy(UniFile::lastModified)
|
||||
} else {
|
||||
mangaDirs.sortedByDescending(UniFile::lastModified)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
/* Do nothing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mangas = mangaDirs
|
||||
.map { mangaDir ->
|
||||
async {
|
||||
SManga.create().apply {
|
||||
title = mangaDir.name.orEmpty()
|
||||
url = mangaDir.name.orEmpty()
|
||||
|
||||
// Try to find the cover
|
||||
coverManager.find(mangaDir.name.orEmpty())?.let {
|
||||
thumbnail_url = it.uri.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
MangasPage(mangas, false)
|
||||
}
|
||||
|
||||
override suspend fun getMangaUpdate(
|
||||
manga: SManga,
|
||||
chapters: List<SChapter>,
|
||||
fetchDetails: Boolean,
|
||||
fetchChapters: Boolean,
|
||||
): SMangaUpdate = supervisorScope {
|
||||
val asyncManga = if (fetchDetails) async { getMangaDetails(manga) } else null
|
||||
val asyncChapters = if (fetchChapters) async { getChapterList(manga) } else null
|
||||
SMangaUpdate(asyncManga?.await() ?: manga, asyncChapters?.await() ?: chapters)
|
||||
}
|
||||
|
||||
// Manga details related
|
||||
private suspend fun getMangaDetails(manga: SManga): SManga = withIOContext {
|
||||
coverManager.find(manga.url)?.let {
|
||||
manga.thumbnail_url = it.uri.toString()
|
||||
}
|
||||
|
||||
// Augment manga details based on metadata files
|
||||
try {
|
||||
val mangaDir = fileSystem.getMangaDirectory(manga.url) ?: error("${manga.url} is not a valid directory")
|
||||
val mangaDirFiles = mangaDir.listFiles().orEmpty()
|
||||
|
||||
val comicInfoFile = mangaDirFiles
|
||||
.firstOrNull { it.name == COMIC_INFO_FILE }
|
||||
val noXmlFile = mangaDirFiles
|
||||
.firstOrNull { it.name == ".noxml" }
|
||||
val legacyJsonDetailsFile = mangaDirFiles
|
||||
.firstOrNull { it.extension == "json" }
|
||||
|
||||
when {
|
||||
// Top level ComicInfo.xml
|
||||
comicInfoFile != null -> {
|
||||
noXmlFile?.delete()
|
||||
setMangaDetailsFromComicInfoFile(comicInfoFile.openInputStream(), manga)
|
||||
}
|
||||
|
||||
// Old custom JSON format
|
||||
// TODO: remove support for this entirely after a while
|
||||
legacyJsonDetailsFile != null -> {
|
||||
json.decodeFromStream<MangaDetails>(legacyJsonDetailsFile.openInputStream()).run {
|
||||
title?.let { manga.title = it }
|
||||
author?.let { manga.author = it }
|
||||
artist?.let { manga.artist = it }
|
||||
description?.let { manga.description = it }
|
||||
genre?.let { manga.genre = it.joinToString() }
|
||||
status?.let { manga.status = it }
|
||||
}
|
||||
// Replace with ComicInfo.xml file
|
||||
val comicInfo = manga.getComicInfo()
|
||||
mangaDir
|
||||
.createFile(COMIC_INFO_FILE)
|
||||
?.openOutputStream()
|
||||
?.use {
|
||||
val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo)
|
||||
it.write(comicInfoString.toByteArray())
|
||||
legacyJsonDetailsFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
// Copy ComicInfo.xml from chapter archive to top level if found
|
||||
noXmlFile == null -> {
|
||||
val chapterArchives = mangaDirFiles.filter(Archive::isSupported)
|
||||
|
||||
val copiedFile = copyComicInfoFileFromChapters(chapterArchives, mangaDir)
|
||||
if (copiedFile != null) {
|
||||
setMangaDetailsFromComicInfoFile(copiedFile.openInputStream(), manga)
|
||||
} else {
|
||||
// Avoid re-scanning
|
||||
mangaDir.createFile(".noxml")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e) { "Error setting manga details from local metadata for ${manga.title}" }
|
||||
}
|
||||
|
||||
return@withIOContext manga
|
||||
}
|
||||
|
||||
private fun <T> getComicInfoForChapter(chapter: UniFile, block: (InputStream) -> T): T? {
|
||||
return if (chapter.isDirectory) {
|
||||
chapter.findFile(COMIC_INFO_FILE)?.openInputStream()?.use(block)
|
||||
} else {
|
||||
chapter.archiveReader(context).use { reader ->
|
||||
reader.getInputStream(COMIC_INFO_FILE)?.use(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyComicInfoFileFromChapters(chapterArchives: List<UniFile>, folder: UniFile): UniFile? {
|
||||
for (chapter in chapterArchives) {
|
||||
val file = getComicInfoForChapter(chapter) f@{ stream ->
|
||||
return@f copyComicInfoFile(stream, folder)
|
||||
}
|
||||
if (file != null) return file
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun copyComicInfoFile(comicInfoFileStream: InputStream, folder: UniFile): UniFile? {
|
||||
return folder.createFile(COMIC_INFO_FILE)?.apply {
|
||||
openOutputStream().use { outputStream ->
|
||||
comicInfoFileStream.use { it.copyTo(outputStream) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseComicInfo(stream: InputStream): ComicInfo {
|
||||
return AndroidXmlReader(stream, StandardCharsets.UTF_8.name()).use {
|
||||
xml.decodeFromReader<ComicInfo>(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMangaDetailsFromComicInfoFile(stream: InputStream, manga: SManga) {
|
||||
manga.copyFromComicInfo(parseComicInfo(stream))
|
||||
}
|
||||
|
||||
private fun setChapterDetailsFromComicInfoFile(stream: InputStream, chapter: SChapter) {
|
||||
val comicInfo = parseComicInfo(stream)
|
||||
|
||||
comicInfo.title?.let { chapter.name = it.value }
|
||||
comicInfo.number?.value?.toFloatOrNull()?.let { chapter.chapter_number = it }
|
||||
comicInfo.translator?.let { chapter.scanlator = it.value }
|
||||
}
|
||||
|
||||
// Chapters
|
||||
private suspend fun getChapterList(manga: SManga): List<SChapter> = withIOContext {
|
||||
val chapters = fileSystem.getFilesInMangaDirectory(manga.url)
|
||||
// Only keep supported formats
|
||||
.filterNot { it.name.orEmpty().startsWith('.') }
|
||||
.filter { it.isDirectory || Archive.isSupported(it) || it.extension.equals("epub", true) }
|
||||
.map { chapterFile ->
|
||||
SChapter.create().apply {
|
||||
url = "${manga.url}/${chapterFile.name}"
|
||||
name = if (chapterFile.isDirectory) {
|
||||
chapterFile.name
|
||||
} else {
|
||||
chapterFile.nameWithoutExtension
|
||||
}.orEmpty()
|
||||
date_upload = chapterFile.lastModified()
|
||||
chapter_number = ChapterRecognition
|
||||
.parseChapterNumber(manga.title, this.name, this.chapter_number.toDouble())
|
||||
.toFloat()
|
||||
|
||||
val format = Format.valueOf(chapterFile)
|
||||
if (format is Format.Epub) {
|
||||
format.file.epubReader(context).use { epub ->
|
||||
epub.fillMetadata(manga, this)
|
||||
}
|
||||
} else {
|
||||
getComicInfoForChapter(chapterFile) { stream ->
|
||||
setChapterDetailsFromComicInfoFile(stream, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sortedWith { c1, c2 ->
|
||||
c2.name.compareToCaseInsensitiveNaturalOrder(c1.name)
|
||||
}
|
||||
|
||||
// Copy the cover from the first chapter found if not available
|
||||
if (manga.thumbnail_url.isNullOrBlank()) {
|
||||
chapters.lastOrNull()?.let { chapter ->
|
||||
updateCover(chapter, manga)
|
||||
}
|
||||
}
|
||||
|
||||
chapters
|
||||
}
|
||||
|
||||
// Filters
|
||||
override fun getFilterList() = FilterList(OrderBy.Popular(context))
|
||||
|
||||
// Unused stuff
|
||||
override suspend fun getPageList(chapter: SChapter): List<Page> = throw UnsupportedOperationException("Unused")
|
||||
|
||||
fun getFormat(chapter: SChapter): Format {
|
||||
try {
|
||||
val (mangaDirName, chapterName) = chapter.url.split('/', limit = 2)
|
||||
return fileSystem.getBaseDirectory()
|
||||
?.findFile(mangaDirName)
|
||||
?.findFile(chapterName)
|
||||
?.let(Format.Companion::valueOf)
|
||||
?: throw Exception(context.stringResource(MR.strings.chapter_not_found))
|
||||
} catch (e: Format.UnknownFormatException) {
|
||||
throw Exception(context.stringResource(MR.strings.local_invalid_format))
|
||||
} catch (e: Exception) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCover(chapter: SChapter, manga: SManga): UniFile? {
|
||||
return try {
|
||||
when (val format = getFormat(chapter)) {
|
||||
is Format.Directory -> {
|
||||
val entry = format.file.listFiles()
|
||||
?.sortedWith { f1, f2 ->
|
||||
f1.name.orEmpty().compareToCaseInsensitiveNaturalOrder(
|
||||
f2.name.orEmpty(),
|
||||
)
|
||||
}
|
||||
?.find {
|
||||
!it.isDirectory && ImageUtil.isImage(it.name) { it.openInputStream() }
|
||||
}
|
||||
|
||||
entry?.let { coverManager.update(manga, it.openInputStream()) }
|
||||
}
|
||||
is Format.Archive -> {
|
||||
format.file.archiveReader(context).use { reader ->
|
||||
val entry = reader.useEntries { entries ->
|
||||
entries
|
||||
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||
.find { it.isFile && ImageUtil.isImage(it.name) { reader.getInputStream(it.name)!! } }
|
||||
}
|
||||
|
||||
entry?.let { coverManager.update(manga, reader.getInputStream(it.name)!!) }
|
||||
}
|
||||
}
|
||||
is Format.Epub -> {
|
||||
format.file.epubReader(context).use { epub ->
|
||||
val entry = epub.getImagesFromPages().firstOrNull()
|
||||
|
||||
entry?.let { coverManager.update(manga, epub.getInputStream(it)!!) }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e) { "Error updating cover for ${manga.title}" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = 0L
|
||||
const val HELP_URL = "https://mihon.app/docs/guides/local-source/"
|
||||
|
||||
private val LATEST_THRESHOLD = 7.days.inWholeMilliseconds
|
||||
}
|
||||
}
|
||||
|
||||
fun Manga.isLocal(): Boolean = source == LocalSource.ID
|
||||
|
||||
fun Source.isLocal(): Boolean = id == LocalSource.ID
|
||||
|
||||
fun DomainSource.isLocal(): Boolean = id == LocalSource.ID
|
||||
@@ -0,0 +1,15 @@
|
||||
package tachiyomi.source.local.filter
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
|
||||
sealed class OrderBy(context: Context, selection: Selection) : Filter.Sort(
|
||||
context.stringResource(MR.strings.local_filter_order_by),
|
||||
arrayOf(context.stringResource(MR.strings.title), context.stringResource(MR.strings.date)),
|
||||
selection,
|
||||
) {
|
||||
class Popular(context: Context) : OrderBy(context, Selection(0, true))
|
||||
class Latest(context: Context) : OrderBy(context, Selection(1, false))
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package tachiyomi.source.local.image
|
||||
|
||||
import android.content.Context
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import tachiyomi.core.common.storage.nameWithoutExtension
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.source.local.io.LocalSourceFileSystem
|
||||
import java.io.InputStream
|
||||
|
||||
private const val DEFAULT_COVER_NAME = "cover.jpg"
|
||||
|
||||
class LocalCoverManager(
|
||||
private val context: Context,
|
||||
private val fileSystem: LocalSourceFileSystem,
|
||||
) {
|
||||
|
||||
fun find(mangaUrl: String): UniFile? {
|
||||
return fileSystem.getFilesInMangaDirectory(mangaUrl)
|
||||
// Get all file whose names start with "cover"
|
||||
.filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) }
|
||||
// Get the first actual image
|
||||
.firstOrNull { ImageUtil.isImage(it.name) { it.openInputStream() } }
|
||||
}
|
||||
|
||||
fun update(
|
||||
manga: SManga,
|
||||
inputStream: InputStream,
|
||||
): UniFile? {
|
||||
val directory = fileSystem.getMangaDirectory(manga.url)
|
||||
if (directory == null) {
|
||||
inputStream.close()
|
||||
return null
|
||||
}
|
||||
|
||||
val targetFile = find(manga.url) ?: directory.createFile(DEFAULT_COVER_NAME)!!
|
||||
|
||||
inputStream.use { input ->
|
||||
targetFile.openOutputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
DiskUtil.createNoMediaFile(directory, context)
|
||||
|
||||
manga.thumbnail_url = targetFile.uri.toString()
|
||||
return targetFile
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package tachiyomi.source.local.io
|
||||
|
||||
import com.hippo.unifile.UniFile
|
||||
import tachiyomi.core.common.storage.extension
|
||||
|
||||
object Archive {
|
||||
|
||||
private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "7z", "cb7", "tar", "cbt")
|
||||
|
||||
fun isSupported(file: UniFile): Boolean {
|
||||
return file.extension?.lowercase() in SUPPORTED_ARCHIVE_TYPES
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package tachiyomi.source.local.io
|
||||
|
||||
import com.hippo.unifile.UniFile
|
||||
import tachiyomi.core.common.storage.extension
|
||||
import tachiyomi.source.local.io.Archive.isSupported as isArchiveSupported
|
||||
|
||||
sealed interface Format {
|
||||
data class Directory(val file: UniFile) : Format
|
||||
data class Archive(val file: UniFile) : Format
|
||||
data class Epub(val file: UniFile) : Format
|
||||
|
||||
class UnknownFormatException : Exception()
|
||||
|
||||
companion object {
|
||||
|
||||
fun valueOf(file: UniFile) = when {
|
||||
file.isDirectory -> Directory(file)
|
||||
file.extension.equals("epub", true) -> Epub(file)
|
||||
isArchiveSupported(file) -> Archive(file)
|
||||
else -> throw UnknownFormatException()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package tachiyomi.source.local.io
|
||||
|
||||
import com.hippo.unifile.UniFile
|
||||
import tachiyomi.domain.storage.service.StorageManager
|
||||
|
||||
class LocalSourceFileSystem(
|
||||
private val storageManager: StorageManager,
|
||||
) {
|
||||
|
||||
fun getBaseDirectory(): UniFile? {
|
||||
return storageManager.getLocalSourceDirectory()
|
||||
}
|
||||
|
||||
fun getFilesInBaseDirectory(): List<UniFile> {
|
||||
return getBaseDirectory()?.listFiles().orEmpty().toList()
|
||||
}
|
||||
|
||||
fun getMangaDirectory(name: String): UniFile? {
|
||||
return getBaseDirectory()
|
||||
?.findFile(name)
|
||||
?.takeIf { it.isDirectory }
|
||||
}
|
||||
|
||||
fun getFilesInMangaDirectory(name: String): List<UniFile> {
|
||||
return getMangaDirectory(name)?.listFiles().orEmpty().toList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package tachiyomi.source.local.metadata
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import mihon.core.archive.EpubReader
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Fills manga and chapter metadata using this epub file's metadata.
|
||||
*/
|
||||
fun EpubReader.fillMetadata(manga: SManga, chapter: SChapter) {
|
||||
val ref = getPackageHref()
|
||||
val doc = getPackageDocument(ref)
|
||||
|
||||
val title = doc.getElementsByTag("dc:title").first()
|
||||
val publisher = doc.getElementsByTag("dc:publisher").first()
|
||||
val creator = doc.getElementsByTag("dc:creator").first()
|
||||
val description = doc.getElementsByTag("dc:description").first()
|
||||
var date = doc.getElementsByTag("dc:date").first()
|
||||
if (date == null) {
|
||||
date = doc.select("meta[property=dcterms:modified]").first()
|
||||
}
|
||||
|
||||
creator?.text()?.let { manga.author = it }
|
||||
description?.text()?.let { manga.description = it }
|
||||
|
||||
title?.text()?.let { chapter.name = it }
|
||||
|
||||
if (publisher != null) {
|
||||
chapter.scanlator = publisher.text()
|
||||
} else if (creator != null) {
|
||||
chapter.scanlator = creator.text()
|
||||
}
|
||||
|
||||
if (date != null) {
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault())
|
||||
try {
|
||||
val parsedDate = dateFormat.parse(date.text())
|
||||
if (parsedDate != null) {
|
||||
chapter.date_upload = parsedDate.time
|
||||
}
|
||||
} catch (e: ParseException) {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user