reformat
This commit is contained in:
parent
1df528c0dc
commit
afa960c808
171 changed files with 3458 additions and 1915 deletions
|
@ -1,9 +1,8 @@
|
|||
package ani.dantotsu.parsers
|
||||
|
||||
import android.net.Uri
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.FileUrl
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.asyncMap
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.loadData
|
||||
|
@ -11,6 +10,7 @@ import ani.dantotsu.others.MalSyncBackup
|
|||
import ani.dantotsu.saveData
|
||||
import ani.dantotsu.tryWithSuspend
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,11 @@ abstract class AnimeParser : BaseParser() {
|
|||
/**
|
||||
* Takes ShowResponse.link & ShowResponse.extra (if you added any) as arguments & gives a list of total episodes present on the site.
|
||||
* **/
|
||||
abstract suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode>
|
||||
abstract suspend fun loadEpisodes(
|
||||
animeLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sAnime: SAnime
|
||||
): List<Episode>
|
||||
|
||||
/**
|
||||
* Takes ShowResponse.link, ShowResponse.extra & the Last Largest Episode Number known by app as arguments
|
||||
|
@ -31,10 +35,15 @@ abstract class AnimeParser : BaseParser() {
|
|||
* Returns the latest episode (If overriding, Make sure the episode is actually the latest episode)
|
||||
* Returns null, if no latest episode is found.
|
||||
* **/
|
||||
open suspend fun getLatestEpisode(animeLink: String, extra: Map<String, String>?, sAnime: SAnime, latest: Float): Episode?{
|
||||
open suspend fun getLatestEpisode(
|
||||
animeLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sAnime: SAnime,
|
||||
latest: Float
|
||||
): Episode? {
|
||||
val episodes = loadEpisodes(animeLink, extra, sAnime)
|
||||
val max = episodes
|
||||
.maxByOrNull { it.number.toFloatOrNull()?:0f }
|
||||
.maxByOrNull { it.number.toFloatOrNull() ?: 0f }
|
||||
return max
|
||||
?.takeIf { latest < (it.number.toFloatOrNull() ?: 0.001f) }
|
||||
}
|
||||
|
@ -44,7 +53,11 @@ abstract class AnimeParser : BaseParser() {
|
|||
*
|
||||
* This returns a Map of "Video Server's Name" & "Link/Data" of all the Video Servers present on the site, which can be further used by loadVideoServers() & loadSingleVideoServer()
|
||||
* **/
|
||||
abstract suspend fun loadVideoServers(episodeLink: String, extra: Map<String,String>?, sEpisode: SEpisode): List<VideoServer>
|
||||
abstract suspend fun loadVideoServers(
|
||||
episodeLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sEpisode: SEpisode
|
||||
): List<VideoServer>
|
||||
|
||||
|
||||
/**
|
||||
|
@ -75,10 +88,12 @@ abstract class AnimeParser : BaseParser() {
|
|||
* **/
|
||||
open suspend fun getVideoExtractor(server: VideoServer): VideoExtractor? {
|
||||
var domain = Uri.parse(server.embed.url).host ?: return null
|
||||
if (domain.startsWith("www.")) {domain = domain.substring(4)}
|
||||
if (domain.startsWith("www.")) {
|
||||
domain = domain.substring(4)
|
||||
}
|
||||
|
||||
val extractor: VideoExtractor? = when (domain) {
|
||||
else -> {
|
||||
else -> {
|
||||
println("$name : No extractor found for: $domain | ${server.embed.url}")
|
||||
null
|
||||
}
|
||||
|
@ -98,7 +113,12 @@ abstract class AnimeParser : BaseParser() {
|
|||
*
|
||||
* Doesn't need to be overridden, if the parser is following the norm.
|
||||
* **/
|
||||
open suspend fun loadByVideoServers(episodeUrl: String, extra: Map<String,String>?, sEpisode: SEpisode, callback: (VideoExtractor) -> Unit) {
|
||||
open suspend fun loadByVideoServers(
|
||||
episodeUrl: String,
|
||||
extra: Map<String, String>?,
|
||||
sEpisode: SEpisode,
|
||||
callback: (VideoExtractor) -> Unit
|
||||
) {
|
||||
tryWithSuspend(true) {
|
||||
loadVideoServers(episodeUrl, extra, sEpisode).asyncMap {
|
||||
getVideoExtractor(it)?.apply {
|
||||
|
@ -116,7 +136,13 @@ abstract class AnimeParser : BaseParser() {
|
|||
*
|
||||
* Doesn't need to be overridden, if the parser is following the norm.
|
||||
* **/
|
||||
open suspend fun loadSingleVideoServer(serverName: String, episodeUrl: String, extra: Map<String,String>?, sEpisode: SEpisode, post: Boolean): VideoExtractor? {
|
||||
open suspend fun loadSingleVideoServer(
|
||||
serverName: String,
|
||||
episodeUrl: String,
|
||||
extra: Map<String, String>?,
|
||||
sEpisode: SEpisode,
|
||||
post: Boolean
|
||||
): VideoExtractor? {
|
||||
return tryWithSuspend(post) {
|
||||
loadVideoServers(episodeUrl, extra, sEpisode).apply {
|
||||
find { it.name == serverName }?.also {
|
||||
|
@ -159,27 +185,43 @@ abstract class AnimeParser : BaseParser() {
|
|||
val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else ""
|
||||
var loaded = loadData<ShowResponse>("${saveName}${dub}_$mediaId")
|
||||
if (loaded == null && malSyncBackupName.isNotEmpty())
|
||||
loaded = MalSyncBackup.get(mediaId, malSyncBackupName, selectDub)?.also { saveShowResponse(mediaId, it, true) }
|
||||
loaded = MalSyncBackup.get(mediaId, malSyncBackupName, selectDub)
|
||||
?.also { saveShowResponse(mediaId, it, true) }
|
||||
return loaded
|
||||
}
|
||||
|
||||
override fun saveShowResponse(mediaId: Int, response: ShowResponse?, selected: Boolean) {
|
||||
if (response != null) {
|
||||
checkIfVariablesAreEmpty()
|
||||
setUserText("${if (selected) currContext()!!.getString(R.string.selected) else currContext()!!.getString(R.string.found)} : ${response.name}")
|
||||
setUserText(
|
||||
"${
|
||||
if (selected) currContext()!!.getString(R.string.selected) else currContext()!!.getString(
|
||||
R.string.found
|
||||
)
|
||||
} : ${response.name}"
|
||||
)
|
||||
val dub = if (isDubAvailableSeparately) "_${if (selectDub) "dub" else "sub"}" else ""
|
||||
saveData("${saveName}${dub}_$mediaId", response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyAnimeParser: AnimeParser() {
|
||||
class EmptyAnimeParser : AnimeParser() {
|
||||
override val name: String = "None"
|
||||
override val saveName: String = "None"
|
||||
|
||||
override val isDubAvailableSeparately: Boolean = false
|
||||
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> = emptyList()
|
||||
override suspend fun loadVideoServers(episodeLink: String, extra: Map<String, String>?, sEpisode: SEpisode): List<VideoServer> = emptyList()
|
||||
override suspend fun loadEpisodes(
|
||||
animeLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sAnime: SAnime
|
||||
): List<Episode> = emptyList()
|
||||
|
||||
override suspend fun loadVideoServers(
|
||||
episodeLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sEpisode: SEpisode
|
||||
): List<VideoServer> = emptyList()
|
||||
|
||||
override suspend fun search(query: String): List<ShowResponse> = emptyList()
|
||||
}
|
||||
|
@ -209,7 +251,7 @@ data class Episode(
|
|||
/**
|
||||
* In case, you want to pass extra data
|
||||
* **/
|
||||
val extra: Map<String,String>? = null,
|
||||
val extra: Map<String, String>? = null,
|
||||
|
||||
//SEpisode from Aniyomi
|
||||
val sEpisode: SEpisode? = null
|
||||
|
@ -221,7 +263,7 @@ data class Episode(
|
|||
thumbnail: String,
|
||||
description: String? = null,
|
||||
isFiller: Boolean = false,
|
||||
extra: Map<String,String>? = null
|
||||
extra: Map<String, String>? = null
|
||||
) : this(number, link, title, FileUrl(thumbnail), description, isFiller, extra)
|
||||
|
||||
constructor(
|
||||
|
@ -231,7 +273,7 @@ data class Episode(
|
|||
thumbnail: String,
|
||||
description: String? = null,
|
||||
isFiller: Boolean = false,
|
||||
extra: Map<String,String>? = null,
|
||||
extra: Map<String, String>? = null,
|
||||
sEpisode: SEpisode? = null
|
||||
) : this(number, link, title, FileUrl(thumbnail), description, isFiller, extra, sEpisode)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package ani.dantotsu.parsers
|
||||
|
||||
import ani.dantotsu.Lazier
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import ani.dantotsu.lazyList
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
|
@ -30,8 +30,8 @@ object AnimeSources : WatchSources() {
|
|||
|
||||
|
||||
object HAnimeSources : WatchSources() {
|
||||
private val aList: List<Lazier<BaseParser>> = lazyList(
|
||||
private val aList: List<Lazier<BaseParser>> = lazyList(
|
||||
)
|
||||
|
||||
override val list = listOf(aList,AnimeSources.list).flatten()
|
||||
override val list = listOf(aList, AnimeSources.list).flatten()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package ani.dantotsu.parsers
|
|||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
|
@ -12,21 +11,21 @@ import android.os.Environment
|
|||
import android.provider.MediaStore
|
||||
import android.widget.Toast
|
||||
import ani.dantotsu.FileUrl
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.anime.AnimeNameAdapter
|
||||
import ani.dantotsu.media.manga.ImageData
|
||||
import ani.dantotsu.media.manga.MangaCache
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.Track
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
|
@ -41,13 +40,13 @@ import kotlinx.coroutines.coroutineScope
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.URL
|
||||
import java.net.URLDecoder
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.*
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class AniyomiAdapter {
|
||||
|
@ -107,7 +106,8 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||
} else {
|
||||
var episodeCounter = 1f
|
||||
// Group by season, sort within each season, and then renumber while keeping episode number 0 as is
|
||||
val seasonGroups = res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 }
|
||||
val seasonGroups =
|
||||
res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 }
|
||||
seasonGroups.keys.sorted().flatMap { season ->
|
||||
seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode ->
|
||||
if (episode.episode_number != 0f) { // Skip renumbering for episode number 0
|
||||
|
@ -302,7 +302,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||
return ret
|
||||
}
|
||||
|
||||
suspend fun imageList(chapterLink: String, sChapter: SChapter): List<ImageData>{
|
||||
suspend fun imageList(chapterLink: String, sChapter: SChapter): List<ImageData> {
|
||||
val source = try {
|
||||
extension.sources[sourceLanguage]
|
||||
} catch (e: Exception) {
|
||||
|
@ -348,7 +348,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||
println("Response: ${response.message}")
|
||||
|
||||
// Convert the Response to an InputStream
|
||||
val inputStream = response.body?.byteStream()
|
||||
val inputStream = response.body.byteStream()
|
||||
|
||||
// Convert InputStream to Bitmap
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
|
@ -379,7 +379,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||
val response = httpSource.getImage(page)
|
||||
|
||||
// Convert the Response to an InputStream
|
||||
val inputStream = response.body?.byteStream()
|
||||
val inputStream = response.body.byteStream()
|
||||
|
||||
// Convert InputStream to Bitmap
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
|
@ -578,7 +578,7 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun AniVideoToSaiVideo(aniVideo: eu.kanade.tachiyomi.animesource.model.Video): ani.dantotsu.parsers.Video {
|
||||
private fun AniVideoToSaiVideo(aniVideo: Video): ani.dantotsu.parsers.Video {
|
||||
// Find the number value from the .quality string
|
||||
val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0
|
||||
|
||||
|
@ -608,14 +608,15 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||
// If the format is still undetermined, log an error or handle it appropriately
|
||||
if (format == null) {
|
||||
logger("Unknown video format: $videoUrl")
|
||||
FirebaseCrashlytics.getInstance().recordException(Exception("Unknown video format: $videoUrl"))
|
||||
FirebaseCrashlytics.getInstance()
|
||||
.recordException(Exception("Unknown video format: $videoUrl"))
|
||||
format = VideoType.CONTAINER
|
||||
}
|
||||
val headersMap: Map<String, String> =
|
||||
aniVideo.headers?.toMultimap()?.mapValues { it.value.joinToString() } ?: mapOf()
|
||||
|
||||
|
||||
return ani.dantotsu.parsers.Video(
|
||||
return Video(
|
||||
number,
|
||||
format,
|
||||
FileUrl(videoUrl, headersMap),
|
||||
|
@ -647,7 +648,7 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||
|
||||
private fun findSubtitleType(url: String): SubtitleType? {
|
||||
// First, try to determine the type based on the URL file extension
|
||||
val type: SubtitleType? = when {
|
||||
val type: SubtitleType = when {
|
||||
url.endsWith(".vtt", true) -> SubtitleType.VTT
|
||||
url.endsWith(".ass", true) -> SubtitleType.ASS
|
||||
url.endsWith(".srt", true) -> SubtitleType.SRT
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
package ani.dantotsu.parsers
|
||||
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.loadData
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.saveData
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import java.io.Serializable
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
|
||||
|
||||
abstract class BaseParser {
|
||||
|
@ -62,18 +67,32 @@ abstract class BaseParser {
|
|||
logger("Result: ${it.name}")
|
||||
}
|
||||
val sortedResults = if (results.isNotEmpty()) {
|
||||
results.sortedByDescending { FuzzySearch.ratio(it.name.lowercase(), mediaObj.mainName().lowercase()) }
|
||||
results.sortedByDescending {
|
||||
FuzzySearch.ratio(
|
||||
it.name.lowercase(),
|
||||
mediaObj.mainName().lowercase()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
response = sortedResults.firstOrNull()
|
||||
|
||||
if (response == null || FuzzySearch.ratio(response.name.lowercase(), mediaObj.mainName().lowercase()) < 100) {
|
||||
if (response == null || FuzzySearch.ratio(
|
||||
response.name.lowercase(),
|
||||
mediaObj.mainName().lowercase()
|
||||
) < 100
|
||||
) {
|
||||
setUserText("Searching : ${mediaObj.nameRomaji}")
|
||||
logger("Searching : ${mediaObj.nameRomaji}")
|
||||
val romajiResults = search(mediaObj.nameRomaji)
|
||||
val sortedRomajiResults = if (romajiResults.isNotEmpty()) {
|
||||
romajiResults.sortedByDescending { FuzzySearch.ratio(it.name.lowercase(), mediaObj.nameRomaji.lowercase()) }
|
||||
romajiResults.sortedByDescending {
|
||||
FuzzySearch.ratio(
|
||||
it.name.lowercase(),
|
||||
mediaObj.nameRomaji.lowercase()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
@ -84,8 +103,14 @@ abstract class BaseParser {
|
|||
logger("No exact match found in results. Using closest match from RomajiResults.")
|
||||
closestRomaji
|
||||
} else {
|
||||
val romajiRatio = FuzzySearch.ratio(closestRomaji?.name?.lowercase() ?: "", mediaObj.nameRomaji.lowercase())
|
||||
val mainNameRatio = FuzzySearch.ratio(response.name.lowercase(), mediaObj.mainName().lowercase())
|
||||
val romajiRatio = FuzzySearch.ratio(
|
||||
closestRomaji?.name?.lowercase() ?: "",
|
||||
mediaObj.nameRomaji.lowercase()
|
||||
)
|
||||
val mainNameRatio = FuzzySearch.ratio(
|
||||
response.name.lowercase(),
|
||||
mediaObj.mainName().lowercase()
|
||||
)
|
||||
logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name.lowercase()}")
|
||||
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name?.lowercase() ?: "None"}")
|
||||
|
||||
|
@ -119,7 +144,13 @@ abstract class BaseParser {
|
|||
open fun saveShowResponse(mediaId: Int, response: ShowResponse?, selected: Boolean = false) {
|
||||
if (response != null) {
|
||||
checkIfVariablesAreEmpty()
|
||||
setUserText("${if (selected) currContext()!!.getString(R.string.selected) else currContext()!!.getString(R.string.found)} : ${response.name}")
|
||||
setUserText(
|
||||
"${
|
||||
if (selected) currContext()!!.getString(R.string.selected) else currContext()!!.getString(
|
||||
R.string.found
|
||||
)
|
||||
} : ${response.name}"
|
||||
)
|
||||
saveData("${saveName}_$mediaId", response)
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +198,7 @@ data class ShowResponse(
|
|||
val total: Int? = null,
|
||||
|
||||
//In case you want to sent some extra data
|
||||
val extra : MutableMap<String,String>?=null,
|
||||
val extra: MutableMap<String, String>? = null,
|
||||
|
||||
//SAnime object from Aniyomi
|
||||
val sAnime: SAnime? = null,
|
||||
|
@ -175,10 +206,23 @@ data class ShowResponse(
|
|||
//SManga object from Aniyomi
|
||||
val sManga: SManga? = null
|
||||
) : Serializable {
|
||||
constructor(name: String, link: String, coverUrl: String, otherNames: List<String> = listOf(), total: Int? = null, extra: MutableMap<String, String>?=null)
|
||||
constructor(
|
||||
name: String,
|
||||
link: String,
|
||||
coverUrl: String,
|
||||
otherNames: List<String> = listOf(),
|
||||
total: Int? = null,
|
||||
extra: MutableMap<String, String>? = null
|
||||
)
|
||||
: this(name, link, FileUrl(coverUrl), otherNames, total, extra)
|
||||
|
||||
constructor(name: String, link: String, coverUrl: String, otherNames: List<String> = listOf(), total: Int? = null)
|
||||
constructor(
|
||||
name: String,
|
||||
link: String,
|
||||
coverUrl: String,
|
||||
otherNames: List<String> = listOf(),
|
||||
total: Int? = null
|
||||
)
|
||||
: this(name, link, FileUrl(coverUrl), otherNames, total)
|
||||
|
||||
constructor(name: String, link: String, coverUrl: String, otherNames: List<String> = listOf())
|
||||
|
|
|
@ -2,9 +2,9 @@ package ani.dantotsu.parsers
|
|||
|
||||
import ani.dantotsu.Lazier
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.anime.Episode
|
||||
import ani.dantotsu.media.manga.MangaChapter
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.tryWithSuspend
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package ani.dantotsu.parsers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
|
@ -15,7 +13,11 @@ abstract class MangaParser : BaseParser() {
|
|||
/**
|
||||
* Takes ShowResponse.link and ShowResponse.extra (if any) as arguments & gives a list of total chapters present on the site.
|
||||
* **/
|
||||
abstract suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter>
|
||||
abstract suspend fun loadChapters(
|
||||
mangaLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sManga: SManga
|
||||
): List<MangaChapter>
|
||||
|
||||
/**
|
||||
* Takes ShowResponse.link, ShowResponse.extra & the Last Largest Chapter Number known by app as arguments
|
||||
|
@ -23,9 +25,14 @@ abstract class MangaParser : BaseParser() {
|
|||
* Returns the latest chapter (If overriding, Make sure the chapter is actually the latest chapter)
|
||||
* Returns null, if no latest chapter is found.
|
||||
* **/
|
||||
open suspend fun getLatestChapter(mangaLink: String, extra: Map<String, String>?, sManga: SManga, latest: Float): MangaChapter? {
|
||||
val chapter = loadChapters(mangaLink, extra, sManga)
|
||||
val max = chapter
|
||||
open suspend fun getLatestChapter(
|
||||
mangaLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sManga: SManga,
|
||||
latest: Float
|
||||
): MangaChapter? {
|
||||
val chapter = loadChapters(mangaLink, extra, sManga)
|
||||
val max = chapter
|
||||
.maxByOrNull { MangaNameAdapter.findChapterNumber(it.number) ?: 0f }
|
||||
return max
|
||||
?.takeIf { latest < (MangaNameAdapter.findChapterNumber(it.number) ?: 0.001f) }
|
||||
|
@ -40,13 +47,18 @@ abstract class MangaParser : BaseParser() {
|
|||
open fun getTransformation(): BitmapTransformation? = null
|
||||
}
|
||||
|
||||
class EmptyMangaParser: MangaParser() {
|
||||
class EmptyMangaParser : MangaParser() {
|
||||
override val name: String = "None"
|
||||
override val saveName: String = "None"
|
||||
|
||||
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> = emptyList()
|
||||
override suspend fun loadChapters(
|
||||
mangaLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sManga: SManga
|
||||
): List<MangaChapter> = emptyList()
|
||||
|
||||
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> = emptyList()
|
||||
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> =
|
||||
emptyList()
|
||||
|
||||
override suspend fun search(query: String): List<ShowResponse> = emptyList()
|
||||
}
|
||||
|
@ -82,7 +94,7 @@ data class MangaImage(
|
|||
val useTransformation: Boolean = false,
|
||||
|
||||
val page: Page? = null,
|
||||
) : Serializable{
|
||||
constructor(url: String,useTransformation: Boolean=false, page: Page? = null)
|
||||
: this(FileUrl(url),useTransformation, page)
|
||||
) : Serializable {
|
||||
constructor(url: String, useTransformation: Boolean = false, page: Page? = null)
|
||||
: this(FileUrl(url), useTransformation, page)
|
||||
}
|
||||
|
|
|
@ -15,11 +15,17 @@ object MangaSources : MangaReadSources() {
|
|||
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
|
||||
// Initialize with the first value from StateFlow
|
||||
val initialExtensions = fromExtensions.first()
|
||||
list = createParsersFromExtensions(initialExtensions) + Lazier({ OfflineMangaParser() }, "Downloaded")
|
||||
list = createParsersFromExtensions(initialExtensions) + Lazier(
|
||||
{ OfflineMangaParser() },
|
||||
"Downloaded"
|
||||
)
|
||||
|
||||
// Update as StateFlow emits new values
|
||||
fromExtensions.collect { extensions ->
|
||||
list = createParsersFromExtensions(extensions) + Lazier({ OfflineMangaParser() }, "Downloaded")
|
||||
list = createParsersFromExtensions(extensions) + Lazier(
|
||||
{ OfflineMangaParser() },
|
||||
"Downloaded"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,7 +40,8 @@ object MangaSources : MangaReadSources() {
|
|||
object HMangaSources : MangaReadSources() {
|
||||
val aList: List<Lazier<BaseParser>> = lazyList()
|
||||
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
|
||||
//todo
|
||||
//todo
|
||||
}
|
||||
override val list = listOf(aList,MangaSources.list).flatten()
|
||||
|
||||
override val list = listOf(aList, MangaSources.list).flatten()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
package ani.dantotsu.parsers
|
||||
|
||||
import com.lagradost.nicehttp.Requests
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ abstract class NovelParser : BaseParser() {
|
|||
|
||||
abstract suspend fun loadBook(link: String, extra: Map<String, String>?): Book
|
||||
|
||||
fun List<ShowResponse>.sortByVolume(query:String) : List<ShowResponse> {
|
||||
fun List<ShowResponse>.sortByVolume(query: String): List<ShowResponse> {
|
||||
val sorted = groupBy { res ->
|
||||
val match = volumeRegex.find(res.name)?.groupValues
|
||||
?.firstOrNull { it.isNotEmpty() }
|
||||
|
@ -42,7 +42,12 @@ data class Book(
|
|||
val description: String? = null,
|
||||
val links: List<FileUrl>
|
||||
) {
|
||||
constructor (name: String, img: String, description: String? = null, links: List<String>) : this(
|
||||
constructor (
|
||||
name: String,
|
||||
img: String,
|
||||
description: String? = null,
|
||||
links: List<String>
|
||||
) : this(
|
||||
name,
|
||||
FileUrl(img),
|
||||
description,
|
||||
|
|
|
@ -2,10 +2,10 @@ package ani.dantotsu.parsers
|
|||
|
||||
import android.util.Log
|
||||
import ani.dantotsu.Lazier
|
||||
import ani.dantotsu.parsers.novel.DynamicNovelParser
|
||||
import ani.dantotsu.parsers.novel.NovelExtension
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import ani.dantotsu.parsers.novel.DynamicNovelParser
|
||||
|
||||
object NovelSources : NovelReadSources() {
|
||||
override var list: List<Lazier<BaseParser>> = emptyList()
|
||||
|
|
|
@ -12,7 +12,7 @@ import uy.kohesive.injekt.Injekt
|
|||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
class OfflineMangaParser: MangaParser() {
|
||||
class OfflineMangaParser : MangaParser() {
|
||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||
|
||||
override val hostUrl: String = "Offline"
|
||||
|
@ -32,7 +32,14 @@ class OfflineMangaParser: MangaParser() {
|
|||
if (directory.exists()) {
|
||||
directory.listFiles()?.forEach {
|
||||
if (it.isDirectory) {
|
||||
val chapter = MangaChapter(it.name, "$mangaLink/${it.name}", it.name, null, null, SChapter.create())
|
||||
val chapter = MangaChapter(
|
||||
it.name,
|
||||
"$mangaLink/${it.name}",
|
||||
it.name,
|
||||
null,
|
||||
null,
|
||||
SChapter.create()
|
||||
)
|
||||
chapters.add(chapter)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,10 +58,16 @@ class StringMatcher {
|
|||
return shows // Return original list if no closest show found
|
||||
}
|
||||
logger("Closest show found for $target is ${closestShowAndIndex.first.name}")
|
||||
return listOf(shows[closestIndex]) + shows.subList(0, closestIndex) + shows.subList(closestIndex + 1, shows.size)
|
||||
return listOf(shows[closestIndex]) + shows.subList(0, closestIndex) + shows.subList(
|
||||
closestIndex + 1,
|
||||
shows.size
|
||||
)
|
||||
}
|
||||
|
||||
private fun closestShow(target: String, shows: List<ShowResponse>): Pair<ShowResponse, Int> {
|
||||
private fun closestShow(
|
||||
target: String,
|
||||
shows: List<ShowResponse>
|
||||
): Pair<ShowResponse, Int> {
|
||||
var minDistance = Int.MAX_VALUE
|
||||
var closestShow = ShowResponse("", "", "")
|
||||
var closestIndex = -1
|
||||
|
|
|
@ -56,14 +56,19 @@ abstract class VideoExtractor : Serializable {
|
|||
data class VideoServer(
|
||||
val name: String,
|
||||
val embed: FileUrl,
|
||||
val extraData : Map<String,String>?=null,
|
||||
val extraData: Map<String, String>? = null,
|
||||
val video: eu.kanade.tachiyomi.animesource.model.Video? = null
|
||||
) : Serializable {
|
||||
constructor(name: String, embedUrl: String,extraData: Map<String,String>?=null)
|
||||
: this(name, FileUrl(embedUrl),extraData)
|
||||
constructor(name: String, embedUrl: String, extraData: Map<String, String>? = null)
|
||||
: this(name, FileUrl(embedUrl), extraData)
|
||||
|
||||
constructor(name: String, embedUrl: String,extraData: Map<String,String>?=null, video: eu.kanade.tachiyomi.animesource.model.Video?)
|
||||
: this(name, FileUrl(embedUrl),extraData, video)
|
||||
constructor(
|
||||
name: String,
|
||||
embedUrl: String,
|
||||
extraData: Map<String, String>? = null,
|
||||
video: eu.kanade.tachiyomi.animesource.model.Video?
|
||||
)
|
||||
: this(name, FileUrl(embedUrl), extraData, video)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +122,13 @@ data class Video(
|
|||
val extraNote: String? = null,
|
||||
) : Serializable {
|
||||
|
||||
constructor(quality: Int? = null, videoType: VideoType, url: String, size: Double?, extraNote: String? = null)
|
||||
constructor(
|
||||
quality: Int? = null,
|
||||
videoType: VideoType,
|
||||
url: String,
|
||||
size: Double?,
|
||||
extraNote: String? = null
|
||||
)
|
||||
: this(quality, videoType, FileUrl(url), size, extraNote)
|
||||
|
||||
constructor(quality: Int? = null, videoType: VideoType, url: String, size: Double?)
|
||||
|
@ -149,15 +160,19 @@ data class Subtitle(
|
|||
*
|
||||
* Supports VTT, SRT & ASS
|
||||
* **/
|
||||
val type:SubtitleType = SubtitleType.VTT,
|
||||
val type: SubtitleType = SubtitleType.VTT,
|
||||
) : Serializable {
|
||||
constructor(language: String, url: String, type: SubtitleType = SubtitleType.VTT) : this(language, FileUrl(url), type)
|
||||
constructor(language: String, url: String, type: SubtitleType = SubtitleType.VTT) : this(
|
||||
language,
|
||||
FileUrl(url),
|
||||
type
|
||||
)
|
||||
}
|
||||
|
||||
enum class VideoType{
|
||||
enum class VideoType {
|
||||
CONTAINER, M3U8, DASH
|
||||
}
|
||||
|
||||
enum class SubtitleType{
|
||||
enum class SubtitleType {
|
||||
VTT, ASS, SRT, UNKNOWN
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package ani.dantotsu.parsers.novel
|
||||
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.parsers.Book
|
||||
import ani.dantotsu.parsers.NovelInterface
|
||||
import ani.dantotsu.parsers.NovelParser
|
||||
|
@ -9,13 +8,14 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class NovelAdapter {
|
||||
}
|
||||
class NovelAdapter
|
||||
|
||||
class DynamicNovelParser(extension: NovelExtension.Installed) : NovelParser() {
|
||||
override val volumeRegex = Regex("vol\\.? (\\d+(\\.\\d+)?)|volume (\\d+(\\.\\d+)?)", RegexOption.IGNORE_CASE)
|
||||
override val volumeRegex =
|
||||
Regex("vol\\.? (\\d+(\\.\\d+)?)|volume (\\d+(\\.\\d+)?)", RegexOption.IGNORE_CASE)
|
||||
var extension: NovelExtension.Installed
|
||||
val client = Injekt.get<NetworkHelper>().requestClient
|
||||
|
||||
init {
|
||||
this.extension = extension
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import ani.dantotsu.logger
|
|||
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
|
@ -27,7 +26,9 @@ class NovelExtensionGithubApi {
|
|||
private val novelExtensionManager: NovelExtensionManager by injectLazy()
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val lastExtCheck: Long = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getLong("last_ext_check", 0)?:0
|
||||
private val lastExtCheck: Long =
|
||||
currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||
?.getLong("last_ext_check", 0) ?: 0
|
||||
|
||||
private var requiresFallbackSource = false
|
||||
|
||||
|
@ -72,7 +73,10 @@ class NovelExtensionGithubApi {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List<AnimeExtension.Installed>? {
|
||||
suspend fun checkForUpdates(
|
||||
context: Context,
|
||||
fromAvailableExtensionList: Boolean = false
|
||||
): List<AnimeExtension.Installed>? {
|
||||
// Limit checks to once a day at most
|
||||
if (fromAvailableExtensionList && Date().time < lastExtCheck + 1.days.inWholeMilliseconds) {
|
||||
return null
|
||||
|
@ -81,7 +85,10 @@ class NovelExtensionGithubApi {
|
|||
val extensions = if (fromAvailableExtensionList) {
|
||||
novelExtensionManager.availableExtensionsFlow.value
|
||||
} else {
|
||||
findExtensions().also { context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()?.putLong("last_ext_check", Date().time)?.apply() }
|
||||
findExtensions().also {
|
||||
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
|
||||
?.putLong("last_ext_check", Date().time)?.apply()
|
||||
}
|
||||
}
|
||||
|
||||
val installedExtensions = NovelExtensionLoader.loadExtensions(context)
|
||||
|
@ -143,6 +150,7 @@ class NovelExtensionGithubApi {
|
|||
fun getApkUrl(extension: NovelExtension.Available): String {
|
||||
return "${getUrlPrefix()}apk/${extension.pkgName}.apk"
|
||||
}
|
||||
|
||||
private fun getUrlPrefix(): String {
|
||||
return if (requiresFallbackSource) {
|
||||
FALLBACK_REPO_URL_PREFIX
|
||||
|
@ -152,8 +160,11 @@ class NovelExtensionGithubApi {
|
|||
}
|
||||
}
|
||||
|
||||
private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/dannovels/novel-extensions/main/"
|
||||
private const val FALLBACK_REPO_URL_PREFIX = "https://gcore.jsdelivr.net/gh/dannovels/novel-extensions@latest/"
|
||||
private const val REPO_URL_PREFIX =
|
||||
"https://raw.githubusercontent.com/dannovels/novel-extensions/main/"
|
||||
private const val FALLBACK_REPO_URL_PREFIX =
|
||||
"https://gcore.jsdelivr.net/gh/dannovels/novel-extensions@latest/"
|
||||
|
||||
@Serializable
|
||||
private data class NovelExtensionJsonObject(
|
||||
val name: String,
|
||||
|
|
|
@ -1,34 +1,18 @@
|
|||
package ani.dantotsu.parsers.novel
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.FileObserver
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import ani.dantotsu.parsers.novel.FileObserver.fileObserver
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.launchNow
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import java.io.File
|
||||
import java.lang.Exception
|
||||
|
||||
|
||||
class NovelExtensionFileObserver(private val listener: Listener, private val path: String) : FileObserver(path, CREATE or DELETE or MOVED_FROM or MOVED_TO or MODIFY) {
|
||||
class NovelExtensionFileObserver(private val listener: Listener, private val path: String) :
|
||||
FileObserver(path, CREATE or DELETE or MOVED_FROM or MOVED_TO or MODIFY) {
|
||||
|
||||
init {
|
||||
fileObserver = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts observing the file changes in the directory.
|
||||
*/
|
||||
|
@ -48,10 +32,12 @@ class NovelExtensionFileObserver(private val listener: Listener, private val pat
|
|||
Log.e("NovelExtensionFileObserver", "File created: $fullPath")
|
||||
listener.onExtensionFileCreated(fullPath)
|
||||
}
|
||||
|
||||
DELETE -> {
|
||||
Log.e("NovelExtensionFileObserver", "File deleted: $fullPath")
|
||||
listener.onExtensionFileDeleted(fullPath)
|
||||
}
|
||||
|
||||
MODIFY -> {
|
||||
Log.e("NovelExtensionFileObserver", "File modified: $fullPath")
|
||||
listener.onExtensionFileModified(fullPath)
|
||||
|
|
|
@ -91,8 +91,12 @@ internal class NovelExtensionInstaller(private val context: Context) {
|
|||
val downloadUri = url.toUri()
|
||||
val request = DownloadManager.Request(downloadUri)
|
||||
.setTitle(extension.name)
|
||||
.setMimeType(NovelExtensionInstaller.APK_MIME)
|
||||
.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, downloadUri.lastPathSegment)
|
||||
.setMimeType(APK_MIME)
|
||||
.setDestinationInExternalFilesDir(
|
||||
context,
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
downloadUri.lastPathSegment
|
||||
)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
val id = downloadManager.enqueue(request)
|
||||
activeDownloads[pkgName] = id
|
||||
|
@ -144,11 +148,14 @@ internal class NovelExtensionInstaller(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun installApk(downloadId: Long, uri: Uri, context: Context, pkgName: String) : InstallStep {
|
||||
val sourcePath = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + "/" + uri.lastPathSegment
|
||||
val destinationPath = context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/$pkgName.apk"
|
||||
fun installApk(downloadId: Long, uri: Uri, context: Context, pkgName: String): InstallStep {
|
||||
val sourcePath =
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + "/" + uri.lastPathSegment
|
||||
val destinationPath =
|
||||
context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/$pkgName.apk"
|
||||
|
||||
val destinationPathDirectory = context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/"
|
||||
val destinationPathDirectory =
|
||||
context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/"
|
||||
val destinationPathDirectoryFile = File(destinationPathDirectory)
|
||||
|
||||
|
||||
|
@ -164,7 +171,7 @@ internal class NovelExtensionInstaller(private val context: Context) {
|
|||
if (destinationDir?.exists() == false) {
|
||||
destinationDir.mkdirs()
|
||||
}
|
||||
if(destinationDir?.setWritable(true) == false) {
|
||||
if (destinationDir?.setWritable(true) == false) {
|
||||
Log.e("Install APK", "Failed to set destinationDir to writable.")
|
||||
downloadsRelay.call(downloadId to InstallStep.Error)
|
||||
return InstallStep.Error
|
||||
|
@ -186,7 +193,8 @@ internal class NovelExtensionInstaller(private val context: Context) {
|
|||
}
|
||||
|
||||
fun uninstallApk(pkgName: String, context: Context) {
|
||||
val apkPath = context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/$pkgName.apk"
|
||||
val apkPath =
|
||||
context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/$pkgName.apk"
|
||||
val fileToDelete = File(apkPath)
|
||||
//give write permission to the file
|
||||
if (fileToDelete.exists() && !fileToDelete.canWrite()) {
|
||||
|
|
|
@ -11,9 +11,7 @@ import ani.dantotsu.parsers.NovelInterface
|
|||
import ani.dantotsu.snackString
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import dalvik.system.PathClassLoader
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
||||
import eu.kanade.tachiyomi.util.lang.Hash
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
@ -27,7 +25,10 @@ internal object NovelExtensionLoader {
|
|||
val results = mutableListOf<NovelLoadResult>()
|
||||
//the number of files
|
||||
Log.e("NovelExtensionLoader", "Loading extensions from $installDir")
|
||||
Log.e("NovelExtensionLoader", "Loading extensions from ${File(installDir).listFiles()?.size}")
|
||||
Log.e(
|
||||
"NovelExtensionLoader",
|
||||
"Loading extensions from ${File(installDir).listFiles()?.size}"
|
||||
)
|
||||
File(installDir).setWritable(false)
|
||||
File(installDir).listFiles()?.forEach {
|
||||
//set the file to read only
|
||||
|
@ -48,7 +49,8 @@ internal object NovelExtensionLoader {
|
|||
* contains the required feature flag before trying to load it.
|
||||
*/
|
||||
fun loadExtensionFromPkgName(context: Context, pkgName: String): NovelLoadResult {
|
||||
val path = context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/$pkgName.apk"
|
||||
val path =
|
||||
context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/$pkgName.apk"
|
||||
//make /extensions/novel read only
|
||||
context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/".let {
|
||||
File(it).setWritable(false)
|
||||
|
@ -67,7 +69,10 @@ internal object NovelExtensionLoader {
|
|||
@Suppress("DEPRECATION")
|
||||
fun loadExtension(context: Context, file: File): NovelLoadResult {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
context.packageManager.getPackageArchiveInfo(file.absolutePath, GET_SIGNING_CERTIFICATES)
|
||||
context.packageManager.getPackageArchiveInfo(
|
||||
file.absolutePath,
|
||||
GET_SIGNING_CERTIFICATES
|
||||
)
|
||||
?: return NovelLoadResult.Error(Exception("Failed to load extension"))
|
||||
} else {
|
||||
context.packageManager.getPackageArchiveInfo(file.absolutePath, GET_SIGNATURES)
|
||||
|
@ -75,8 +80,8 @@ internal object NovelExtensionLoader {
|
|||
}
|
||||
val appInfo = packageInfo.applicationInfo
|
||||
?: return NovelLoadResult.Error(Exception("Failed to load Extension Info"))
|
||||
appInfo.sourceDir = file.absolutePath;
|
||||
appInfo.publicSourceDir = file.absolutePath;
|
||||
appInfo.sourceDir = file.absolutePath
|
||||
appInfo.publicSourceDir = file.absolutePath
|
||||
|
||||
val signatureHash = getSignatureHash(packageInfo)
|
||||
|
||||
|
@ -88,13 +93,14 @@ internal object NovelExtensionLoader {
|
|||
}
|
||||
|
||||
val extension = NovelExtension.Installed(
|
||||
packageInfo.applicationInfo?.loadLabel(context.packageManager)?.toString() ?:
|
||||
return NovelLoadResult.Error(Exception("Failed to load Extension Info")),
|
||||
packageInfo.applicationInfo?.loadLabel(context.packageManager)?.toString()
|
||||
?: return NovelLoadResult.Error(Exception("Failed to load Extension Info")),
|
||||
packageInfo.packageName
|
||||
?: return NovelLoadResult.Error(Exception("Failed to load Extension Info")),
|
||||
packageInfo.versionName ?: "",
|
||||
packageInfo.versionCode.toLong() ?: 0,
|
||||
loadSources(context, file,
|
||||
packageInfo.versionCode.toLong(),
|
||||
loadSources(
|
||||
context, file,
|
||||
packageInfo.applicationInfo?.loadLabel(context.packageManager)?.toString()!!
|
||||
),
|
||||
packageInfo.applicationInfo?.loadIcon(context.packageManager)
|
||||
|
@ -126,7 +132,8 @@ internal object NovelExtensionLoader {
|
|||
}
|
||||
Log.e("NovelExtensionLoader", "isFileWritable: ${file.canWrite()}")
|
||||
val classLoader = PathClassLoader(file.absolutePath, null, context.classLoader)
|
||||
val className = "some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className"
|
||||
val className =
|
||||
"some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className"
|
||||
val loadedClass = classLoader.loadClass(className)
|
||||
val instance = loadedClass.newInstance()
|
||||
val novelInterfaceInstance = instance as? NovelInterface
|
||||
|
|
|
@ -2,7 +2,6 @@ package ani.dantotsu.parsers.novel
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.snackString
|
||||
import eu.kanade.tachiyomi.extension.InstallStep
|
||||
|
@ -51,7 +50,7 @@ class NovelExtensionManager(private val context: Context) {
|
|||
init {
|
||||
initNovelExtensions()
|
||||
val path = context.getExternalFilesDir(null)?.absolutePath + "/extensions/novel/"
|
||||
NovelExtensionFileObserver(NovelInstallationListener(),path).register()
|
||||
NovelExtensionFileObserver(NovelInstallationListener(), path).register()
|
||||
}
|
||||
|
||||
private fun initNovelExtensions() {
|
||||
|
@ -129,8 +128,9 @@ class NovelExtensionManager(private val context: Context) {
|
|||
* @param extension The anime extension to be updated.
|
||||
*/
|
||||
fun updateExtension(extension: NovelExtension.Installed): Observable<InstallStep> {
|
||||
val availableExt = _availableNovelExtensionsFlow.value.find { it.pkgName == extension.pkgName }
|
||||
?: return Observable.empty()
|
||||
val availableExt =
|
||||
_availableNovelExtensionsFlow.value.find { it.pkgName == extension.pkgName }
|
||||
?: return Observable.empty()
|
||||
return installExtension(availableExt)
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,8 @@ class NovelExtensionManager(private val context: Context) {
|
|||
* @param pkgName The package name of the uninstalled application.
|
||||
*/
|
||||
private fun unregisterNovelExtension(pkgName: String) {
|
||||
val installedNovelExtension = _installedNovelExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
val installedNovelExtension =
|
||||
_installedNovelExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
if (installedNovelExtension != null) {
|
||||
_installedNovelExtensionsFlow.value -= installedNovelExtension
|
||||
}
|
||||
|
@ -210,10 +211,12 @@ class NovelExtensionManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExtensionFileDeleted(file: File) {
|
||||
val pkgName = file.nameWithoutExtension
|
||||
unregisterNovelExtension(pkgName)
|
||||
}
|
||||
|
||||
override fun onExtensionFileModified(file: File) {
|
||||
NovelExtensionLoader.loadExtension(context, file).let {
|
||||
if (it is NovelLoadResult.Success) {
|
||||
|
@ -235,7 +238,8 @@ class NovelExtensionManager(private val context: Context) {
|
|||
}
|
||||
|
||||
private fun NovelExtension.Installed.updateExists(availableNovelExtension: NovelExtension.Available? = null): Boolean {
|
||||
val availableExt = availableNovelExtension ?: _availableNovelExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
val availableExt = availableNovelExtension
|
||||
?: _availableNovelExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||
if (isUnofficial || availableExt == null) return false
|
||||
|
||||
return (availableExt.versionCode > versionCode)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue