diff --git a/src/en/coloredmanga/build.gradle b/src/en/coloredmanga/build.gradle deleted file mode 100644 index 8b1a265b9..000000000 --- a/src/en/coloredmanga/build.gradle +++ /dev/null @@ -1,8 +0,0 @@ -ext { - extName = 'ColoredManga' - extClass = '.ColoredManga' - extVersionCode = 36 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/coloredmanga/res/mipmap-hdpi/ic_launcher.png b/src/en/coloredmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6cceaf4f9..000000000 Binary files a/src/en/coloredmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/coloredmanga/res/mipmap-mdpi/ic_launcher.png b/src/en/coloredmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 6f06e84d4..000000000 Binary files a/src/en/coloredmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/coloredmanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/coloredmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9e5ee030f..000000000 Binary files a/src/en/coloredmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/coloredmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/coloredmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index db9a7f730..000000000 Binary files a/src/en/coloredmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/coloredmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/coloredmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b21c662fe..000000000 Binary files a/src/en/coloredmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredManga.kt b/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredManga.kt deleted file mode 100644 index c920c2d06..000000000 --- a/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredManga.kt +++ /dev/null @@ -1,439 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.coloredmanga - -import android.util.Base64 -import eu.kanade.tachiyomi.network.GET -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.SManga.Companion.COMPLETED -import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody -import okhttp3.Protocol -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import org.json.JSONObject -import org.jsoup.nodes.Document -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone - -class ColoredManga : HttpSource() { - - override val name = "ColoredManga" - - override val baseUrl = "https://coloredmanga.net" - - override val versionId = 2 - - private val searchUrl = "$baseUrl/manga" - - override val lang = "en" - - override val supportsLatest = true - - private val json: Json by injectLazy() - - private val nameRegex = Regex(" -$") - - private val chapterNameRegex = Regex(" 0+(\\d+)") - - override val client = network.cloudflareClient - .newBuilder() - .addInterceptor(::Intercept) - .build() - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET(searchUrl, headers) - } - - private val dateFormat = SimpleDateFormat("MMM dd, yyyy, HH:mm", Locale.ENGLISH).apply { - timeZone = TimeZone.getTimeZone("UTC") - } - private val chapterDateFormat = SimpleDateFormat("MMM d, yyyy, HH:mm", Locale.ENGLISH).apply { - timeZone = TimeZone.getTimeZone("UTC") - } - override fun popularMangaParse(response: Response): MangasPage = searchMangas(response, sortBy = "pop" to "desc") - - private fun searchMangas(response: Response, title: String = "", filters: FilterList? = null, sortBy: Pair = "" to ""): MangasPage { - var sort = sortBy - val mangas = getMangas(response.asJsoup()).filter { - val genreIncluded: MutableList = mutableListOf() - val genreExcluded: MutableList = mutableListOf() - - val typeIncluded: MutableList = mutableListOf() - val typeExcluded: MutableList = mutableListOf() - - val colorIncluded: MutableList = mutableListOf() - val colorExcluded: MutableList = mutableListOf() - - val statusIncluded: MutableList = mutableListOf() - val statusExcluded: MutableList = mutableListOf() - - filters?.forEach { filter -> - when (filter) { - is SortFilter -> { - sort = filter.getValue() to if (filter.state!!.ascending) "asc" else "desc" - } - is GenreFilter -> { - if (filter.state.isNotEmpty()) { - filter.state.split(",").filter(String::isNotBlank).map { tag -> - val trimmed = tag.trim().lowercase() - if (trimmed.startsWith("-")) { - genreExcluded.add(trimmed) - } else { - genreIncluded.add(trimmed) - } - } - } - } - - is TypeFilter -> { - filter.state.filter { state -> state.isIncluded() }.forEach { tri -> - typeIncluded.add(tri.value) - } - - filter.state.filter { state -> state.isExcluded() }.forEach { tri -> - typeExcluded.add(tri.value) - } - } - is ColorFilter -> { - filter.state.filter { state -> state.isIncluded() }.forEach { tri -> - colorIncluded.add(tri.value) - } - - filter.state.filter { state -> state.isExcluded() }.forEach { tri -> - colorExcluded.add(tri.value) - } - } - is StatusFilter -> { - filter.state.filter { state -> state.isIncluded() }.forEach { tri -> - statusIncluded.add(tri.value) - } - - filter.state.filter { state -> state.isExcluded() }.forEach { tri -> - statusExcluded.add(tri.value) - } - } - else -> {} - } - } - - val includeGenre = genreIncluded.isEmpty() || it.tags.map { genre -> genre.lowercase() }.containsAll(genreIncluded) - val excludeGenre = genreExcluded.isNotEmpty() && it.tags.map { genre -> genre.lowercase() }.containsAll(genreExcluded) - - val includeType = typeIncluded.isEmpty() || typeIncluded.contains(it.type.lowercase()) - val excludeType = typeExcluded.isNotEmpty() && typeExcluded.contains(it.type) - - val includeColor = colorIncluded.isEmpty() || colorIncluded.contains(it.version) - val excludeColor = colorExcluded.isNotEmpty() && colorExcluded.contains(it.version) - - val regularSearch = it.name.contains(title, true) || it.synopsis.contains(title, true) - - includeGenre && !excludeGenre && - includeType && !excludeType && - includeColor && !excludeColor && - regularSearch - } - - val sorted = when (sort.first) { - "pop" -> { - if (sort.second == "desc") { - mangas.sortedByDescending { it.totalViews } - } else { - mangas.sortedBy { it.totalViews } - } - } - "tit" -> { - if (sort.second == "desc") { - mangas.sortedByDescending { it.name } - } else { - mangas.sortedBy { it.name } - } - } - "new" -> { - if (sort.second == "desc") { - mangas.sortedByDescending { - try { - dateFormat.parse(it.date)!!.time - } catch (e: Exception) { - 0L - } - } - } else { - mangas.sortedBy { - try { - dateFormat.parse(it.date)!!.time - } catch (e: Exception) { - 0L - } - } - } - } - else -> { - if (sort.second == "desc") { - mangas.sortedByDescending { - try { - dateFormat.parse(it.chapters.last().date)!!.time - } catch (e: Exception) { - 0L - } - } - } else { - mangas.sortedBy { - try { - dateFormat.parse(it.chapters.last().date)!!.time - } catch (e: Exception) { - 0L - } - } - } - } - } - - val final = sorted.map(::popularManga) - return MangasPage(final, false) - } - - private fun getMangas(doc: Document): List { - val mangasRaw = doc.selectFirst("script:containsData(\\\"cover)")!!.data() - val mangasJson = mangasRaw - .replace(Regex("""\\([\\"])"""), "$1") - .replaceBefore("\"data\":[", "{") - .removeSuffix("]}]\\n\"])") - - return mangasJson.parseAs().data - } - - private fun getManga(url: String): Request { - val formData = MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("id", url.substringAfter("manga/")) - .build() - - val request = Request.Builder() - .url("$baseUrl/api/selectedManga") - .put(formData) - .addHeader("Cache-Control", "no-store") - .build() - - return request - } - - private fun getImage(manga_name: String, volume_name: String? = "", chapter: Chapter, index: String): String { - val chapterNumber = index.padStart(4, '0') - val chapterName = listOf(chapter.number, chapter.title) - .joinToString(" - ") - .trim() - .replace(nameRegex, "") - - val formData = MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("path", "/images/content/$manga_name/$volume_name$chapterName") - .addFormDataPart("number", chapterNumber) - .build() - - val request = Request.Builder() - .url("$baseUrl/api/dynamicImages") - .put(formData) - .addHeader("Cache-Control", "no-store") - .build() - - val response = client.newCall(request).execute().body.string() - - val responseJson = JSONObject(response) - val image = responseJson.getString("image") - - return image - } - - private fun popularManga(manga: Mangas): SManga { - return SManga.create().apply { - title = manga.name - setUrlWithoutDomain("$baseUrl/manga/${manga.id}") - thumbnail_url = "$baseUrl${manga.cover}" - } - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesParse(response: Response): MangasPage = searchMangas(response, sortBy = "lat" to "desc") - - // Search - - override fun fetchSearchManga( - page: Int, - query: String, - filters: FilterList, - ): Observable { - val response = client.newCall(GET(searchUrl, headers)).execute() - - return Observable.just( - searchMangas(response, query, filters), - ) - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(page) - - override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) - - // Details - - override fun mangaDetailsRequest(manga: SManga): Request { - return getManga(manga.url) - } - - override fun getMangaUrl(manga: SManga): String = "$baseUrl${manga.url}" - - override fun mangaDetailsParse(response: Response): SManga { - val manga = response.parseAs() - return SManga.create().apply { - title = manga.name - url = "/manga/${manga.id}" - author = manga.author - artist = manga.artist - genre = manga.tags.joinToString() - description = manga.synopsis - status = when (manga.status.lowercase()) { - "Ongoing" -> SManga.ONGOING - "Cancelled" -> SManga.CANCELLED - "Hiatus" -> SManga.ON_HIATUS - else -> COMPLETED - } - thumbnail_url = "$baseUrl${manga.cover}" - initialized = true - } - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga) - override fun chapterListParse(response: Response): List { - val manga = response.parseAs() - - val listMangas = if (manga.chapters.isNotEmpty()) { - manga.chapters.map { chapter -> - SChapter.create().apply { - name = listOf(chapter.number, chapter.title) - .joinToString(" - ") - .trim() - .replace(nameRegex, "") - .replace(chapterNameRegex, " $1") - - url = "/manga/${manga.id}/${chapter.number}" - date_upload = try { - chapterDateFormat.parse(chapter.date)!!.time - } catch (e: Exception) { - 0L - } - } - } - } else { - manga.volume.flatMap { volume -> - volume.chapters.map { chapter -> - SChapter.create().apply { - name = "${volume.number} | ${chapter.number} - ${chapter.title}".replace(nameRegex, "").replace(chapterNameRegex, " $1") - url = "/manga/${manga.id}/${chapter.number}" - date_upload = try { - chapterDateFormat.parse(chapter.date)!!.time - } catch (e: Exception) { - 0L - } - } - } - } - } - - return listMangas.reversed() - } - - override fun getFilterList(): FilterList = getFilters() - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return getManga(chapter.url.substringBeforeLast("/")) - } - - override fun fetchPageList(chapter: SChapter): Observable> { - val response = client.newCall(pageListRequest(chapter)).execute() - val manga = response.parseAs() - val volumes = manga.chapters.isEmpty() || manga.volume.isNotEmpty() - chapter.apply { name = chapter.url.substringAfterLast("/") } - - val spChapter = if (volumes) { - manga.volume - .flatMap { it.chapters } - .find { it.number == chapter.name } - } else { - manga.chapters.find { it.number == chapter.name } - } - val chapterJson = spChapter!!.toJson() - - return Observable.just( - List(spChapter.totalImage) { - val url = "https://127.0.0.1/#${it + 1}+${manga.name}" - val volumeInfo = if (volumes) { - manga.volume.find { vol -> vol.chapters.any { chap -> chap.number == chapter.name } } - ?.let { vol -> - listOf(vol.number, vol.title) - .joinToString(" - ") - .trim() - .replace(nameRegex, "") + "/" - } ?: "" - } else { - "" - } - Page(it, url = "$baseUrl${chapter.url}", imageUrl = "$url&volume=$volumeInfo&chapter=$chapterJson") - }, - ) - } - - private val mediaTypePattern = Regex("""(^[^;,]*)[;,]""") - private fun Intercept(chain: Interceptor.Chain): Response { - val yurl = chain.request().url - return if (yurl.toString().startsWith("https://127.0.0.1/#")) { - val index = yurl.fragment!!.substringBefore("+") - val manga_name = yurl.fragment!!.substringAfter("+").substringBefore("&chapter=").substringBefore("&volume=") - val volume_name = yurl.fragment!!.substringAfter("&volume=").substringBefore("&chapter=") - val chapter = (yurl.fragment!!.substringAfter("chapter=")).parseAs() - - val image = getImage(manga_name, volume_name, chapter, index) - - val dataString = image.substringAfter(":") - val byteArray = Base64.decode(dataString.substringAfter("base64,"), Base64.DEFAULT) - val mediaType = mediaTypePattern.find(dataString)!!.value.toMediaTypeOrNull() - Response.Builder().body(byteArray.toResponseBody(mediaType)) - .request(chain.request()) - .protocol(Protocol.HTTP_1_0) - .code(200) - .message("") - .build() - } else { - chain.proceed(chain.request()) - } - } - private inline fun String.parseAs(): T { - return json.decodeFromString(this) - } - - private inline fun Response.parseAs(): T { - return json.decodeFromString(body.string()) - } - - private fun Chapter.toJson(): String { - return json.encodeToString(this) - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() - override fun pageListParse(response: Response): List = throw UnsupportedOperationException() -} diff --git a/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredMangaDto.kt b/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredMangaDto.kt deleted file mode 100644 index cf7205d8a..000000000 --- a/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredMangaDto.kt +++ /dev/null @@ -1,43 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.coloredmanga - -import kotlinx.serialization.Serializable - -@Serializable -class Mangas( - val id: String, - val name: String, - val date: String, - val tags: List, - val volume: List = listOf(), - val chapters: List = listOf(), - val totalViews: Int, - val synopsis: String, - val author: String, - val artist: String, - val cover: String, - val status: String, - val version: String, - val type: String, -) - -@Serializable -class MangasList( - val data: List, -) - -@Serializable -class Volume( - val id: String, - val title: String = "", - val number: String, - val chapters: List, -) - -@Serializable -class Chapter( - val id: String, - val title: String = "", - val number: String, - val date: String, - val totalImage: Int, -) diff --git a/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredMangaFilters.kt b/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredMangaFilters.kt deleted file mode 100644 index c9a896693..000000000 --- a/src/en/coloredmanga/src/eu/kanade/tachiyomi/extension/en/coloredmanga/ColoredMangaFilters.kt +++ /dev/null @@ -1,63 +0,0 @@ -@file:JvmName("ColoredMangaKt") - -package eu.kanade.tachiyomi.extension.en.coloredmanga - -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList - -fun getFilters(): FilterList { - return FilterList( - SortFilter("Sort by", Filter.Sort.Selection(0, false), getSortsList), - TypeFilter("Types"), - ColorFilter("Color"), - StatusFilter("Status"), - GenreFilter("Genre"), - ) -} - -internal class ColorFilter(name: String) : - Filter.Group( - name, - listOf( - "B/W", - "Color", - ).map { TriFilter(it, it.lowercase()) }, - ) - -internal class TypeFilter(name: String) : - Filter.Group( - name, - listOf( - "Manga", - "Manhwa", - ).map { TriFilter(it, it.lowercase()) }, - ) - -internal class StatusFilter(name: String) : - Filter.Group( - name, - listOf( - "Ongoing", - "Completed", - "Cancelled", - "Hiatus", - ).map { TriFilter(it, it.lowercase()) }, - ) - -internal class GenreFilter(name: String) : TextFilter(name) - -internal open class TriFilter(name: String, val value: String) : Filter.TriState(name) - -internal open class TextFilter(name: String) : Filter.Text(name) - -internal open class SortFilter(name: String, selection: Selection, private val vals: List>) : - Filter.Sort(name, vals.map { it.first }.toTypedArray(), selection) { - fun getValue() = vals[state!!.index].second -} - -private val getSortsList: List> = listOf( - Pair("Last Updated", "lat"), - Pair("Newest", "new"), - Pair("Popularity", "pop"), - Pair("Title", "tit"), -)