This repository has been archived by the owner on Jan 21, 2024. It is now read-only.
forked from brahmkshatriya/saikou
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,170 additions
and
0 deletions.
There are no files selected for viewing
165 changes: 165 additions & 0 deletions
165
app/src/main/java/ani/saikou/parsers/manga/sources/AllAnime.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package ani.saikou.parsers.manga.sources | ||
|
||
import ani.saikou.connections.anilist.Anilist | ||
import ani.saikou.client | ||
import ani.saikou.parsers.manga.MangaChapter | ||
import ani.saikou.parsers.manga.MangaImage | ||
import ani.saikou.parsers.manga.MangaParser | ||
import ani.saikou.parsers.ShowResponse | ||
import ani.saikou.tryWithSuspend | ||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
import okhttp3.HttpUrl.Companion.toHttpUrl | ||
import java.text.DecimalFormat | ||
|
||
class AllAnime : MangaParser() { | ||
override val name = "AllAnime" | ||
override val saveName = "all_anime_manga" | ||
override val hostUrl = "https://allmanga.to" | ||
|
||
private val ytAnimeCoversHost = "https://wp.youtube-anime.com/aln.youtube-anime.com" | ||
private val idRegex = Regex("${hostUrl}/manga/(\\w+)") | ||
private val epNumRegex = Regex("/[sd]ub/(\\d+)") | ||
|
||
private val idHash = "affde0163d56435e1c5378d50b3ef0b3aa614c83936a6833e6e5f0fd142055b4" | ||
private val episodeInfoHash = "c8f3ac51f598e630a1d09d7f7fb6924cff23277f354a23e473b962a367880f7d" | ||
private val searchHash = "a27e57ef5de5bae714db701fb7b5cf57e13d57938fc6256f7d5c70a975d11f3d" | ||
private val chapterHash = "d877ecac37a54bd0599836d275acbb30a53d6ff50780c5da1f6c76048eebb388" | ||
|
||
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?): List<MangaChapter> { | ||
val showId = idRegex.find(mangaLink)?.groupValues?.get(1)!! | ||
val episodeInfos = getEpisodeInfos(showId)!! | ||
val format = DecimalFormat("#####.#####") | ||
|
||
return episodeInfos.sortedBy { it.episodeIdNum }.map { epInfo -> | ||
val link = """${hostUrl}/manga/$showId/chapters/sub/${epInfo.episodeIdNum}""" | ||
val epNum = format.format(epInfo.episodeIdNum).toString() | ||
MangaChapter(epNum, link, epInfo.notes) | ||
} | ||
} | ||
|
||
override suspend fun loadImages(chapterLink: String): List<MangaImage> { | ||
val showId = idRegex.find(chapterLink)?.groupValues?.get(1)!! | ||
val episodeNum = epNumRegex.find(chapterLink)?.groupValues?.get(1)!! | ||
val variables = """{"mangaId":"$showId","translationType":"sub","chapterString":"$episodeNum","limit":1000000}""" | ||
val chapterPages = graphqlQuery(variables, chapterHash)?.data?.chapterPages?.edges | ||
// For future reference: If pictureUrlHead is null then the link provided is a relative link of the "apivtwo" variety, but it doesn't seem to contain useful images | ||
val chapter = chapterPages?.filter { !it.pictureUrlHead.isNullOrEmpty() }?.get(0)!! | ||
return chapter.pictureUrls.sortedBy { it.num } | ||
.map { MangaImage("""${chapter.pictureUrlHead}${it.url}""") } | ||
|
||
} | ||
|
||
override suspend fun search(query: String): List<ShowResponse> { | ||
|
||
val variables = | ||
"""{"search":{"query":"$query","isManga":true},"limit":26,"page":1,"translationType":"sub","countryOrigin":"ALL"}""" | ||
val edges = | ||
graphqlQuery(variables, searchHash)?.data?.mangas?.edges!! | ||
|
||
return edges.map { show -> | ||
val link = """${hostUrl}/manga/${show.id}""" | ||
val otherNames = mutableListOf<String>() | ||
show.englishName?.let { otherNames.add(it) } | ||
show.nativeName?.let { otherNames.add(it) } | ||
show.altNames?.forEach { otherNames.add(it) } | ||
var thumbnail = show.thumbnail | ||
if (thumbnail.startsWith("mcovers")) { | ||
thumbnail = "$ytAnimeCoversHost/$thumbnail" | ||
} | ||
ShowResponse(show.name, link, thumbnail, otherNames, show.availableChapters.sub) | ||
} | ||
} | ||
|
||
private suspend fun graphqlQuery(variables: String, persistHash: String): Query? { | ||
val extensions = """{"persistedQuery":{"version":1,"sha256Hash":"$persistHash"}}""" | ||
val graphqlUrl = ("https://api.allanime.day/api").toHttpUrl().newBuilder().addQueryParameter("variables", variables) | ||
.addQueryParameter("extensions", extensions).build() | ||
val headers = mutableMapOf<String, String>() | ||
headers["Host"] = "api.allanime.day" | ||
headers["origin"] = "https://allmanga.to" | ||
return tryWithSuspend { | ||
client.get(graphqlUrl.toString(), headers).parsed() | ||
} | ||
} | ||
|
||
private suspend fun getEpisodeInfos(showId: String): List<EpisodeInfo>? { | ||
val variables = """{"ids": "$showId"}""" | ||
|
||
val manga = graphqlQuery(variables, idHash)?.data?.manga | ||
println("manga info : $manga") | ||
if (manga != null) { | ||
val epCount = manga.availableChapters.sub | ||
val epVariables = """{"showId":"manga@$showId","episodeNumStart":0,"episodeNumEnd":${epCount}}""" | ||
return graphqlQuery( | ||
epVariables, | ||
episodeInfoHash | ||
)?.data?.episodeInfos | ||
} | ||
|
||
return null | ||
} | ||
|
||
@Serializable | ||
private data class Query( | ||
@SerialName("data") var data: Data? | ||
) { | ||
@Serializable | ||
data class Data( | ||
@SerialName("manga") val manga: Manga?, | ||
@SerialName("mangas") val mangas: MangasConnection?, | ||
@SerialName("episodeInfos") val episodeInfos: List<EpisodeInfo>?, | ||
@SerialName("chapterPages") val chapterPages: ChapterConnection?, | ||
) | ||
|
||
@Serializable | ||
data class MangasConnection( | ||
@SerialName("edges") val edges: List<Manga> | ||
) | ||
|
||
@Serializable | ||
data class Manga( | ||
@SerialName("_id") val id: String, | ||
@SerialName("name") val name: String, | ||
@SerialName("description") val description: String?, | ||
@SerialName("englishName") val englishName: String?, | ||
@SerialName("nativeName") val nativeName: String?, | ||
@SerialName("thumbnail") val thumbnail: String, | ||
@SerialName("availableChapters") val availableChapters: AvailableChapters, | ||
@SerialName("altNames") val altNames: List<String>? | ||
) | ||
|
||
@Serializable | ||
data class AvailableChapters( | ||
@SerialName("sub") val sub: Int, | ||
@SerialName("raw") val raw: Int | ||
) | ||
|
||
@Serializable | ||
data class ChapterConnection( | ||
@SerialName("edges") val edges: List<Chapter> | ||
) { | ||
@Serializable | ||
data class Chapter( | ||
@SerialName("pictureUrls") val pictureUrls: List<PictureUrl>, | ||
@SerialName("pictureUrlHead") val pictureUrlHead: String? | ||
) | ||
|
||
@Serializable | ||
data class PictureUrl( | ||
@SerialName("num") val num: Int, | ||
@SerialName("url") val url: String | ||
|
||
) | ||
} | ||
} | ||
|
||
@Serializable | ||
private data class EpisodeInfo( | ||
// Episode "numbers" can have decimal values, hence float | ||
@SerialName("episodeIdNum") val episodeIdNum: Float, | ||
@SerialName("notes") val notes: String?, | ||
@SerialName("thumbnails") val thumbnails: List<String>?, | ||
) | ||
|
||
} |
83 changes: 83 additions & 0 deletions
83
app/src/main/java/ani/saikou/parsers/manga/sources/ComickFun.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package ani.saikou.parsers.manga.sources | ||
|
||
import ani.saikou.Mapper | ||
import ani.saikou.client | ||
import ani.saikou.parsers.manga.MangaChapter | ||
import ani.saikou.parsers.manga.MangaImage | ||
import ani.saikou.parsers.manga.MangaParser | ||
import ani.saikou.parsers.ShowResponse | ||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
class ComickFun : MangaParser() { | ||
|
||
override val name = "ComickFun" | ||
override val saveName = "comick_fun" | ||
override val hostUrl = "https://api.comick.app" | ||
|
||
override suspend fun search(query: String): List<ShowResponse> { | ||
val resp = Mapper.parse<List<SearchData>>(client.get("https://api.comick.app/v1.0/search?q=${encode(query)}").text) | ||
|
||
return resp.map { manga -> | ||
val coverimg = "https://meo.comick.pictures/llxWY.jpg" | ||
|
||
val mangaLink = "$hostUrl/comic/${manga.hid}/chapters" | ||
ShowResponse(manga.title,mangaLink,coverimg ,manga.md_titles.map { it.title }) | ||
} | ||
} | ||
|
||
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?): List<MangaChapter> { | ||
val resp = client.get(mangaLink).parsed<MangaChapterData>() | ||
return resp.chapters.reversed().map { | ||
val chapterLink = "$hostUrl/chapter/${it.hid}?tachiyomi=true" | ||
MangaChapter(number = it.chap.toString(), link = chapterLink, title = it.title) | ||
} | ||
} | ||
|
||
override suspend fun loadImages(chapterLink: String): List<MangaImage> { | ||
val resp = client.get(chapterLink).parsed<MangaImageData>() | ||
return resp.chapter.images.map { MangaImage(url = it.url) } | ||
} | ||
|
||
@Serializable | ||
private data class SearchData( | ||
@SerialName("title") val title: String, | ||
@SerialName("id") val id: Int, | ||
@SerialName("hid") val hid: String, | ||
@SerialName("slug") val slug: String, | ||
@SerialName("md_titles") val md_titles: List<MdTitles>, // other titles | ||
@SerialName("md_covers") val md_covers: String, | ||
@SerialName("b2key") val b2key: String, | ||
) { | ||
@Serializable | ||
data class MdTitles( | ||
@SerialName("title") val title: String, | ||
) | ||
} | ||
|
||
@Serializable | ||
private data class MangaChapterData( | ||
@SerialName("chapters") val chapters: List<Chap> | ||
) | ||
|
||
@Serializable | ||
private data class Chap( | ||
val chap: String? = null, | ||
val title: String? = null, | ||
val vol: String? = null, | ||
val lang: String? = null, | ||
val hid: String? = null, | ||
) | ||
|
||
@Serializable | ||
private data class MangaImageData(@SerialName("chapter") val chapter: Chapter) { | ||
|
||
@Serializable | ||
data class Chapter(@SerialName("images") val images: List<Image>) { | ||
|
||
@Serializable | ||
data class Image(@SerialName("url") val url: String) | ||
} | ||
} | ||
|
||
} |
128 changes: 128 additions & 0 deletions
128
app/src/main/java/ani/saikou/parsers/manga/sources/Manga4Life.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package ani.saikou.parsers.manga.sources | ||
|
||
import ani.saikou.Mapper | ||
import ani.saikou.client | ||
import ani.saikou.findBetween | ||
import ani.saikou.parsers.manga.MangaChapter | ||
import ani.saikou.parsers.manga.MangaImage | ||
import ani.saikou.parsers.manga.MangaParser | ||
import ani.saikou.parsers.ShowResponse | ||
import ani.saikou.sortByTitle | ||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
open class Manga4Life : MangaParser() { | ||
|
||
override val name = "Manga4Life" | ||
override val saveName = "manga_see" | ||
override val hostUrl = host | ||
|
||
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?): List<MangaChapter> { | ||
|
||
val json = client.get("$hostUrl/manga/$mangaLink").text.findBetween("vm.Chapters = ", ";")!! | ||
|
||
return Mapper.parse<List<MangaResponse>>(json).reversed().map { | ||
val chap = it.chapter | ||
val num = "${ | ||
if (chap.startsWith("0") || chap.startsWith("1")) "" else "S" + chap[0] + " : " | ||
}${ | ||
chap.drop(1).dropLast(1).toInt() | ||
}${ | ||
if (chap.endsWith("0")) "" else (".${chap[chap.length - 1]}") | ||
}" | ||
val link = hostUrl + "/read-online/$mangaLink" + chapterURLEncode(chap) | ||
MangaChapter(num, link, it.chapterName) | ||
} | ||
} | ||
|
||
override suspend fun loadImages(chapterLink: String): List<MangaImage> { | ||
val str = client.get(chapterLink).text | ||
val server = str.findBetween("vm.CurPathName = ", ";")?.trim('"') ?: return listOf() | ||
var slug = str.findBetween("vm.IndexName = ", ";")?.trim('"') ?: return listOf() | ||
val json = Mapper.parse<ChapterResponse>( | ||
str.findBetween("vm.CurChapter = ", ";") ?: return listOf() | ||
) | ||
slug += json.directory.let { if (it.isEmpty()) "" else "/$it" } | ||
val chap = chapterImage(json.chapter) | ||
|
||
return (1..json.page.toInt()).map { | ||
val link = "https://$server/manga/$slug/$chap-${"000$it".takeLast(3)}.png" | ||
MangaImage(link) | ||
} | ||
} | ||
|
||
override suspend fun search(query: String): List<ShowResponse> { | ||
val list = getSearchData().toMutableList() | ||
list.sortByTitle(query) | ||
return list | ||
} | ||
|
||
companion object { | ||
private const val host = "https://manga4life.com" | ||
private var response: List<ShowResponse>? = null | ||
suspend fun getSearchData(): List<ShowResponse> { | ||
response = if (response != null) response ?: listOf() | ||
else { | ||
val json = client.get("$host/search/").text.findBetween("vm.Directory = ", "\n")!!.replace(";", "") | ||
Mapper.parse<List<SearchResponse>>(json).map { | ||
ShowResponse( | ||
it.s, it.i, "https://temp.compsci88.com/cover/${it.i}.jpg" | ||
) | ||
} | ||
} | ||
return response ?: listOf() | ||
} | ||
} | ||
|
||
private fun chapterURLEncode(e: String): String { | ||
var index = "" | ||
val t = e.substring(0, 1).toInt() | ||
if (1 != t) { | ||
index = "-index-$t" | ||
} | ||
val dgt = when { | ||
e.toInt() < 100100 -> 4 | ||
e.toInt() < 101000 -> 3 | ||
e.toInt() < 110000 -> 2 | ||
else -> 1 | ||
} | ||
val n = e.substring(dgt, e.length - 1) | ||
var suffix = "" | ||
val path = e.substring(e.length - 1).toInt() | ||
if (0 != path) { | ||
suffix = ".$path" | ||
} | ||
return "-chapter-$n$suffix$index.html" | ||
} | ||
|
||
private val chapterImageRegex = Regex("""^0+""") | ||
|
||
private fun chapterImage(e: String, cleanString: Boolean = false): String { | ||
val a = e.substring(1, e.length - 1).let { if (cleanString) it.replace(chapterImageRegex, "") else it } | ||
val b = e.substring(e.length - 1).toInt() | ||
return when { | ||
(b == 0 && a.isNotEmpty()) -> a | ||
(b == 0 && a.isEmpty()) -> "0" | ||
else -> "$a.$b" | ||
} | ||
} | ||
|
||
@Serializable | ||
private data class MangaResponse( | ||
@SerialName("Chapter") val chapter: String, | ||
@SerialName("ChapterName") val chapterName: String? | ||
) | ||
|
||
@Serializable | ||
private data class ChapterResponse( | ||
@SerialName("Chapter") val chapter: String, | ||
@SerialName("Page") val page: String, | ||
@SerialName("Directory") val directory: String | ||
) | ||
|
||
@Serializable | ||
private data class SearchResponse( | ||
val s: String, | ||
val i: String | ||
) | ||
} |
Oops, something went wrong.