diff --git a/common-dependencies.gradle b/common-dependencies.gradle index 888a3df16..b01845e74 100644 --- a/common-dependencies.gradle +++ b/common-dependencies.gradle @@ -1,7 +1,7 @@ // used both in common.gradle and themesources library dependencies { // Lib 1.2, but using specific commit so we don't need to bump up the version - compileOnly "com.github.tachiyomiorg:extensions-lib:a596412" + compileOnly "com.github.jmir1:extensions-lib:e6cc2ea" // These are provided by the app itself compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCake.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCake.kt deleted file mode 100644 index 5114651ce..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCake.kt +++ /dev/null @@ -1,185 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.comicake - -import android.os.Build -import eu.kanade.tachiyomi.extensions.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale - -abstract class ComiCake( - override val name: String, - final override val baseUrl: String, - override val lang: String, - readerEndpoint: String = COMICAKE_DEFAULT_READER_ENDPOINT, - apiEndpoint: String = COMICAKE_DEFAULT_API_ENDPOINT -) : HttpSource() { - - override val versionId = 1 - override val supportsLatest = true - private val readerBase = baseUrl + readerEndpoint - private var apiBase = baseUrl + apiEndpoint - - private val userAgent = "Mozilla/5.0 (" + - "Android ${Build.VERSION.RELEASE}; Mobile) " + - "Tachiyomi/${BuildConfig.VERSION_NAME}" - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", userAgent) - } - - override fun popularMangaRequest(page: Int): Request { - return GET("$apiBase/comics.json?ordering=-created_at&page=$page") // Not actually popular, just latest added to system - } - - override fun popularMangaParse(response: Response): MangasPage { - val res = response.body()!!.string() - return getMangasPageFromComicsResponse(res) - } - - private fun getMangasPageFromComicsResponse(json: String, nested: Boolean = false): MangasPage { - val response = JSONObject(json) - val results = response.getJSONArray("results") - val mangas = ArrayList() - val ids = mutableListOf() - - for (i in 0 until results.length()) { - val obj = results.getJSONObject(i) - if (nested) { - val nestedComic = obj.getJSONObject("comic") - val id = nestedComic.getInt("id") - if (ids.contains(id)) - continue - ids.add(id) - val manga = SManga.create() - manga.url = id.toString() - manga.title = nestedComic.getString("name") - mangas.add(manga) - } else { - val id = obj.getInt("id") - if (ids.contains(id)) - continue - ids.add(id) - mangas.add(parseComicJson(obj)) - } - } - - return MangasPage(mangas, !(response.getString("next").isNullOrEmpty() || response.getString("next") == "null")) - } - - // Shenanigans to allow "open in webview" to show a webpage instead of JSON - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(apiMangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET("$apiBase/comics/${manga.url}.json") - } - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(manga.description!!.substringAfterLast("\n")) - } - - override fun mangaDetailsParse(response: Response): SManga { - val comicJson = JSONObject(response.body()!!.string()) - return parseComicJson(comicJson, true) - } - - private fun parseComicJson(obj: JSONObject, human: Boolean = false) = SManga.create().apply { - url = if (human) { - "$readerBase/series/${obj.getString("slug")}/" - } else { - obj.getInt("id").toString() // Yeah, I know... Feel free to improve on this - } - title = obj.getString("name") - thumbnail_url = obj.getString("cover") - author = parseListNames(obj.getJSONArray("author")) - artist = parseListNames(obj.getJSONArray("artist")) - description = obj.getString("description") + - "\n\n${readerBase}series/${obj.getString("slug")}/" // webpage for "open in webview" - genre = parseListNames(obj.getJSONArray("tags")) - status = SManga.UNKNOWN - } - - private fun parseListNames(arr: JSONArray): String { - val hold = ArrayList(arr.length()) - for (i in 0 until arr.length()) - hold.add(arr.getJSONObject(i).getString("name")) - return hold.sorted().joinToString(", ") - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // TODO filters - return GET("$apiBase/comics.json?page=$page&search=$query") - } - - override fun searchMangaParse(response: Response): MangasPage { - val res = response.body()!!.string() - return getMangasPageFromComicsResponse(res) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiBase/chapters.json?page=$page&expand=comic") - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val res = response.body()!!.string() - return getMangasPageFromComicsResponse(res, true) - } - - private fun parseChapterJson(obj: JSONObject) = SChapter.create().apply { - name = obj.getString("title") // title will always have content, vs. name that's an optional field - chapter_number = (obj.getInt("chapter") + (obj.getInt("subchapter") / 10.0)).toFloat() - date_upload = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", Locale.getDefault()).parse(obj.getString("published_at"))?.time ?: 0L - // TODO scanlator field by adding team to expandable in CC (low priority given the use case of CC) - url = obj.getString("manifest") - } - - override fun chapterListRequest(manga: SManga): Request { - return GET("$apiBase/chapters.json?comic=${manga.url}&ordering=-volume%2C-chapter%2C-subchapter&n=1000", headers) // There's no pagination in Tachiyomi for chapters so we get 1k chapters - } - - override fun chapterListParse(response: Response): List { - val chapterJson = JSONObject(response.body()!!.string()) - val results = chapterJson.getJSONArray("results") - val ret = ArrayList() - for (i in 0 until results.length()) { - ret.add(parseChapterJson(results.getJSONObject(i))) - } - return ret - } - - override fun pageListParse(response: Response): List { - val webPub = JSONObject(response.body()!!.string()) - val readingOrder = webPub.getJSONArray("readingOrder") - val ret = ArrayList() - for (i in 0 until readingOrder.length()) { - val pageUrl = readingOrder.getJSONObject(i).getString("href") - ret.add(Page(i, "", pageUrl)) - } - return ret - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!") - - companion object { - private const val COMICAKE_DEFAULT_API_ENDPOINT = "/api" // Highly unlikely to change - private const val COMICAKE_DEFAULT_READER_ENDPOINT = "/r/" // Can change based on CC config - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCakeGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCakeGenerator.kt deleted file mode 100644 index 7baa3aff5..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicake/ComiCakeGenerator.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.comicake - -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class ComiCakeGenerator : ThemeSourceGenerator { - - override val themePkg = "comicake" - - override val themeClass = "ComiCake" - - override val baseVersionCode: Int = 1 - - override val sources = listOf( - SingleLang("LetItGo Scans", "https://reader.letitgo.scans.today", "en"), - SingleLang("WhimSubs", "https://whimsubs.xyz", "en") - ) - - companion object { - @JvmStatic - fun main(args: Array) { - ComiCakeGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/eromuse/EroMuse.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/eromuse/EroMuse.kt deleted file mode 100644 index 857412f7b..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/eromuse/EroMuse.kt +++ /dev/null @@ -1,438 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.eromuse - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -@ExperimentalStdlibApi -open class EroMuse(override val name: String, override val baseUrl: String) : HttpSource() { - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - /** - * Browse, search, and latest all run through an ArrayDeque of requests that acts as a stack we push and pop to/from - * For the fetch functions, we only need to worry about pushing the first page to the stack because subsequent pages - * get pushed to the stack during parseManga(). Page 1's URL must include page=1 if the next page would be page=2, - * if page 2 is path_to/2, nothing special needs to be done. - */ - - // the stack - shouldn't need to touch these except for visibility - protected data class StackItem(val url: String, val pageType: Int) - private lateinit var stackItem: StackItem - protected val pageStack = ArrayDeque() - companion object { - const val VARIOUS_AUTHORS = 0 - const val AUTHOR = 1 - const val SEARCH_RESULTS_OR_BASE = 2 - } - protected lateinit var currentSortingMode: String - - private val albums = getAlbumList() - - // might need to override for new sources - private val nextPageSelector = ".pagination span.current + span a" - protected open val albumSelector = "a.c-tile:has(img):not(:has(.members-only))" - protected open val topLevelPathSegment = "comics/album" - private val pageQueryRegex = Regex("""page=\d+""") - - private fun Document.nextPageOrNull(): String? { - val url = this.location() - return this.select(nextPageSelector).firstOrNull()?.text()?.toIntOrNull()?.let { int -> - if (url.contains(pageQueryRegex)) { - url.replace(pageQueryRegex, "page=$int") - } else { - val httpUrl = HttpUrl.parse(url)!! - val builder = if (httpUrl.pathSegments().last().toIntOrNull() is Int) { - httpUrl.newBuilder().removePathSegment(httpUrl.pathSegments().lastIndex) - } else { - httpUrl.newBuilder() - } - builder.addPathSegment(int.toString()).toString() - } - } - } - - private fun Document.addNextPageToStack() { - this.nextPageOrNull()?.let { pageStack.add(StackItem(it, stackItem.pageType)) } - } - - protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src") - - private fun mangaFromElement(element: Element): SManga { - return SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.text() - thumbnail_url = element.select("img").firstOrNull()?.imgAttr() - } - } - - protected fun getAlbumType(url: String, default: Int = AUTHOR): Int { - return albums.filter { it.third != SEARCH_RESULTS_OR_BASE && url.contains(it.second, true) } - .getOrElse(0) { Triple(null, null, default) }.third - } - - protected fun parseManga(document: Document): MangasPage { - fun internalParse(internalDocument: Document): List { - val authorDocument = if (stackItem.pageType == VARIOUS_AUTHORS) { - internalDocument.select(albumSelector)?.let { - elements -> - elements.reversed().map { pageStack.addLast(StackItem(it.attr("abs:href"), AUTHOR)) } - } - client.newCall(stackRequest()).execute().asJsoup() - } else { - internalDocument - } - authorDocument.addNextPageToStack() - return authorDocument.select(albumSelector).map { mangaFromElement(it) } - } - - if (stackItem.pageType in listOf(VARIOUS_AUTHORS, SEARCH_RESULTS_OR_BASE)) document.addNextPageToStack() - val mangas = when (stackItem.pageType) { - VARIOUS_AUTHORS -> { - document.select(albumSelector)?.let { - elements -> - elements.reversed().map { pageStack.addLast(StackItem(it.attr("abs:href"), AUTHOR)) } - } - internalParse(document) - } - AUTHOR -> { - internalParse(document) - } - SEARCH_RESULTS_OR_BASE -> { - val searchMangas = mutableListOf() - document.select(albumSelector) - .map { element -> - val url = element.attr("abs:href") - val depth = url.removePrefix("$baseUrl/$topLevelPathSegment/").split("/").count() - - when (getAlbumType(url)) { - VARIOUS_AUTHORS -> { - when (depth) { - 1 -> { // eg. /comics/album/Fakku-Comics - pageStack.addLast(StackItem(url, VARIOUS_AUTHORS)) - if (searchMangas.isEmpty()) searchMangas += internalParse(client.newCall(stackRequest()).execute().asJsoup()) else null - } - 2 -> { // eg. /comics/album/Fakku-Comics/Bosshi - pageStack.addLast(StackItem(url, AUTHOR)) - if (searchMangas.isEmpty()) searchMangas += internalParse(client.newCall(stackRequest()).execute().asJsoup()) else null - } - else -> { - // eg. 3 -> /comics/album/Fakku-Comics/Bosshi/After-Summer-After - // eg. 5 -> /comics/album/Various-Authors/Firollian/Reward/Reward-22/ElfAlfie - // eg. 6 -> /comics/album/Various-Authors/Firollian/Area69/Area69-no_1/SamusAran/001_Dialogue - searchMangas.add(mangaFromElement(element)) - } - } - } - AUTHOR -> { - if (depth == 1) { // eg. /comics/album/ShadBase-Comics - pageStack.addLast(StackItem(url, AUTHOR)) - if (searchMangas.isEmpty()) searchMangas += internalParse(client.newCall(stackRequest()).execute().asJsoup()) else null - } else { - // eg. 2 -> /comics/album/ShadBase-Comics/RickMorty - // eg. 3 -> /comics/album/Incase-Comics/Comic/Alfie - searchMangas.add(mangaFromElement(element)) - } - } - else -> null // SEARCH_RESULTS_OR_BASE shouldn't be a case - } - } - searchMangas - } - else -> emptyList() - } - return MangasPage(mangas, pageStack.isNotEmpty()) - } - - protected fun stackRequest(): Request { - stackItem = pageStack.removeLast() - val url = if (stackItem.pageType == AUTHOR && currentSortingMode.isNotEmpty() && !stackItem.url.contains("sort")) { - HttpUrl.parse(stackItem.url)!!.newBuilder().addQueryParameter("sort", currentSortingMode).toString() - } else { - stackItem.url - } - return GET(url, headers) - } - - // Popular - - protected fun fetchManga(url: String, page: Int, sortingMode: String): Observable { - if (page == 1) { - pageStack.clear() - pageStack.addLast(StackItem(url, VARIOUS_AUTHORS)) - currentSortingMode = sortingMode - } - - return client.newCall(stackRequest()) - .asObservableSuccess() - .map { response -> parseManga(response.asJsoup()) } - } - - override fun fetchPopularManga(page: Int): Observable = fetchManga("$baseUrl/comics/album/Various-Authors", page, "") - - override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Latest - - override fun fetchLatestUpdates(page: Int): Observable = fetchManga("$baseUrl/comics/album/Various-Authors?sort=date", page, "date") - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - if (page == 1) { - pageStack.clear() - - val filterList = if (filters.isEmpty()) getFilterList() else filters - currentSortingMode = filterList.filterIsInstance().first().toQueryValue() - - if (query.isNotBlank()) { - val url = HttpUrl.parse("$baseUrl/search?q=$query")!!.newBuilder().apply { - if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) - addQueryParameter("page", "1") - } - pageStack.addLast(StackItem(url.toString(), SEARCH_RESULTS_OR_BASE)) - } else { - val albumFilter = filterList.filterIsInstance().first().selection() - val url = HttpUrl.parse("$baseUrl/comics/${albumFilter.pathSegments}")!!.newBuilder().apply { - if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) - if (albumFilter.pageType != AUTHOR) addQueryParameter("page", "1") - } - pageStack.addLast(StackItem(url.toString(), albumFilter.pageType)) - } - } - - return client.newCall(stackRequest()) - .asObservableSuccess() - .map { response -> parseManga(response.asJsoup()) } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(response: Response): SManga { - return SManga.create().apply { - with(response.asJsoup()) { - setUrlWithoutDomain(response.request().url().toString()) - thumbnail_url = select("$albumSelector img").firstOrNull()?.imgAttr() - author = when (getAlbumType(url)) { - AUTHOR -> { - // eg. https://comics.8muses.com/comics/album/ShadBase-Comics/RickMorty - // eg. https://comics.8muses.com/comics/album/Incase-Comics/Comic/Alfie - select("div.top-menu-breadcrumb li:nth-child(2)").text() - } - VARIOUS_AUTHORS -> { - // eg. https://comics.8muses.com/comics/album/Various-Authors/NLT-Media/A-Sunday-Schooling - select("div.top-menu-breadcrumb li:nth-child(3)").text() - } - else -> null - } - } - } - } - - // Chapters - - protected open val linkedChapterSelector = "a.c-tile:has(img)[href*=/comics/album/]" - protected open val pageThumbnailSelector = "a.c-tile:has(img)[href*=/comics/picture/] img" - - override fun chapterListParse(response: Response): List { - fun parseChapters(document: Document, isFirstPage: Boolean, chapters: ArrayDeque): List { - // Linked chapters - document.select(linkedChapterSelector) - .mapNotNull { - chapters.addFirst( - SChapter.create().apply { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - ) - } - - if (isFirstPage) { - // Self - document.select(pageThumbnailSelector).firstOrNull()?.let { - chapters.add( - SChapter.create().apply { - name = "Chapter" - setUrlWithoutDomain(response.request().url().toString()) - } - ) - } - } - - document.nextPageOrNull()?.let { url -> parseChapters(client.newCall(GET(url, headers)).execute().asJsoup(), false, chapters) } - return chapters - } - - return parseChapters(response.asJsoup(), true, ArrayDeque()) - } - - // Pages - - protected open val pageThumbnailPathSegment = "/th/" - protected open val pageFullSizePathSegment = "/fl/" - - override fun pageListParse(response: Response): List { - fun parsePages( - document: Document, - nestedChapterDocuments: ArrayDeque = ArrayDeque(), - pages: ArrayList = ArrayList() - ): List { - - // Nested chapters aka folders - document.select(linkedChapterSelector) - .mapNotNull { - nestedChapterDocuments.add( - client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup() - ) - } - - var lastPage: Int = pages.size - pages.addAll( - document.select(pageThumbnailSelector).mapIndexed { i, img -> - Page(lastPage + i, "", img.imgAttr().replace(pageThumbnailPathSegment, pageFullSizePathSegment)) - } - ) - - document.nextPageOrNull()?.let { - url -> - pages.addAll(parsePages(client.newCall(GET(url, headers)).execute().asJsoup(), nestedChapterDocuments, pages)) - } - - while (!nestedChapterDocuments.isEmpty()) { - pages.addAll(parsePages(nestedChapterDocuments.removeFirst())) - } - - return pages - } - - return parsePages(response.asJsoup()) - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList(): FilterList { - return FilterList( - Filter.Header("Text search only combines with sort!"), - Filter.Separator(), - AlbumFilter(getAlbumList()), - SortFilter(getSortList()) - ) - } - - protected class AlbumFilter(private val vals: Array>) : Filter.Select("Album", vals.map { it.first }.toTypedArray()) { - fun selection() = AlbumFilterData(vals[state].second, vals[state].third) - data class AlbumFilterData(val pathSegments: String, val pageType: Int) - } - protected open fun getAlbumList() = arrayOf( - Triple("All Authors", "", SEARCH_RESULTS_OR_BASE), - Triple("Various Authors", "album/Various-Authors", VARIOUS_AUTHORS), - Triple("Fakku Comics", "album/Fakku-Comics", VARIOUS_AUTHORS), - Triple("Hentai and Manga English", "album/Hentai-and-Manga-English", VARIOUS_AUTHORS), - Triple("Fake Celebrities Sex Pictures", "album/Fake-Celebrities-Sex-Pictures", AUTHOR), - Triple("MilfToon Comics", "album/MilfToon-Comics", AUTHOR), - Triple("BE Story Club Comics", "album/BE-Story-Club-Comics", AUTHOR), - Triple("ShadBase Comics", "album/ShadBase-Comics", AUTHOR), - Triple("ZZZ Comics", "album/ZZZ-Comics", AUTHOR), - Triple("PalComix Comics", "album/PalComix-Comics", AUTHOR), - Triple("MCC Comics", "album/MCC-Comics", AUTHOR), - Triple("Expansionfan Comics", "album/Expansionfan-Comics", AUTHOR), - Triple("JAB Comics", "album/JAB-Comics", AUTHOR), - Triple("Giantess Fan Comics", "album/Giantess-Fan-Comics", AUTHOR), - Triple("Renderotica Comics", "album/Renderotica-Comics", AUTHOR), - Triple("IllustratedInterracial.com Comics", "album/IllustratedInterracial_com-Comics", AUTHOR), - Triple("Giantess Club Comics", "album/Giantess-Club-Comics", AUTHOR), - Triple("Innocent Dickgirls Comics", "album/Innocent-Dickgirls-Comics", AUTHOR), - Triple("Locofuria Comics", "album/Locofuria-Comics", AUTHOR), - Triple("PigKing - CrazyDad Comics", "album/PigKing-CrazyDad-Comics", AUTHOR), - Triple("Cartoon Reality Comics", "album/Cartoon-Reality-Comics", AUTHOR), - Triple("Affect3D Comics", "album/Affect3D-Comics", AUTHOR), - Triple("TG Comics", "album/TG-Comics", AUTHOR), - Triple("Melkormancin.com Comics", "album/Melkormancin_com-Comics", AUTHOR), - Triple("Seiren.com.br Comics", "album/Seiren_com_br-Comics", AUTHOR), - Triple("Tracy Scops Comics", "album/Tracy-Scops-Comics", AUTHOR), - Triple("Fred Perry Comics", "album/Fred-Perry-Comics", AUTHOR), - Triple("Witchking00 Comics", "album/Witchking00-Comics", AUTHOR), - Triple("8muses Comics", "album/8muses-Comics", AUTHOR), - Triple("KAOS Comics", "album/KAOS-Comics", AUTHOR), - Triple("Vaesark Comics", "album/Vaesark-Comics", AUTHOR), - Triple("Fansadox Comics", "album/Fansadox-Comics", AUTHOR), - Triple("DreamTales Comics", "album/DreamTales-Comics", AUTHOR), - Triple("Croc Comics", "album/Croc-Comics", AUTHOR), - Triple("Jay Marvel Comics", "album/Jay-Marvel-Comics", AUTHOR), - Triple("JohnPersons.com Comics", "album/JohnPersons_com-Comics", AUTHOR), - Triple("MuscleFan Comics", "album/MuscleFan-Comics", AUTHOR), - Triple("Taboolicious.xxx Comics", "album/Taboolicious_xxx-Comics", AUTHOR), - Triple("MongoBongo Comics", "album/MongoBongo-Comics", AUTHOR), - Triple("Slipshine Comics", "album/Slipshine-Comics", AUTHOR), - Triple("Everfire Comics", "album/Everfire-Comics", AUTHOR), - Triple("PrismGirls Comics", "album/PrismGirls-Comics", AUTHOR), - Triple("Abimboleb Comics", "album/Abimboleb-Comics", AUTHOR), - Triple("Y3DF - Your3DFantasy.com Comics", "album/Y3DF-Your3DFantasy_com-Comics", AUTHOR), - Triple("Grow Comics", "album/Grow-Comics", AUTHOR), - Triple("OkayOkayOKOk Comics", "album/OkayOkayOKOk-Comics", AUTHOR), - Triple("Tufos Comics", "album/Tufos-Comics", AUTHOR), - Triple("Cartoon Valley", "album/Cartoon-Valley", AUTHOR), - Triple("3DMonsterStories.com Comics", "album/3DMonsterStories_com-Comics", AUTHOR), - Triple("Kogeikun Comics", "album/Kogeikun-Comics", AUTHOR), - Triple("The Foxxx Comics", "album/The-Foxxx-Comics", AUTHOR), - Triple("Theme Collections", "album/Theme-Collections", AUTHOR), - Triple("Interracial-Comics", "album/Interracial-Comics", AUTHOR), - Triple("Expansion Comics", "album/Expansion-Comics", AUTHOR), - Triple("Moiarte Comics", "album/Moiarte-Comics", AUTHOR), - Triple("Incognitymous Comics", "album/Incognitymous-Comics", AUTHOR), - Triple("DizzyDills Comics", "album/DizzyDills-Comics", AUTHOR), - Triple("DukesHardcoreHoneys.com Comics", "album/DukesHardcoreHoneys_com-Comics", AUTHOR), - Triple("Stormfeder Comics", "album/Stormfeder-Comics", AUTHOR), - Triple("Bimbo Story Club Comics", "album/Bimbo-Story-Club-Comics", AUTHOR), - Triple("Smudge Comics", "album/Smudge-Comics", AUTHOR), - Triple("Dollproject Comics", "album/Dollproject-Comics", AUTHOR), - Triple("SuperHeroineComixxx", "album/SuperHeroineComixxx", AUTHOR), - Triple("Karmagik Comics", "album/Karmagik-Comics", AUTHOR), - Triple("Blacknwhite.com Comics", "album/Blacknwhite_com-Comics", AUTHOR), - Triple("ArtOfJaguar Comics", "album/ArtOfJaguar-Comics", AUTHOR), - Triple("Kirtu.com Comics", "album/Kirtu_com-Comics", AUTHOR), - Triple("UberMonkey Comics", "album/UberMonkey-Comics", AUTHOR), - Triple("DarkSoul3D Comics", "album/DarkSoul3D-Comics", AUTHOR), - Triple("Markydaysaid Comics", "album/Markydaysaid-Comics", AUTHOR), - Triple("Central Comics", "album/Central-Comics", AUTHOR), - Triple("Frozen Parody Comics", "album/Frozen-Parody-Comics", AUTHOR), - Triple("Blacknwhitecomics.com Comix", "album/Blacknwhitecomics_com-Comix", AUTHOR) - ) - - protected class SortFilter(private val vals: Array>) : Filter.Select("Sort Order", vals.map { it.first }.toTypedArray()) { - fun toQueryValue() = vals[state].second - } - protected open fun getSortList() = arrayOf( - Pair("Views", ""), - Pair("Likes", "like"), - Pair("Date", "date"), - Pair("A-Z", "az") - ) -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/eromuse/EroMuseGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/eromuse/EroMuseGenerator.kt deleted file mode 100644 index 40a7731a0..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/eromuse/EroMuseGenerator.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.eromuse - -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class EroMuseGenerator : ThemeSourceGenerator { - - override val themePkg = "eromuse" - - override val themeClass = "EroMuse" - - override val baseVersionCode: Int = 1 - - override val sources = listOf( - SingleLang("8Muses", "https://comics.8muses.com", "en", className = "EightMuses", isNsfw = true, overrideVersionCode = 1), - SingleLang("Erofus", "https://www.erofus.com", "en", isNsfw = true, overrideVersionCode = 1) - ) - - companion object { - @JvmStatic - fun main(args: Array) { - EroMuseGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt deleted file mode 100644 index daa70b886..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt +++ /dev/null @@ -1,473 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.fmreader - -import android.util.Base64 -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import java.nio.charset.Charset -import java.util.Calendar - -/** - * For sites based on the Flat-Manga CMS - */ -abstract class FMReader( - override val name: String, - override val baseUrl: String, - override val lang: String -) : ParsedHttpSource() { - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64) Gecko/20100101 Firefox/77.0") - add("Referer", baseUrl) - } - - protected fun Elements.imgAttr(): String? = getImgAttr(this.firstOrNull()) - - private fun Element.imgAttr(): String? = getImgAttr(this) - - open fun getImgAttr(element: Element?): String? { - return when { - element == null -> null - element.hasAttr("data-original") -> element.attr("abs:data-original") - element.hasAttr("data-src") -> element.attr("abs:data-src") - element.hasAttr("data-bg") -> element.attr("abs:data-bg") - else -> element.attr("abs:src") - } - } - - open val requestPath = "manga-list.html" - - open val popularSort = "sort=views" - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/$requestPath?listType=pagination&page=$page&$popularSort&sort_type=DESC", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/$requestPath?")!!.newBuilder() - .addQueryParameter("name", query) - .addQueryParameter("page", page.toString()) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Status -> { - val status = arrayOf("", "1", "2")[filter.state] - url.addQueryParameter("m_status", status) - } - is TextField -> url.addQueryParameter(filter.key, filter.state) - is GenreList -> { - var genre = String() - var ungenre = String() - filter.state.forEach { - if (it.isIncluded()) genre += ",${it.name}" - if (it.isExcluded()) ungenre += ",${it.name}" - } - url.addQueryParameter("genre", genre) - url.addQueryParameter("ungenre", ungenre) - } - is SortBy -> { - url.addQueryParameter( - "sort", - when (filter.state?.index) { - 0 -> "name" - 1 -> "views" - else -> "last_update" - } - ) - if (filter.state?.ascending == true) - url.addQueryParameter("sort_type", "ASC") - } - } - } - return GET(url.toString(), headers) - } - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC", headers) - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) } - - // check if there's a next page - val hasNextPage = (document.select(popularMangaNextPageSelector())?.first()?.text() ?: "").let { - if (it.contains(Regex("""\w*\s\d*\s\w*\s\d*"""))) { - it.split(" ").let { pageOf -> pageOf[1] != pageOf[3] } // current page not last page - } else { - it.isNotEmpty() // standard next page check - } - } - - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesParse(response: Response) = popularMangaParse(response) - - override fun searchMangaParse(response: Response) = popularMangaParse(response) - - override fun popularMangaSelector() = "div.media, .thumb-item-flow" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - open val headerSelector = "h3 a, .series-title a" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("$headerSelector").let { - setUrlWithoutDomain(it.attr("abs:href")) - title = it.text() - } - thumbnail_url = element.select("img, .thumb-wrapper .img-in-ratio").imgAttr() - } - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - /** - * can select one of 2 different types of elements - * one is an element with text "page x of y", must be the first element if it's part of a collection - * the other choice is the standard "next page" element (but most FMReader sources don't have this one) - */ - override fun popularMangaNextPageSelector() = "div.col-lg-9 button.btn-info, .pagination a:contains(»):not(.disabled)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.row").first() - - return SManga.create().apply { - infoElement.select("li a.btn-info").text().let { - if (it.contains("Updating", true).not()) author = it - } - genre = infoElement.select("li a.btn-danger").joinToString { it.text() } - status = parseStatus(infoElement.select("li a.btn-success").first()?.text()) - description = document.select("div.detail .content, div.row ~ div.row:has(h3:first-child) p, .summary-content p").text().trim() - thumbnail_url = infoElement.select("img.thumbnail").imgAttr() - - // add alternative name to manga description - infoElement.select(altNameSelector).firstOrNull()?.ownText()?.let { - if (it.isEmpty().not() && it.contains("Updating", true).not()) { - description += when { - description!!.isEmpty() -> altName + it - else -> "\n\n$altName" + it - } - } - } - } - } - - open val altNameSelector = "li:contains(Other names)" - open val altName = "Alternative Name" // the alt name already contains ": " eg. ": alt name1, alt name2" - - // languages: en, vi, tr - fun parseStatus(status: String?): Int { - val completedWords = setOf("completed", "complete", "incomplete", "đã hoàn thành", "tamamlandı", "hoàn thành") - val ongoingWords = setOf("ongoing", "on going", "updating", "chưa hoàn thành", "đang cập nhật", "devam ediyor", "Đang tiến hành") - return when { - status == null -> SManga.UNKNOWN - completedWords.any { it.equals(status, ignoreCase = true) } -> SManga.COMPLETED - ongoingWords.any { it.equals(status, ignoreCase = true) } -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } - - override fun chapterListParse(response: Response): List { - val document = response.asJsoup() - val mangaTitle = document.select(".manga-info h1, .manga-info h3").text() - return document.select(chapterListSelector()).map { chapterFromElement(it, mangaTitle) }.distinctBy { it.url } - } - - override fun chapterFromElement(element: Element): SChapter { - return chapterFromElement(element, "") - } - - override fun chapterListSelector() = "div#list-chapters p, table.table tr, .list-chapters > a" - - open val chapterUrlSelector = "a" - - open val chapterTimeSelector = "time, .chapter-time" - - open val chapterNameAttrSelector = "title" - - open fun chapterFromElement(element: Element, mangaTitle: String = ""): SChapter { - return SChapter.create().apply { - if (chapterUrlSelector != "") { - element.select(chapterUrlSelector).first().let { - setUrlWithoutDomain(it.attr("abs:href")) - name = it.text().substringAfter("$mangaTitle ") - } - } else { - element.let { - setUrlWithoutDomain(it.attr("abs:href")) - name = element.attr(chapterNameAttrSelector).substringAfter("$mangaTitle ") - } - } - date_upload = element.select(chapterTimeSelector).let { if (it.hasText()) parseChapterDate(it.text()) else 0 } - } - } - - // gets the number from "1 day ago" - open val dateValueIndex = 0 - - // gets the unit of time (day, week hour) from "1 day ago" - open val dateWordIndex = 1 - - private fun parseChapterDate(date: String): Long { - val value = date.split(' ')[dateValueIndex].toInt() - val dateWord = date.split(' ')[dateWordIndex].let { - if (it.contains("(")) { - it.substringBefore("(") - } else { - it.substringBefore("s") - } - } - - // languages: en, vi, es, tr - return when (dateWord) { - "min", "minute", "phút", "minuto", "dakika" -> Calendar.getInstance().apply { - add(Calendar.MINUTE, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "hour", "giờ", "hora", "saat" -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "day", "ngày", "día", "gün" -> Calendar.getInstance().apply { - add(Calendar.DATE, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "week", "tuần", "semana", "hafta" -> Calendar.getInstance().apply { - add(Calendar.DATE, value * 7 * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "month", "tháng", "mes", "ay" -> Calendar.getInstance().apply { - add(Calendar.MONTH, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "year", "năm", "año", "yıl" -> Calendar.getInstance().apply { - add(Calendar.YEAR, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - else -> { - return 0 - } - } - } - - open val pageListImageSelector = "img.chapter-img" - - override fun pageListParse(document: Document): List { - return document.select(pageListImageSelector).mapIndexed { i, img -> - Page(i, document.location(), img.imgAttr()) - } - } - - protected fun base64PageListParse(document: Document): List { - fun Element.decoded(): String { - val attr = - when { - this.hasAttr("data-original") -> "data-original" - this.hasAttr("data-src") -> "data-src" - this.hasAttr("data-srcset") -> "data-srcset" - this.hasAttr("data-aload") -> "data-aload" - else -> "src" - } - return if (!this.attr(attr).contains(".")) { - Base64.decode(this.attr(attr), Base64.DEFAULT).toString(Charset.defaultCharset()) - } else { - this.attr("abs:$attr") - } - } - - return document.select(pageListImageSelector).mapIndexed { i, img -> - Page(i, document.location(), img.decoded()) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Status : Filter.Select("Status", arrayOf("Any", "Completed", "Ongoing")) - class GenreList(genres: List) : Filter.Group("Genre", genres) - class Genre(name: String, val id: String = name.replace(' ', '+')) : Filter.TriState(name) - private class SortBy : Filter.Sort("Sorted By", arrayOf("A-Z", "Most vỉews", "Last updated"), Selection(1, false)) - - // TODO: Country (leftover from original LHTranslation) - override fun getFilterList() = FilterList( - TextField("Author", "author"), - TextField("Group", "group"), - Status(), - SortBy(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll("div.panel-body a")].map((el,i) => `Genre("${el.innerText.trim()}")`).join(',\n') - // on https://lhtranslation.net/search - open fun getGenreList() = listOf( - Genre("Action"), - Genre("18+"), - Genre("Adult"), - Genre("Anime"), - Genre("Comedy"), - Genre("Comic"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Live action"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Martial Art"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("One shot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shojou Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Adventure"), - Genre("Yaoi") - ) - - // from manhwa18.com/search, removed a few that didn't return results/wouldn't be terribly useful - fun getAdultGenreList() = listOf( - Genre("18"), - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Anime"), - Genre("Comedy"), - Genre("Comic"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Live action"), - Genre("Magic"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("Oneshot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of life"), - Genre("Smut"), - Genre("Soft Yaoi"), - Genre("Soft Yuri"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("VnComic"), - Genre("Webtoon") - ) - - // taken from readcomiconline.org/search - fun getComicsGenreList() = listOf( - Genre("Action"), - Genre("Adventure"), - Genre("Anthology"), - Genre("Anthropomorphic"), - Genre("Biography"), - Genre("Children"), - Genre("Comedy"), - Genre("Crime"), - Genre("Drama"), - Genre("Family"), - Genre("Fantasy"), - Genre("Fighting"), - Genre("GraphicNovels"), - Genre("Historical"), - Genre("Horror"), - Genre("LeadingLadies"), - Genre("LGBTQ"), - Genre("Literature"), - Genre("Manga"), - Genre("MartialArts"), - Genre("Mature"), - Genre("Military"), - Genre("Mystery"), - Genre("Mythology"), - Genre("Personal"), - Genre("Political"), - Genre("Post-Apocalyptic"), - Genre("Psychological"), - Genre("Pulp"), - Genre("Religious"), - Genre("Robots"), - Genre("Romance"), - Genre("Schoollife"), - Genre("Sci-Fi"), - Genre("Sliceoflife"), - Genre("Sport"), - Genre("Spy"), - Genre("Superhero"), - Genre("Supernatural"), - Genre("Suspense"), - Genre("Thriller"), - Genre("Vampires"), - Genre("VideoGames"), - Genre("War"), - Genre("Western"), - Genre("Zombies") - ) -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt deleted file mode 100644 index 3c412dff0..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fmreader/FMReaderGenerator.kt +++ /dev/null @@ -1,42 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.fmreader - -import generator.ThemeSourceData.MultiLang -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class FMReaderGenerator : ThemeSourceGenerator { - - override val themePkg = "fmreader" - - override val themeClass = "FMReader" - - override val baseVersionCode: Int = 2 - - /** For future sources: when testing and popularMangaRequest() returns a Jsoup error instead of results - * most likely the fix is to override popularMangaNextPageSelector() */ - - override val sources = listOf( - SingleLang("Epik Manga", "https://www.epikmanga.com", "tr"), - SingleLang("HeroScan", "https://heroscan.com", "en"), - SingleLang("KissLove", "https://kissaway.net", "ja"), - SingleLang("LHTranslation", "https://lhtranslation.net", "en", overrideVersionCode = 1), - SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR"), - SingleLang("ManhuaScan", "https://manhuascan.com", "en", isNsfw = true, overrideVersionCode = 1), - SingleLang("Manhwa18", "https://manhwa18.com", "en", isNsfw = true), - MultiLang("Manhwa18.net", "https://manhwa18.net", listOf("en", "ko"), className = "Manhwa18NetFactory", isNsfw = true), - SingleLang("ManhwaSmut", "https://manhwasmut.com", "en", isNsfw = true, overrideVersionCode = 2), - SingleLang("RawLH", "https://lovehug.net", "ja"), - SingleLang("Say Truyen", "https://saytruyen.com", "vi"), - SingleLang("KSGroupScans", "https://ksgroupscans.com", "en"), - // Sites that went down - //SingleLang("18LHPlus", "https://18lhplus.com", "en", className = "EighteenLHPlus"), - //SingleLang("HanaScan (RawQQ)", "https://hanascan.com", "ja", className = "HanaScanRawQQ"), - ) - - companion object { - @JvmStatic - fun main(args: Array) { - FMReaderGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt deleted file mode 100644 index d68dbe457..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt +++ /dev/null @@ -1,301 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.foolslide - -import com.github.salomonbrys.kotson.get -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Date -import java.util.HashSet -import java.util.Locale - -abstract class FoolSlide( - override val name: String, - override val baseUrl: String, - override val lang: String, - val urlModifier: String = "" -) : ParsedHttpSource() { - - protected open val dedupeLatestUpdates = true - - override val supportsLatest = true - - override fun popularMangaSelector() = "div.group" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl$urlModifier/directory/$page/", headers) - } - - val latestUpdatesUrls = HashSet() - - override fun latestUpdatesParse(response: Response): MangasPage { - val mp = super.latestUpdatesParse(response) - return if (dedupeLatestUpdates) { - val mangas = mp.mangas.distinctBy { it.url }.filterNot { latestUpdatesUrls.contains(it.url) } - latestUpdatesUrls.addAll(mangas.map { it.url }) - MangasPage(mangas, mp.hasNextPage) - } else mp - } - - override fun latestUpdatesSelector() = "div.group" - - override fun latestUpdatesRequest(page: Int): Request { - if (page == 1) { - latestUpdatesUrls.clear() - } - return GET("$baseUrl$urlModifier/latest/$page/") - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - element.select("a[title]").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - - element.select("img").first()?.let { - manga.thumbnail_url = it.absUrl("src").replace("/thumb_", "/") - } - - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a[title]").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun popularMangaNextPageSelector() = "div.next" - - override fun latestUpdatesNextPageSelector(): String? = "div.next" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val searchHeaders = headersBuilder().add("Content-Type", "application/x-www-form-urlencoded").build() - - val form = FormBody.Builder() - .add("search", query) - - return POST("$baseUrl$urlModifier/search/", searchHeaders, form.build()) - } - - override fun searchMangaSelector() = "div.group" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a[title]").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun searchMangaNextPageSelector() = "a:has(span.next)" - - override fun mangaDetailsRequest(manga: SManga) = allowAdult(super.mangaDetailsRequest(manga)) - - open val mangaDetailsInfoSelector = "div.info" - - // if there's no image on the details page, get the first page of the first chapter - fun getDetailsThumbnail(document: Document, urlSelector: String = chapterUrlSelector): String? { - return document.select("div.thumbnail img, table.thumb img").firstOrNull()?.attr("abs:src") - ?: document.select(chapterListSelector()).last().select(urlSelector).attr("abs:href") - .let { url -> client.newCall(allowAdult(GET(url, headers))).execute() } - .let { response -> pageListParse(response).first().imageUrl } - } - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - document.select(mangaDetailsInfoSelector).firstOrNull()?.html()?.let { infoHtml -> - author = Regex("""(?i)(Author|Autore):\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2) - artist = Regex("""Artist:\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(1) - description = Regex("""(?i)(Synopsis|Description|Trama):\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2) - } - thumbnail_url = getDetailsThumbnail(document) - } - } - - /** - * Transform a GET request into a POST request that automatically authorizes all adult content - */ - private fun allowAdult(request: Request) = allowAdult(request.url().toString()) - - private fun allowAdult(url: String): Request { - return POST( - url, - body = FormBody.Builder() - .add("adult", "true") - .build() - ) - } - - override fun chapterListRequest(manga: SManga) = allowAdult(super.chapterListRequest(manga)) - - override fun chapterListSelector() = "div.group div.element, div.list div.element" - - open val chapterDateSelector = "div.meta_r" - - open val chapterUrlSelector = "a[title]" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select(chapterUrlSelector).first() - val dateElement = element.select(chapterDateSelector).first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = dateElement.text()?.let { parseChapterDate(it.substringAfter(", ")) } - ?: 0 - return chapter - } - - open fun parseChapterDate(date: String): Long? { - val lcDate = date.toLowerCase() - if (lcDate.endsWith(" ago")) - parseRelativeDate(lcDate)?.let { return it } - - // Handle 'yesterday' and 'today', using midnight - var relativeDate: Calendar? = null - // Result parsed but no year, copy current year over - when { - lcDate.startsWith("yesterday") -> { - relativeDate = Calendar.getInstance() - relativeDate.add(Calendar.DAY_OF_MONTH, -1) // yesterday - relativeDate.set(Calendar.HOUR_OF_DAY, 0) - relativeDate.set(Calendar.MINUTE, 0) - relativeDate.set(Calendar.SECOND, 0) - relativeDate.set(Calendar.MILLISECOND, 0) - } - lcDate.startsWith("today") -> { - relativeDate = Calendar.getInstance() - relativeDate.set(Calendar.HOUR_OF_DAY, 0) - relativeDate.set(Calendar.MINUTE, 0) - relativeDate.set(Calendar.SECOND, 0) - relativeDate.set(Calendar.MILLISECOND, 0) - } - lcDate.startsWith("tomorrow") -> { - relativeDate = Calendar.getInstance() - relativeDate.add(Calendar.DAY_OF_MONTH, +1) // tomorrow - relativeDate.set(Calendar.HOUR_OF_DAY, 0) - relativeDate.set(Calendar.MINUTE, 0) - relativeDate.set(Calendar.SECOND, 0) - relativeDate.set(Calendar.MILLISECOND, 0) - } - } - - relativeDate?.timeInMillis?.let { - return it - } - - var result = DATE_FORMAT_1.parseOrNull(date) - - for (dateFormat in DATE_FORMATS_WITH_ORDINAL_SUFFIXES) { - if (result == null) - result = dateFormat.parseOrNull(date) - else - break - } - - for (dateFormat in DATE_FORMATS_WITH_ORDINAL_SUFFIXES_NO_YEAR) { - if (result == null) { - result = dateFormat.parseOrNull(date) - - if (result != null) { - // Result parsed but no year, copy current year over - result = Calendar.getInstance().apply { - time = result!! - set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR)) - }.time - } - } else break - } - - return result?.time ?: 0L - } - - /** - * Parses dates in this form: - * `11 days ago` - */ - private fun parseRelativeDate(date: String): Long? { - val trimmedDate = date.split(" ") - - if (trimmedDate[2] != "ago") return null - - val number = trimmedDate[0].toIntOrNull() ?: return null - val unit = trimmedDate[1].removeSuffix("s") // Remove 's' suffix - - val now = Calendar.getInstance() - - // Map English unit to Java unit - val javaUnit = when (unit) { - "year", "yr" -> Calendar.YEAR - "month" -> Calendar.MONTH - "week", "wk" -> Calendar.WEEK_OF_MONTH - "day" -> Calendar.DAY_OF_MONTH - "hour", "hr" -> Calendar.HOUR - "minute", "min" -> Calendar.MINUTE - "second", "sec" -> Calendar.SECOND - else -> return null - } - - now.add(javaUnit, -number) - - return now.timeInMillis - } - - private fun SimpleDateFormat.parseOrNull(string: String): Date? { - return try { - parse(string) - } catch (e: ParseException) { - null - } - } - - override fun pageListRequest(chapter: SChapter) = allowAdult(super.pageListRequest(chapter)) - - override fun pageListParse(document: Document): List { - val doc = document.toString() - val jsonstr = doc.substringAfter("var pages = ").substringBefore(";") - val json = JsonParser().parse(jsonstr).asJsonArray - val pages = mutableListOf() - json.forEach { - // Create dummy element to resolve relative URL - val absUrl = document.createElement("a") - .attr("href", it["url"].asString) - .absUrl("href") - - pages.add(Page(pages.size, "", absUrl)) - } - return pages - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - companion object { - private val ORDINAL_SUFFIXES = listOf("st", "nd", "rd", "th") - private val DATE_FORMAT_1 = SimpleDateFormat("yyyy.MM.dd", Locale.US) - private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES = ORDINAL_SUFFIXES.map { - SimpleDateFormat("dd'$it' MMMM, yyyy", Locale.US) - } - private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES_NO_YEAR = ORDINAL_SUFFIXES.map { - SimpleDateFormat("dd'$it' MMMM", Locale.US) - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt deleted file mode 100644 index aef3e06bb..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt +++ /dev/null @@ -1,59 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.foolslide - -import generator.ThemeSourceData.MultiLang -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class FoolSlideGenerator : ThemeSourceGenerator { - - override val themePkg = "foolslide" - - override val themeClass = "FoolSlide" - - override val baseVersionCode: Int = 1 - - override val sources = listOf( - SingleLang("The Cat Scans", "https://reader2.thecatscans.com/", "en"), - SingleLang("Silent Sky", "https://reader.silentsky-scans.net", "en"), - SingleLang("Death Toll Scans", "https://reader.deathtollscans.net", "en"), - SingleLang("MangaScouts", "http://onlinereader.mangascouts.org", "de"), - SingleLang("Lilyreader", "https://manga.smuglo.li", "en"), - SingleLang("Evil Flowers", "https://reader.evilflowers.com", "en"), - SingleLang("Русификация", "https://rusmanga.ru", "ru", className = "Russification"), - SingleLang("PowerManga", "https://reader.powermanga.org", "it", className = "PowerMangaIT"), - MultiLang("FoolSlide Customizable", "", listOf("other")), - SingleLang("Menudo-Fansub", "https://www.menudo-fansub.com", "es", className = "MenudoFansub", overrideVersionCode = 1), - SingleLang("Sense-Scans", "https://sensescans.com", "en", className = "SenseScans", overrideVersionCode = 1), - SingleLang("Kirei Cake", "https://reader.kireicake.com", "en"), - SingleLang("Mangatellers", "http://www.mangatellers.gr", "en"), - SingleLang("Iskultrip Scans", "https://maryfaye.net", "en"), - SingleLang("Anata no Motokare", "https://motokare.xyz", "en", className = "AnataNoMotokare"), - SingleLang("Yuri-ism", "https://www.yuri-ism.net", "en", className = "YuriIsm"), - SingleLang("Ajia no Scantrad", "https://www.ajianoscantrad.fr", "fr", className = "AjiaNoScantrad"), - SingleLang("Storm in Heaven", "https://www.storm-in-heaven.net", "it", className = "StormInHeaven"), - SingleLang("LupiTeam", "https://lupiteam.net", "it"), - SingleLang("Zandy no Fansub", "https://zandynofansub.aishiteru.org", "en"), - SingleLang("Kirishima Fansub", "https://www.kirishimafansub.net", "es"), - SingleLang("Baixar Hentai", "https://leitura.baixarhentai.net", "pt-BR", isNsfw = true, overrideVersionCode = 1), - MultiLang("HNI-Scantrad", "https://hni-scantrad.com", listOf("fr", "en"), className = "HNIScantradFactory", pkgName = "hniscantrad", overrideVersionCode = 1), - SingleLang("The Phoenix Scans", "https://www.phoenixscans.com", "it", className = "PhoenixScans"), - SingleLang("GTO The Great Site", "https://www.gtothegreatsite.net", "it", className = "GTO"), - SingleLang("Fall World Reader", "https://faworeader.altervista.org", "it", className = "FallenWorldOrder"), - SingleLang("NIFTeam", "http://read-nifteam.info", "it"), - SingleLang("TuttoAnimeManga", "https://tuttoanimemanga.net", "it"), - SingleLang("Tortuga Ceviri", "http://tortuga-ceviri.com", "tr"), - SingleLang("Rama", "https://www.ramareader.it", "it"), - SingleLang("Mabushimajo", "http://mabushimajo.com", "tr"), - SingleLang("Hyakuro", "https://hyakuro.com/reader", "en"), - SingleLang("Le Cercle du Scan", "https://lel.lecercleduscan.com", "fr") - //Sites that are down - //SingleLang("One Time Scans", "https://reader.otscans.com", "en"), - ) - - companion object { - @JvmStatic - fun main(args: Array) { - FoolSlideGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/Genkan.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/Genkan.kt deleted file mode 100644 index 92a32b8ef..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/Genkan.kt +++ /dev/null @@ -1,180 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.genkan - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements - -open class Genkan( - override val name: String, - override val baseUrl: String, - override val lang: String -) : ParsedHttpSource() { - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaSelector() = "div.list-item" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/comics?page=$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - // Track which manga titles have been added to latestUpdates's MangasPage - private val latestUpdatesTitles = mutableSetOf() - - override fun latestUpdatesRequest(page: Int): Request { - if (page == 1) latestUpdatesTitles.clear() - return GET("$baseUrl/latest?page=$page", headers) - } - - // To prevent dupes, only add manga to MangasPage if its title is not one we've added already - override fun latestUpdatesParse(response: Response): MangasPage { - val latestManga = mutableListOf() - val document = response.asJsoup() - - document.select(latestUpdatesSelector()).forEach { element -> - latestUpdatesFromElement(element).let { manga -> - if (manga.title !in latestUpdatesTitles) { - latestManga.add(manga) - latestUpdatesTitles.add(manga.title) - } - } - } - - return MangasPage(latestManga, document.select(latestUpdatesNextPageSelector()).hasText()) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.list-title").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = styleToUrl(element.select("a.media-content").first()) - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "[rel=next]" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/comics?query=$query", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - private fun styleToUrl(element: Element): String { - return element.attr("style").substringAfter("(").substringBefore(")") - .let { if (it.startsWith("http")) it else baseUrl + it } - } - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - title = document.select("div#content h5").first().text() - description = document.select("div.col-lg-9").text().substringAfter("Description ").substringBefore(" Volume") - thumbnail_url = styleToUrl(document.select("div.media a").first()) - } - } - - override fun chapterListSelector() = "div.col-lg-9 div.flex" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - - val urlElement = element.select("a.item-author") - val chapNum = urlElement.attr("href").split("/").last() - - setUrlWithoutDomain(urlElement.attr("href")) - name = if (urlElement.text().contains("Chapter $chapNum")) { - urlElement.text() - } else { - "Ch. $chapNum: ${urlElement.text()}" - } - date_upload = parseChapterDate(element.select("a.item-company").first().text()) ?: 0 - } - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("MMM d, yyyy", Locale.US) - } - } - - // If the date string contains the word "ago" send it off for relative date parsing otherwise use dateFormat - private fun parseChapterDate(string: String): Long? { - return if ("ago" in string) { - parseRelativeDate(string) ?: 0 - } else { - dateFormat.parse(string)?.time ?: 0 - } - } - - // Subtract relative date (e.g. posted 3 days ago) - private fun parseRelativeDate(date: String): Long? { - val trimmedDate = date.substringBefore(" ago").removeSuffix("s").split(" ") - - val calendar = Calendar.getInstance() - when (trimmedDate[1]) { - "year" -> calendar.apply { add(Calendar.YEAR, -trimmedDate[0].toInt()) } - "month" -> calendar.apply { add(Calendar.MONTH, -trimmedDate[0].toInt()) } - "week" -> calendar.apply { add(Calendar.WEEK_OF_MONTH, -trimmedDate[0].toInt()) } - "day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) } - "hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) } - "minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) } - "second" -> calendar.apply { add(Calendar.SECOND, 0) } - } - - return calendar.timeInMillis - } - - override fun pageListParse(document: Document): List { - val pages = mutableListOf() - - val allImages = document.select("div#pages-container + script").first().data() - .substringAfter("[").substringBefore("];") - .replace(Regex("""["\\]"""), "") - .split(",") - - for (i in allImages.indices) { - pages.add(Page(i, "", allImages[i])) - } - - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun imageRequest(page: Page): Request { - return if (page.imageUrl!!.startsWith("http")) GET(page.imageUrl!!, headers) else GET(baseUrl + page.imageUrl!!, headers) - } - - override fun getFilterList() = FilterList() -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanGenerator.kt deleted file mode 100644 index d4fda9efe..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanGenerator.kt +++ /dev/null @@ -1,30 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.genkan - -import generator.ThemeSourceData.MultiLang -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class GenkanGenerator : ThemeSourceGenerator { - - override val themePkg = "genkan" - - override val themeClass = "Genkan" - - override val baseVersionCode: Int = 1 - - override val sources = listOf( - SingleLang("Hunlight Scans", "https://hunlight-scans.info", "en"), - SingleLang("ZeroScans", "https://zeroscans.com", "en"), - SingleLang("The Nonames Scans", "https://the-nonames.com", "en"), - SingleLang("Edelgarde Scans", "https://edelgardescans.com", "en"), - SingleLang("Method Scans", "https://methodscans.com", "en"), - SingleLang("LynxScans", "https://lynxscans.com", "en", overrideVersionCode = 1), - ) - - companion object { - @JvmStatic - fun main(args: Array) { - GenkanGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanOriginal.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanOriginal.kt deleted file mode 100644 index d632f94da..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanOriginal.kt +++ /dev/null @@ -1,75 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.genkan - -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements - -/** -* For sites using the older Genkan CMS that didn't have a search function - */ -open class GenkanOriginal( - override val name: String, - override val baseUrl: String, - override val lang: String -) : Genkan(name, baseUrl, lang) { - - private var searchQuery = "" - private var searchPage = 1 - private var nextPageSelectorElement = Elements() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (page == 1) searchPage = 1 - searchQuery = query - return popularMangaRequest(page) - } - - override fun searchMangaParse(response: Response): MangasPage { - val searchMatches = mutableListOf() - val document = response.asJsoup() - searchMatches.addAll(getMatchesFrom(document)) - - /* call another function if there's more pages to search - not doing it this way can lead to a false "no results found" - if no matches are found on the first page but there are matches - on subsequent pages */ - nextPageSelectorElement = document.select(searchMangaNextPageSelector()) - while (nextPageSelectorElement.hasText()) { - searchMatches.addAll(searchMorePages()) - } - - return MangasPage(searchMatches, false) - } - - // search the given document for matches - private fun getMatchesFrom(document: Document): MutableList { - val searchMatches = mutableListOf() - document.select(searchMangaSelector()) - .filter { it.text().contains(searchQuery, ignoreCase = true) } - .map { searchMatches.add(searchMangaFromElement(it)) } - - return searchMatches - } - - // search additional pages if called - private fun searchMorePages(): MutableList { - searchPage++ - val nextPage = client.newCall(popularMangaRequest(searchPage)).execute().asJsoup() - val searchMatches = mutableListOf() - searchMatches.addAll(getMatchesFrom(nextPage)) - nextPageSelectorElement = nextPage.select(searchMangaNextPageSelector()) - - return searchMatches - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanOriginalGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanOriginalGenerator.kt deleted file mode 100644 index a5792f835..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/genkan/GenkanOriginalGenerator.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.genkan - -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class GenkanOriginalGenerator : ThemeSourceGenerator { - - override val themePkg = "genkan" - - override val themeClass = "GenkanOriginal" - - override val baseVersionCode: Int = 1 - - override val sources = listOf( - SingleLang("Reaper Scans", "https://reaperscans.com", "en"), - SingleLang("Hatigarm Scans", "https://hatigarmscanz.net", "en", overrideVersionCode = 1), - ) - - companion object { - @JvmStatic - fun main(args: Array) { - GenkanOriginalGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/luscious/Luscious.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/luscious/Luscious.kt deleted file mode 100644 index f41b44224..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/luscious/Luscious.kt +++ /dev/null @@ -1,608 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.luscious - -import com.github.salomonbrys.kotson.addProperty -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.set -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -abstract class Luscious( - override val name: String, - override val baseUrl: String, - override val lang: String ) : HttpSource() { - - //Based on Luscios single source extension form https://github.com/tachiyomiorg/tachiyomi-extensions/commit/aacf56d0c0ddb173372aac69d798ae998f178377 - //with modifiaction to make it support multisrc - - override val supportsLatest: Boolean = true - private val apiBaseUrl: String = "$baseUrl/graphql/nobatch/" - private val gson = Gson() - override val client: OkHttpClient = network.cloudflareClient - private val lusLang: String = lusLang(lang) - private fun lusLang(lang: String): String { - return when (lang) { - "en" -> "1" - "ja" -> "2" - "es" -> "3" - "it" -> "4" - "de" -> "5" - "fr" -> "6" - "zh" -> "8" - "ko" -> "9" - "pt" -> "100" - "th" -> "101" - else -> "99" - } - } - - - // Common - - private fun buildAlbumListRequestInput(page: Int, filters: FilterList, query: String = ""): JsonObject { - val sortByFilter = filters.findInstance()!! - val albumTypeFilter = filters.findInstance()!! - val interestsFilter = filters.findInstance()!! - val languagesFilter = filters.findInstance()!! - val tagsFilter = filters.findInstance()!! - val genreFilter = filters.findInstance()!! - val contentTypeFilter = filters.findInstance()!! - - return JsonObject().apply { - add( - "input", - JsonObject().apply { - addProperty("display", sortByFilter.selected) - addProperty("page", page) - add( - "filters", - JsonArray().apply { - - if (contentTypeFilter.selected != FILTER_VALUE_IGNORE) - add(contentTypeFilter.toJsonObject("content_id")) - - if (albumTypeFilter.selected != FILTER_VALUE_IGNORE) - add(albumTypeFilter.toJsonObject("album_type")) - - with(interestsFilter) { - if (this.selected.isEmpty()) { - throw Exception("Please select an Interest") - } - add(this.toJsonObject("audience_ids")) - } - - add( - languagesFilter.toJsonObject("language_ids").apply { - set("value", "+$lusLang${get("value").asString}") - } - ) - - if (tagsFilter.anyNotIgnored()) { - add(tagsFilter.toJsonObject("tagged")) - } - - if (genreFilter.anyNotIgnored()) { - add(genreFilter.toJsonObject("genre_ids")) - } - - if (query != "") { - add( - JsonObject().apply { - addProperty("name", "search_query") - addProperty("value", query) - } - ) - } - } - ) - } - ) - } - } - - private fun buildAlbumListRequest(page: Int, filters: FilterList, query: String = ""): Request { - val input = buildAlbumListRequestInput(page, filters, query) - val url = HttpUrl.parse(apiBaseUrl)!!.newBuilder() - .addQueryParameter("operationName", "AlbumList") - .addQueryParameter("query", ALBUM_LIST_REQUEST_GQL) - .addQueryParameter("variables", input.toString()) - .toString() - return GET(url, headers) - } - - private fun parseAlbumListResponse(response: Response): MangasPage { - val data = gson.fromJson(response.body()!!.string()) - with(data["data"]["album"]["list"]) { - return MangasPage( - this["items"].asJsonArray.map { - SManga.create().apply { - url = it["url"].asString - title = it["title"].asString - thumbnail_url = it["cover"]["url"].asString - } - }, - this["info"]["has_next_page"].asBoolean - ) - } - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = buildAlbumListRequest(page, getSortFilters(LATEST_DEFAULT_SORT_STATE)) - - override fun latestUpdatesParse(response: Response): MangasPage = parseAlbumListResponse(response) - - // Chapters - - override fun chapterListParse(response: Response): List { - val document = response.asJsoup() - return listOf( - SChapter.create().apply { - url = response.request().url().toString() - name = "Chapter" - date_upload = document.select(".album-info-item:contains(Created:)")?.first()?.ownText()?.trim()?.let { - DATE_FORMATS_WITH_ORDINAL_SUFFIXES.mapNotNull { format -> format.parseOrNull(it) }.firstOrNull()?.time - } ?: 0L - chapter_number = 1f - } - ) - } - - // Pages - - private fun buildAlbumPicturesRequestInput(id: String, page: Int, sortPagesByOption: String): JsonObject { - return JsonObject().apply { - addProperty( - "input", - JsonObject().apply { - addProperty( - "filters", - JsonArray().apply { - add( - JsonObject().apply { - addProperty("name", "album_id") - addProperty("value", id) - } - ) - } - ) - addProperty("display", sortPagesByOption) - addProperty("page", page) - } - ) - } - } - - private fun buildAlbumPicturesPageUrl(id: String, page: Int, sortPagesByOption: String): String { - val input = buildAlbumPicturesRequestInput(id, page, sortPagesByOption) - return HttpUrl.parse(apiBaseUrl)!!.newBuilder() - .addQueryParameter("operationName", "AlbumListOwnPictures") - .addQueryParameter("query", ALBUM_PICTURES_REQUEST_GQL) - .addQueryParameter("variables", input.toString()) - .toString() - } - - private fun parseAlbumPicturesResponse(response: Response, sortPagesByOption: String): List { - - val id = response.request().url().queryParameter("variables").toString() - .let { gson.fromJson(it)["input"]["filters"].asJsonArray } - .let { it.first { f -> f["name"].asString == "album_id" } } - .let { it["value"].asString } - - val data = gson.fromJson(response.body()!!.string()) - .let { it["data"]["picture"]["list"].asJsonObject } - - return data["items"].asJsonArray.mapIndexed { index, it -> - Page(index, imageUrl = it["thumbnails"][0]["url"].asString) - } + if (data["info"]["total_pages"].asInt > 1) { // get 2nd page onwards - (ITEMS_PER_PAGE until data["info"]["total_items"].asInt).chunked(ITEMS_PER_PAGE).mapIndexed { page, indices -> - indices.map { Page(it, url = buildAlbumPicturesPageUrl(id, page + 2, sortPagesByOption)) } - }.flatten() - } else emptyList() - } - - private fun getAlbumSortPagesOption(chapter: SChapter): Observable { - return client.newCall(GET(chapter.url)) - .asObservableSuccess() - .map { - val sortByKey = it.asJsoup().select(".o-input-select:contains(Sorted By) .o-select-value")?.text() ?: "" - ALBUM_PICTURES_SORT_OPTIONS.getValue(sortByKey) - } - } - - override fun fetchPageList(chapter: SChapter): Observable> { - val id = chapter.url.substringAfterLast("_").removeSuffix("/") - - return getAlbumSortPagesOption(chapter) - .concatMap { sortPagesByOption -> - client.newCall(GET(buildAlbumPicturesPageUrl(id, 1, sortPagesByOption))) - .asObservableSuccess() - .map { parseAlbumPicturesResponse(it, sortPagesByOption) } - } - } - - override fun pageListParse(response: Response): List = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - override fun fetchImageUrl(page: Page): Observable { - if (page.imageUrl != null) { - return Observable.just(page.imageUrl) - } - - return client.newCall(GET(page.url, headers)) - .asObservableSuccess() - .map { - val data = gson.fromJson(it.body()!!.string()).let { data -> - data["data"]["picture"]["list"].asJsonObject - } - data["items"].asJsonArray[page.index % 50].asJsonObject["thumbnails"][0]["url"].asString - } - } - - // Details - - private fun parseMangaGenre(document: Document): String { - return listOf( - document.select(".o-tag--secondary").map { it.text().substringBefore("(").trim() }, - document.select(".o-tag:not([href *= /tags/artist])").map { it.text() }, - document.select(".album-info-item:contains(Content:) .o-tag").map { it.text() } - ).flatten().joinToString() - } - - private fun parseMangaDescription(document: Document): String { - val pageCount: String? = ( - document.select(".album-info-item:contains(pictures)").firstOrNull() - ?: document.select(".album-info-item:contains(gifs)").firstOrNull() - )?.text() - - return listOf( - Pair("Description", document.select(".album-description:last-of-type")?.text()), - Pair("Pages", pageCount) - ).let { - it + listOf("Parody", "Character", "Ethnicity") - .map { key -> key to document.select(".o-tag--category:contains($key) .o-tag").joinToString { t -> t.text() } } - }.filter { desc -> !desc.second.isNullOrBlank() } - .joinToString("\n\n") { "${it.first}:\n${it.second}" } - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - return SManga.create().apply { - - artist = document.select(".o-tag--category:contains(Artist:) .o-tag")?.joinToString { it.text() } - author = artist - - genre = parseMangaGenre(document) - - title = document.select("a[title]").text() - status = when { - title.contains("ongoing", true) -> SManga.ONGOING - else -> SManga.COMPLETED - } - - description = parseMangaDescription(document) - } - } - - // Popular - - override fun popularMangaParse(response: Response): MangasPage = parseAlbumListResponse(response) - - override fun popularMangaRequest(page: Int): Request = buildAlbumListRequest(page, getSortFilters(POPULAR_DEFAULT_SORT_STATE)) - - // Search - - override fun searchMangaParse(response: Response): MangasPage = parseAlbumListResponse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = buildAlbumListRequest( - page, - filters.let { - if (it.isEmpty()) getSortFilters(SEARCH_DEFAULT_SORT_STATE) - else it - }, - query - ) - - class TriStateFilterOption(name: String, val value: String) : Filter.TriState(name) - abstract class TriStateGroupFilter(name: String, options: List) : Filter.Group(name, options) { - val included: List - get() = state.filter { it.isIncluded() }.map { it.value } - - val excluded: List - get() = state.filter { it.isExcluded() }.map { it.value } - - fun anyNotIgnored(): Boolean = state.any { !it.isIgnored() } - - override fun toString(): String = (included.map { "+$it" } + excluded.map { "-$it" }).joinToString("") - } - - private fun Filter<*>.toJsonObject(key: String): JsonObject { - val value = this.toString() - return JsonObject().apply { - addProperty("name", key) - addProperty("value", value) - } - } - - private class TagGroupFilter(filters: List) : TriStateGroupFilter("Tags", filters) - private class GenreGroupFilter(filters: List) : TriStateGroupFilter("Genres", filters) - - class CheckboxFilterOption(name: String, val value: String, default: Boolean = true) : Filter.CheckBox(name, default) - abstract class CheckboxGroupFilter(name: String, options: List) : Filter.Group(name, options) { - val selected: List - get() = state.filter { it.state }.map { it.value } - - override fun toString(): String = selected.joinToString("") { "+$it" } - } - - private class InterestGroupFilter(options: List) : CheckboxGroupFilter("Interests", options) - private class LanguageGroupFilter(options: List) : CheckboxGroupFilter("Languages", options) - - class SelectFilterOption(val name: String, val value: String) - - abstract class SelectFilter(name: String, private val options: List, default: Int = 0) : Filter.Select(name, options.map { it.name }.toTypedArray(), default) { - val selected: String - get() = options[state].value - - override fun toString(): String = selected - } - class SortBySelectFilter(options: List, default: Int) : SelectFilter("Sort By", options, default) - class AlbumTypeSelectFilter(options: List) : SelectFilter("Album Type", options) - class ContentTypeSelectFilter(options: List) : SelectFilter("Content Type", options) - - override fun getFilterList(): FilterList = getSortFilters(POPULAR_DEFAULT_SORT_STATE) - - private fun getSortFilters(sortState: Int) = FilterList( - SortBySelectFilter(getSortFilters(), sortState), - AlbumTypeSelectFilter(getAlbumTypeFilters()), - ContentTypeSelectFilter(getContentTypeFilters()), - InterestGroupFilter(getInterestFilters()), - LanguageGroupFilter(getLanguageFilters()), - TagGroupFilter(getTagFilters()), - GenreGroupFilter(getGenreFilters()) - ) - - fun getSortFilters() = listOf( - SelectFilterOption("Rating - All Time", "rating_all_time"), - SelectFilterOption("Rating - Last 7 Days", "rating_7_days"), - SelectFilterOption("Rating - Last 14 Days", "rating_14_days"), - SelectFilterOption("Rating - Last 30 Days", "rating_30_days"), - SelectFilterOption("Rating - Last 90 Days", "rating_90_days"), - SelectFilterOption("Rating - Last Year", "rating_1_year"), - SelectFilterOption("Rating - Last Year", "rating_1_year"), - SelectFilterOption("Date - Newest First", "date_newest"), - SelectFilterOption("Date - 2020", "date_2020"), - SelectFilterOption("Date - 2019", "date_2019"), - SelectFilterOption("Date - 2018", "date_2018"), - SelectFilterOption("Date - 2017", "date_2017"), - SelectFilterOption("Date - 2016", "date_2016"), - SelectFilterOption("Date - 2015", "date_2015"), - SelectFilterOption("Date - 2014", "date_2014"), - SelectFilterOption("Date - 2013", "date_2013"), - SelectFilterOption("Date - Oldest First", "date_oldest"), - SelectFilterOption("Date - Upcoming", "date_upcoming"), - SelectFilterOption("Date - Trending", "date_trending"), - SelectFilterOption("Date - Featured", "date_featured"), - SelectFilterOption("Date - Last Viewed", "date_last_interaction") - ) - - fun getAlbumTypeFilters() = listOf( - SelectFilterOption("Manga", "manga"), - SelectFilterOption("All", FILTER_VALUE_IGNORE), - SelectFilterOption("Pictures", "pictures") - ) - - fun getContentTypeFilters() = listOf( - SelectFilterOption("All", FILTER_VALUE_IGNORE), - SelectFilterOption("Hentai", "0"), - SelectFilterOption("Non-Erotic", "5"), - SelectFilterOption("Real People", "6") - ) - - fun getInterestFilters() = listOf( - CheckboxFilterOption("Straight Sex", "1"), - CheckboxFilterOption("Trans x Girl", "10", false), - CheckboxFilterOption("Gay / Yaoi", "2"), - CheckboxFilterOption("Lesbian / Yuri", "3"), - CheckboxFilterOption("Trans", "5"), - CheckboxFilterOption("Solo Girl", "6"), - CheckboxFilterOption("Trans x Trans", "8"), - CheckboxFilterOption("Trans x Guy", "9") - ) - - fun getLanguageFilters() = listOf( - CheckboxFilterOption("English", ENGLISH_LUS_LANG_VAL, false), - CheckboxFilterOption("Japanese", JAPANESE_LUS_LANG_VAL, false), - CheckboxFilterOption("Spanish", SPANISH_LUS_LANG_VAL, false), - CheckboxFilterOption("Italian", ITALIAN_LUS_LANG_VAL, false), - CheckboxFilterOption("German", GERMAN_LUS_LANG_VAL, false), - CheckboxFilterOption("French", FRENCH_LUS_LANG_VAL, false), - CheckboxFilterOption("Chinese", CHINESE_LUS_LANG_VAL, false), - CheckboxFilterOption("Korean", KOREAN_LUS_LANG_VAL, false), - CheckboxFilterOption("Others", OTHERS_LUS_LANG_VAL, false), - CheckboxFilterOption("Portugese", PORTUGESE_LUS_LANG_VAL, false), - CheckboxFilterOption("Thai", THAI_LUS_LANG_VAL, false) - ).filterNot { it.value == lusLang } - - fun getTagFilters() = listOf( - TriStateFilterOption("Big Breasts", "big_breasts"), - TriStateFilterOption("Blowjob", "blowjob"), - TriStateFilterOption("Anal", "anal"), - TriStateFilterOption("Group", "group"), - TriStateFilterOption("Big Ass", "big_ass"), - TriStateFilterOption("Full Color", "full_color"), - TriStateFilterOption("Schoolgirl", "schoolgirl"), - TriStateFilterOption("Rape", "rape"), - TriStateFilterOption("Glasses", "glasses"), - TriStateFilterOption("Nakadashi", "nakadashi"), - TriStateFilterOption("Yuri", "yuri"), - TriStateFilterOption("Paizuri", "paizuri"), - TriStateFilterOption("Ahegao", "ahegao"), - TriStateFilterOption("Group: metart", "group%3A_metart"), - TriStateFilterOption("Brunette", "brunette"), - TriStateFilterOption("Solo", "solo"), - TriStateFilterOption("Blonde", "blonde"), - TriStateFilterOption("Shaved Pussy", "shaved_pussy"), - TriStateFilterOption("Small Breasts", "small_breasts"), - TriStateFilterOption("Cum", "cum"), - TriStateFilterOption("Stockings", "stockings"), - TriStateFilterOption("Yuri", "yuri"), - TriStateFilterOption("Ass", "ass"), - TriStateFilterOption("Creampie", "creampie"), - TriStateFilterOption("Rape", "rape"), - TriStateFilterOption("Oral Sex", "oral_sex"), - TriStateFilterOption("Bondage", "bondage"), - TriStateFilterOption("Futanari", "futanari"), - TriStateFilterOption("Double Penetration", "double_penetration"), - TriStateFilterOption("Threesome", "threesome"), - TriStateFilterOption("Anal Sex", "anal_sex"), - TriStateFilterOption("Big Cock", "big_cock"), - TriStateFilterOption("Straight Sex", "straight_sex"), - TriStateFilterOption("Yaoi", "yaoi") - ) - - fun getGenreFilters() = listOf( - TriStateFilterOption("3D / Digital Art", "25"), - TriStateFilterOption("Amateurs", "20"), - TriStateFilterOption("Artist Collection", "19"), - TriStateFilterOption("Asian Girls", "12"), - TriStateFilterOption("Cosplay", "22"), - TriStateFilterOption("BDSM", "27"), - TriStateFilterOption("Cross-Dressing", "30"), - TriStateFilterOption("Defloration / First Time", "59"), - TriStateFilterOption("Ebony Girls", "32"), - TriStateFilterOption("European Girls", "46"), - TriStateFilterOption("Fantasy / Monster Girls", "10"), - TriStateFilterOption("Fetish", "2"), - TriStateFilterOption("Furries", "8"), - TriStateFilterOption("Futanari", "31"), - TriStateFilterOption("Group Sex", "36"), - TriStateFilterOption("Harem", "56"), - TriStateFilterOption("Humor", "41"), - TriStateFilterOption("Interracial", "28"), - TriStateFilterOption("Kemonomimi / Animal Ears", "39"), - TriStateFilterOption("Latina Girls", "33"), - TriStateFilterOption("Mature", "13"), - TriStateFilterOption("Members: Original Art", "18"), - TriStateFilterOption("Members: Verified Selfies", "21"), - TriStateFilterOption("Military", "48"), - TriStateFilterOption("Mind Control", "34"), - TriStateFilterOption("Monsters & Tentacles", "38"), - TriStateFilterOption("Netorare / Cheating", "40"), - TriStateFilterOption("No Genre Given", "1"), - TriStateFilterOption("Nonconsent / Reluctance", "37"), - TriStateFilterOption("Other Ethnicity Girls", "57"), - TriStateFilterOption("Public Sex", "43"), - TriStateFilterOption("Romance", "42"), - TriStateFilterOption("School / College", "35"), - TriStateFilterOption("Sex Workers", "47"), - TriStateFilterOption("Softcore / Ecchi", "9"), - TriStateFilterOption("Superheroes", "17"), - TriStateFilterOption("Tankobon", "45"), - TriStateFilterOption("TV / Movies", "51"), - TriStateFilterOption("Trans", "14"), - TriStateFilterOption("Video Games", "15"), - TriStateFilterOption("Vintage", "58"), - TriStateFilterOption("Western", "11"), - TriStateFilterOption("Workplace Sex", "50") - ) - - private inline fun Iterable<*>.findInstance() = find { it is T } as? T - - private fun SimpleDateFormat.parseOrNull(string: String): Date? { - return try { - parse(string) - } catch (e: ParseException) { - null - } - } - - companion object { - - private val ALBUM_PICTURES_SORT_OPTIONS = hashMapOf( - Pair("Sort By Newest", "date_newest"), - Pair("Sort By Rating", "rating_all_time") - ).withDefault { "position" } - - private const val ITEMS_PER_PAGE = 50 - - private val ORDINAL_SUFFIXES = listOf("st", "nd", "rd", "th") - private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES = ORDINAL_SUFFIXES.map { - SimpleDateFormat("MMMM dd'$it', yyyy", Locale.US) - } - - const val ENGLISH_LUS_LANG_VAL = "1" - const val JAPANESE_LUS_LANG_VAL = "2" - const val SPANISH_LUS_LANG_VAL = "3" - const val ITALIAN_LUS_LANG_VAL = "4" - const val GERMAN_LUS_LANG_VAL = "5" - const val FRENCH_LUS_LANG_VAL = "6" - const val CHINESE_LUS_LANG_VAL = "8" - const val KOREAN_LUS_LANG_VAL = "9" - const val OTHERS_LUS_LANG_VAL = "99" - const val PORTUGESE_LUS_LANG_VAL = "100" - const val THAI_LUS_LANG_VAL = "101" - - private const val POPULAR_DEFAULT_SORT_STATE = 0 - private const val LATEST_DEFAULT_SORT_STATE = 7 - private const val SEARCH_DEFAULT_SORT_STATE = 0 - - private const val FILTER_VALUE_IGNORE = "" - - private val ALBUM_LIST_REQUEST_GQL = """ - query AlbumList(${'$'}input: AlbumListInput!) { - album { - list(input: ${'$'}input) { - info { - page - has_next_page - } - items - } - } - } - """.replace("\n", " ").replace("\\s+".toRegex(), " ") - - private val ALBUM_PICTURES_REQUEST_GQL = """ - query AlbumListOwnPictures(${'$'}input: PictureListInput!) { - picture { - list(input: ${'$'}input) { - info { - total_items - total_pages - page - has_next_page - } - items { - thumbnails { - url - } - } - } - } - } - """.replace("\n", " ").replace("\\s+".toRegex(), " ") - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/luscious/LusciousGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/luscious/LusciousGenerator.kt deleted file mode 100644 index f1bc21bb8..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/luscious/LusciousGenerator.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.luscious - -import generator.ThemeSourceData.MultiLang -import generator.ThemeSourceData.SingleLang -import generator.ThemeSourceGenerator - -class LusciousGenerator : ThemeSourceGenerator { - - override val themePkg = "luscious" - - override val themeClass = "Luscious" - - override val baseVersionCode: Int = 2 - - override val sources = listOf( - MultiLang("Luscious", "https://www.luscious.net", listOf("en","ja", "es", "it", "de", "fr", "zh", "ko", "other", "pt", "th"), isNsfw = true, className = "LusciousFactory", overrideVersionCode = 2), - MultiLang("Luscious (Members)", "https://members.luscious.net", listOf("en","ja", "es", "it", "de", "fr", "zh", "ko", "other", "pt", "th"), isNsfw = true, className = "LusciousMembersFactory", pkgName = "lusciousmembers"),//Requires Account - MultiLang("Luscious (API)", "https://api.luscious.net", listOf("en","ja", "es", "it", "de", "fr", "zh", "ko", "other", "pt", "th"), isNsfw = true, className = "LusciousAPIFactory", pkgName = "lusciousapi") - ) - - companion object { - @JvmStatic - fun main(args: Array) { - LusciousGenerator().createAll() - } - } -} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt deleted file mode 100644 index 850c7b0a7..000000000 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/madara/Madara.kt +++ /dev/null @@ -1,554 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.madara - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale -import java.util.concurrent.TimeUnit -import kotlin.math.absoluteValue -import kotlin.random.Random - -abstract class Madara( - override val name: String, - override val baseUrl: String, - override val lang: String, - private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US) -) : ParsedHttpSource() { - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build() - - // helps with cloudflare for some sources, makes it worse for others; override with empty string if the latter is true - protected open val userAgentRandomizer = " ${Random.nextInt().absoluteValue}" - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/78.0$userAgentRandomizer") - - // Popular Manga - - override fun popularMangaSelector() = "div.page-item-detail" - - open val popularMangaUrlSelector = "div.post-title a" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - with(element) { - select(popularMangaUrlSelector).first()?.let { - manga.setUrlWithoutDomain(it.attr("abs:href")) - manga.title = it.ownText() - } - - select("img").first()?.let { - manga.thumbnail_url = imageFromElement(it) - } - } - - return manga - } - - open fun formBuilder(page: Int, popular: Boolean) = FormBody.Builder().apply { - add("action", "madara_load_more") - add("page", (page - 1).toString()) - add("template", "madara-core/content/content-archive") - add("vars[orderby]", "meta_value_num") - add("vars[paged]", "1") - add("vars[posts_per_page]", "20") - add("vars[post_type]", "wp-manga") - add("vars[post_status]", "publish") - add("vars[meta_key]", if (popular) "_wp_manga_views" else "_latest_update") - add("vars[order]", "desc") - add("vars[sidebar]", if (popular) "full" else "right") - add("vars[manga_archives_item_layout]", "big_thumbnail") - } - - open val formHeaders: Headers by lazy { headersBuilder().build() } - - override fun popularMangaRequest(page: Int): Request { - return POST("$baseUrl/wp-admin/admin-ajax.php", formHeaders, formBuilder(page, true).build(), CacheControl.FORCE_NETWORK) - } - - override fun popularMangaNextPageSelector(): String? = "body:not(:has(.no-posts))" - - // Latest Updates - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga { - // Even if it's different from the popular manga's list, the relevant classes are the same - return popularMangaFromElement(element) - } - - override fun latestUpdatesRequest(page: Int): Request { - return POST("$baseUrl/wp-admin/admin-ajax.php", formHeaders, formBuilder(page, false).build(), CacheControl.FORCE_NETWORK) - } - - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - - override fun latestUpdatesParse(response: Response): MangasPage { - val mp = super.latestUpdatesParse(response) - val mangas = mp.mangas.distinctBy { it.url } - return MangasPage(mangas, mp.hasNextPage) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - // Error message for exceeding last page - if (response.code() == 404) - error("Already on the Last Page!") - else throw Exception("HTTP error ${response.code()}") - } - } - .map { response -> - searchMangaParse(response) - } - } - - // Search Manga - - protected open fun searchPage(page: Int): String = "page/$page/" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/${searchPage(page)}")!!.newBuilder() - url.addQueryParameter("s", query) - url.addQueryParameter("post_type", "wp-manga") - filters.forEach { filter -> - when (filter) { - is AuthorFilter -> { - if (filter.state.isNotBlank()) { - url.addQueryParameter("author", filter.state) - } - } - is ArtistFilter -> { - if (filter.state.isNotBlank()) { - url.addQueryParameter("artist", filter.state) - } - } - is YearFilter -> { - if (filter.state.isNotBlank()) { - url.addQueryParameter("release", filter.state) - } - } - is StatusFilter -> { - filter.state.forEach { - if (it.state) { - url.addQueryParameter("status[]", it.id) - } - } - } - is OrderByFilter -> { - if (filter.state != 0) { - url.addQueryParameter("m_orderby", filter.toUriPart()) - } - } - is GenreConditionFilter -> { - url.addQueryParameter("op", filter.toUriPart()) - } - is GenreList -> { - filter.state - .filter { it.state } - .let { list -> - if (list.isNotEmpty()) { list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } } - } - } - } - } - return GET(url.toString(), headers) - } - - private class AuthorFilter : Filter.Text("Author") - private class ArtistFilter : Filter.Text("Artist") - private class YearFilter : Filter.Text("Year of Released") - private class StatusFilter(status: List) : Filter.Group("Status", status) - private class OrderByFilter : UriPartFilter( - "Order By", - arrayOf( - Pair(""), - Pair("title", "A-Z"), - Pair("update", "Latest Update"), - Pair("create", "Latest Added") - ) - ) - - private class GenreList : UriPartFilter( - "Select Genre", - arrayOf( - Pair("", "", ""), - Pair("1 ~ 9", "1-9"), - Pair("10 ~ 29", "10-29"), - Pair("30 ~ 99", "30-99"), - Pair("100 ~ 199", "100-199"), - Pair("200+", "200"), - Pair("100+", "100"), - Pair("50+", "50"), - Pair("10+", "10"), - Pair("1+", "1") - ) - ) - - private class SortBy : UriPartFilter( - "Sorts By", - arrayOf( - Pair("", ""), - Pair("Pending", "pending"), - Pair("Ongoing", "ongoing"), - Pair("Completed", "completed"), - Pair("Hiatus", "hiatus"), - Pair("Cancelled", "cancelled") - ) - ) - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - ChapterFilter(), - SortBy(), - StatusFilter(), - OriginFilter(getOriginList()), - GenreFilter(getGenreList()) - ) - - private fun getOriginList() = listOf( - Tag("my"), - Tag("ceb"), - Tag("zh"), - Tag("zh_hk"), - Tag("en"), - Tag("en_us"), - Tag("fil"), - Tag("id"), - Tag("it"), - Tag("ja"), - Tag("ko"), - Tag("ms"), - Tag("pt_br"), - Tag("th"), - Tag("vi") - ) - - private fun getGenreList() = listOf( - Genre("Artbook"), - Genre("Cartoon"), - Genre("Comic"), - Genre("Doujinshi"), - Genre("Imageset"), - Genre("Manga"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Webtoon"), - Genre("Western"), - Genre("Josei"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo_Ai"), - Genre("Shounen"), - Genre("Shounen_Ai"), - Genre("Yaoi"), - Genre("Yuri"), - Genre("Ecchi"), - Genre("Mature"), - Genre("Adult"), - Genre("Gore"), - Genre("Violence"), - Genre("Smut"), - Genre("Hentai"), - Genre("4_Koma"), - Genre("Action"), - Genre("Adaptation"), - Genre("Adventure"), - Genre("Aliens"), - Genre("Animals"), - Genre("Anthology"), - Genre("Comedy"), - Genre("Cooking"), - Genre("Crime"), - Genre("Crossdressing"), - Genre("Delinquents"), - Genre("Dementia"), - Genre("Demons"), - Genre("Drama"), - Genre("Fantasy"), - Genre("Fan_Colored"), - Genre("Full_Color"), - Genre("Game"), - Genre("Gender_Bender"), - Genre("Genderswap"), - Genre("Ghosts"), - Genre("Gyaru"), - Genre("Harem"), - Genre("Harlequin"), - Genre("Historical"), - Genre("Horror"), - Genre("Incest"), - Genre("Isekai"), - Genre("Kids"), - Genre("Loli"), - Genre("Lolicon"), - Genre("Magic"), - Genre("Magical_Girls"), - Genre("Martial_Arts"), - Genre("Mecha"), - Genre("Medical"), - Genre("Military"), - Genre("Monster_Girls"), - Genre("Monsters"), - Genre("Music"), - Genre("Mystery"), - Genre("Netorare"), - Genre("Ninja"), - Genre("Office_Workers"), - Genre("Oneshot"), - Genre("Parody"), - Genre("Philosophical"), - Genre("Police"), - Genre("Post_Apocalyptic"), - Genre("Psychological"), - Genre("Reincarnation"), - Genre("Reverse_Harem"), - Genre("Romance"), - Genre("Samurai"), - Genre("School_Life"), - Genre("Sci_Fi"), - Genre("Shota"), - Genre("Shotacon"), - Genre("Slice_Of_Life"), - Genre("SM_BDSM"), - Genre("Space"), - Genre("Sports"), - Genre("Super_Power"), - Genre("Superhero"), - Genre("Supernatural"), - Genre("Survival"), - Genre("Thriller"), - Genre("Time_Travel"), - Genre("Tragedy"), - Genre("Vampires"), - Genre("Video_Games"), - Genre("Virtual_Reality"), - Genre("Wuxia"), - Genre("Xianxia"), - Genre("Xuanhuan"), - Genre("Zombies"), - Genre("award_winning"), - Genre("youkai"), - Genre("uncategorized") - ) - - private open class UriPartFilter(displayName: String, val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private class Tag(name: String) : Filter.CheckBox(name) - private class Genre(name: String) : Filter.TriState(name) -} diff --git a/src/all/batoto/src/eu/kanade/tachiyomi/extension/all/batoto/BatoToFactory.kt b/src/all/batoto/src/eu/kanade/tachiyomi/extension/all/batoto/BatoToFactory.kt deleted file mode 100644 index cc833c3f5..000000000 --- a/src/all/batoto/src/eu/kanade/tachiyomi/extension/all/batoto/BatoToFactory.kt +++ /dev/null @@ -1,60 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.batoto - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class BatoToFactory : SourceFactory { - override fun createSources(): List = languages.map { BatoTo(it.first, it.second) } -} - -private val languages = listOf( - //commented langueges do currently not exist on Bato.to but haven in the past - Pair("all",""), - Pair("ar", "ar"), - Pair("bg", "bg"), - Pair("cs", "cs"), - Pair("da", "da"), - Pair("de", "de"), - Pair("el", "el"), - Pair("en", "en"), - Pair("en-US", "en_us"), - Pair("es", "es"), - Pair("es-419", "es_419"), - Pair("eu", "eu"), - Pair("fa", "fa"), - Pair("fi", "fi"), - Pair("fil", "fil"), - Pair("fr", "fr"), - Pair("he", "he"), - //Pair("hi", "hi"), - Pair("hr", "hr"), - Pair("hu", "hu"), - Pair("id", "id"), - Pair("it", "it"), - Pair("ja", "ja"), - Pair("ko", "ko"), - //Pair("ku", "ku"), - Pair("ml", "ml"), - Pair("mn", "mn"), - Pair("ms", "ms"), - Pair("my", "my"), - Pair("nl", "nl"), - Pair("no", "no"), - Pair("pl", "pl"), - Pair("pt", "pt"), - Pair("pt-BR", "pt_br"), - Pair("pt-PT", "pt_pt"), - Pair("ro", "ro"), - Pair("ru", "ru"), - Pair("th", "th"), - Pair("tr", "tr"), - Pair("uk", "uk"), - Pair("vi", "vi"), - //Pair("xh", "xh"), - Pair("zh", "zh"), - Pair("zh-rHK", "zh_hk"), - Pair("zh-rTW", "zh_tw"), - Pair("zu", "zu"), -) diff --git a/src/all/cubari/AndroidManifest.xml b/src/all/cubari/AndroidManifest.xml deleted file mode 100644 index a28e3cbe3..000000000 --- a/src/all/cubari/AndroidManifest.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/all/cubari/build.gradle b/src/all/cubari/build.gradle deleted file mode 100644 index 87ea99701..000000000 --- a/src/all/cubari/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Cubari' - pkgNameSuffix = "all.cubari" - extClass = '.CubariFactory' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/cubari/res/mipmap-hdpi/ic_launcher.png b/src/all/cubari/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a43b765ef..000000000 Binary files a/src/all/cubari/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/cubari/res/mipmap-mdpi/ic_launcher.png b/src/all/cubari/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d2a531cfc..000000000 Binary files a/src/all/cubari/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/cubari/res/mipmap-xhdpi/ic_launcher.png b/src/all/cubari/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b8ce2733b..000000000 Binary files a/src/all/cubari/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/cubari/res/mipmap-xxhdpi/ic_launcher.png b/src/all/cubari/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 804782509..000000000 Binary files a/src/all/cubari/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/cubari/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/cubari/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 84438c81a..000000000 Binary files a/src/all/cubari/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/cubari/res/web_hi_res_512.png b/src/all/cubari/res/web_hi_res_512.png deleted file mode 100644 index 1dfd0c08c..000000000 Binary files a/src/all/cubari/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt deleted file mode 100644 index b918bd30f..000000000 --- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt +++ /dev/null @@ -1,354 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.cubari - -import android.os.Build -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import rx.Observable - -open class Cubari(override val lang: String) : HttpSource() { - - final override val name = "Cubari" - final override val baseUrl = "https://cubari.moe" - final override val supportsLatest = true - - override fun headersBuilder() = Headers.Builder().apply { - add( - "User-Agent", - "(Android ${Build.VERSION.RELEASE}; " + - "${Build.MANUFACTURER} ${Build.MODEL}) " + - "Tachiyomi/${BuildConfig.VERSION_NAME} " + - Build.ID - ) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/", headers) - } - - override fun fetchLatestUpdates(page: Int): Observable { - return client.newBuilder() - .addInterceptor(RemoteStorageUtils.HomeInterceptor()) - .build()!! - .newCall(latestUpdatesRequest(page)) - .asObservableSuccess() - .map { response -> latestUpdatesParse(response) } - } - - override fun latestUpdatesParse(response: Response): MangasPage { - return parseMangaList(JSONArray(response.body()!!.string()), SortType.UNPINNED) - } - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/", headers) - } - - override fun fetchPopularManga(page: Int): Observable { - return client.newBuilder() - .addInterceptor(RemoteStorageUtils.HomeInterceptor()) - .build()!! - .newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> popularMangaParse(response) } - } - - override fun popularMangaParse(response: Response): MangasPage { - return parseMangaList(JSONArray(response.body()!!.string()), SortType.PINNED) - } - - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> mangaDetailsParse(response, manga) } - } - - // Called when the series is loaded, or when opening in browser - override fun mangaDetailsRequest(manga: SManga): Request { - return GET("$baseUrl${manga.url}", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - throw Exception("Unused") - } - - private fun mangaDetailsParse(response: Response, manga: SManga): SManga { - return parseMangaFromApi(JSONObject(response.body()!!.string()), manga) - } - - override fun fetchChapterList(manga: SManga): Observable> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> chapterListParse(response, manga) } - } - - // Gets the chapter list based on the series being viewed - override fun chapterListRequest(manga: SManga): Request { - val urlComponents = manga.url.split("/") - val source = urlComponents[2] - val slug = urlComponents[3] - - return GET("$baseUrl/read/api/$source/series/$slug/", headers) - } - - override fun chapterListParse(response: Response): List { - throw Exception("Unused") - } - - // Called after the request - private fun chapterListParse(response: Response, manga: SManga): List { - val res = response.body()!!.string() - return parseChapterList(res, manga) - } - - override fun fetchPageList(chapter: SChapter): Observable> { - return when { - chapter.url.contains("/chapter/") -> { - client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .map { response -> - directPageListParse(response) - } - } - else -> { - client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .map { response -> - seriesJsonPageListParse(response, chapter) - } - } - } - } - - override fun pageListRequest(chapter: SChapter): Request { - return when { - chapter.url.contains("/chapter/") -> { - GET("$baseUrl${chapter.url}", headers) - } - else -> { - var url = chapter.url.split("/") - val source = url[2] - val slug = url[3] - - GET("$baseUrl/read/api/$source/series/$slug/", headers) - } - } - } - - private fun directPageListParse(response: Response): List { - val res = response.body()!!.string() - val pages = JSONArray(res) - val pageArray = ArrayList() - - for (i in 0 until pages.length()) { - val page = if (pages.optJSONObject(i) != null) { - pages.getJSONObject(i).getString("src") - } else { - pages[i] - } - pageArray.add(Page(i + 1, "", page.toString())) - } - return pageArray - } - - private fun seriesJsonPageListParse(response: Response, chapter: SChapter): List { - val res = response.body()!!.string() - val json = JSONObject(res) - val groups = json.getJSONObject("groups") - val groupIter = groups.keys() - val groupMap = HashMap() - - while (groupIter.hasNext()) { - val groupKey = groupIter.next() - groupMap[groups.getString(groupKey)] = groupKey - } - - val chapters = json.getJSONObject("chapters") - - val pages = if (chapters.has(chapter.chapter_number.toString())) { - chapters - .getJSONObject(chapter.chapter_number.toString()) - .getJSONObject("groups") - .getJSONArray(groupMap[chapter.scanlator]) - } else { - chapters - .getJSONObject(chapter.chapter_number.toInt().toString()) - .getJSONObject("groups") - .getJSONArray(groupMap[chapter.scanlator]) - } - val pageArray = ArrayList() - for (i in 0 until pages.length()) { - val page = if (pages.optJSONObject(i) != null) { - pages.getJSONObject(i).getString("src") - } else { - pages[i] - } - pageArray.add(Page(i + 1, "", page.toString())) - } - return pageArray - } - - // Stub - override fun pageListParse(response: Response): List { - throw Exception("Unused") - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return when { - query.startsWith(PROXY_PREFIX) -> { - val trimmedQuery = query.removePrefix(PROXY_PREFIX) - // Only tag for recently read on search - client.newBuilder() - .addInterceptor(RemoteStorageUtils.TagInterceptor()) - .build()!! - .newCall(searchMangaRequest(page, trimmedQuery, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, trimmedQuery) - } - } - else -> throw Exception(SEARCH_FALLBACK_MSG) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - try { - val queryFragments = query.split("/") - val source = queryFragments[0] - val slug = queryFragments[1] - - return GET("$baseUrl/read/api/$source/series/$slug/", headers) - } catch (e: Exception) { - throw Exception(SEARCH_FALLBACK_MSG) - } - } - - override fun searchMangaParse(response: Response): MangasPage { - throw Exception("Unused") - } - - private fun searchMangaParse(response: Response, query: String): MangasPage { - return parseSearchList(JSONObject(response.body()!!.string()), query) - } - - // ------------- Helpers and whatnot --------------- - - private fun parseChapterList(payload: String, manga: SManga): List { - val json = JSONObject(payload) - val groups = json.getJSONObject("groups") - val chapters = json.getJSONObject("chapters") - - val chapterList = ArrayList() - - val iter = chapters.keys() - - while (iter.hasNext()) { - val chapterNum = iter.next() - val chapterObj = chapters.getJSONObject(chapterNum) - val chapterGroups = chapterObj.getJSONObject("groups") - val groupsIter = chapterGroups.keys() - - while (groupsIter.hasNext()) { - val groupNum = groupsIter.next() - val chapter = SChapter.create() - - chapter.scanlator = groups.getString(groupNum) - if (chapterObj.has("release_date")) { - chapter.date_upload = - chapterObj.getJSONObject("release_date").getLong(groupNum) * 1000 - } - chapter.name = chapterNum + " - " + chapterObj.getString("title") - chapter.chapter_number = chapterNum.toFloat() - chapter.url = - if (chapterGroups.optJSONArray(groupNum) != null) { - "${manga.url}/$chapterNum/$groupNum" - } else { - chapterGroups.getString(groupNum) - } - chapterList.add(chapter) - } - } - - return chapterList.reversed() - } - - private fun parseMangaList(payload: JSONArray, sortType: SortType): MangasPage { - val mangas = ArrayList() - - for (i in 0 until payload.length()) { - val json = payload.getJSONObject(i) - val pinned = json.getBoolean("pinned") - - if (sortType == SortType.PINNED && pinned) { - mangas.add(parseMangaFromRemoteStorage(json)) - } else if (sortType == SortType.UNPINNED && !pinned) { - mangas.add(parseMangaFromRemoteStorage(json)) - } - } - - return MangasPage(mangas, false) - } - - private fun parseSearchList(payload: JSONObject, query: String): MangasPage { - val mangas = ArrayList() - val tempManga = SManga.create() - tempManga.url = "/read/$query" - mangas.add(parseMangaFromApi(payload, tempManga)) - return MangasPage(mangas, false) - } - - private fun parseMangaFromRemoteStorage(json: JSONObject): SManga { - val manga = SManga.create() - manga.title = json.getString("title") - manga.artist = json.optString("artist", ARTIST_FALLBACK) - manga.author = json.optString("author", AUTHOR_FALLBACK) - manga.description = json.optString("description", DESCRIPTION_FALLBACK) - manga.url = json.getString("url") - manga.thumbnail_url = json.getString("coverUrl") - - return manga - } - - private fun parseMangaFromApi(json: JSONObject, mangaReference: SManga): SManga { - val manga = SManga.create() - manga.title = json.getString("title") - manga.artist = json.optString("artist", ARTIST_FALLBACK) - manga.author = json.optString("author", AUTHOR_FALLBACK) - manga.description = json.optString("description", DESCRIPTION_FALLBACK) - manga.url = mangaReference.url - manga.thumbnail_url = json.optString("cover", "") - - return manga - } - - // ----------------- Things we aren't supporting ----------------- - - override fun imageUrlParse(response: Response): String { - throw Exception("imageUrlParse not supported.") - } - - companion object { - const val PROXY_PREFIX = "cubari:" - - const val AUTHOR_FALLBACK = "Unknown" - const val ARTIST_FALLBACK = "Unknown" - const val DESCRIPTION_FALLBACK = "No description." - - const val SEARCH_FALLBACK_MSG = "Unable to parse. Is your query in the format of $PROXY_PREFIX/?" - - enum class SortType { - PINNED, - UNPINNED - } - } -} diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariFactory.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariFactory.kt deleted file mode 100644 index 00f589e50..000000000 --- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.cubari - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class CubariFactory : SourceFactory { - override fun createSources(): List = listOf( - Cubari("en"), - Cubari("all"), - Cubari("other") - ) -} diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariUrlActivity.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariUrlActivity.kt deleted file mode 100644 index 4d06f04e9..000000000 --- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariUrlActivity.kt +++ /dev/null @@ -1,64 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.cubari - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -class CubariUrlActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val host = intent?.data?.host - val pathSegments = intent?.data?.pathSegments - - if (host != null && pathSegments != null) { - val query = when (host) { - "m.imgur.com", "imgur.com" -> fromImgur(pathSegments) - else -> fromCubari(pathSegments) - } - - if (query == null) { - Log.e("CubariUrlActivity", "Unable to parse URI from intent $intent") - finish() - exitProcess(1) - } - - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", query) - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("CubariUrlActivity", e.toString()) - } - } - - finish() - exitProcess(0) - } - - private fun fromImgur(pathSegments: List): String? { - if (pathSegments.size >= 2) { - val id = pathSegments[1] - - return "${Cubari.PROXY_PREFIX}imgur/$id" - } - return null - } - - private fun fromCubari(pathSegments: MutableList): String? { - return if (pathSegments.size >= 3) { - val source = pathSegments[1] - val slug = pathSegments[2] - "${Cubari.PROXY_PREFIX}$source/$slug" - } else { - null - } - } -} diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/RemoteStorageUtils.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/RemoteStorageUtils.kt deleted file mode 100644 index 3e4fbd060..000000000 --- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/RemoteStorageUtils.kt +++ /dev/null @@ -1,147 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.cubari - -import android.annotation.SuppressLint -import android.app.Application -import android.os.Handler -import android.os.Looper -import android.webkit.JavascriptInterface -import android.webkit.WebView -import android.webkit.WebViewClient -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.IOException -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -class RemoteStorageUtils { - abstract class GenericInterceptor(private val transparent: Boolean) : Interceptor { - private val handler = Handler(Looper.getMainLooper()) - - abstract val jsScript: String - - abstract fun urlModifier(originalUrl: String): String - - internal class JsInterface(private val latch: CountDownLatch, var payload: String = "") { - @JavascriptInterface - fun passPayload(passedPayload: String) { - payload = passedPayload - latch.countDown() - } - } - - @Synchronized - override fun intercept(chain: Interceptor.Chain): Response { - try { - val originalRequest = chain.request() - val originalResponse = chain.proceed(originalRequest) - return proceedWithWebView(originalRequest, originalResponse) - } catch (e: Exception) { - throw IOException(e) - } - } - - @SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface") - private fun proceedWithWebView(request: Request, response: Response): Response { - val latch = CountDownLatch(1) - - var webView: WebView? = null - - val origRequestUrl = request.url().toString() - val headers = request.headers().toMultimap().mapValues { - it.value.getOrNull(0) ?: "" - }.toMutableMap() - val jsInterface = JsInterface(latch) - - handler.post { - val webview = WebView(Injekt.get()) - webView = webview - with(webview.settings) { - javaScriptEnabled = true - domStorageEnabled = true - databaseEnabled = true - useWideViewPort = false - loadWithOverviewMode = false - userAgentString = request.header("User-Agent") - } - - webview.addJavascriptInterface(jsInterface, "android") - - webview.webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView, url: String) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - view.evaluateJavascript(jsScript) {} - } - if (transparent) { - latch.countDown() - } - } - } - - webview.loadUrl(urlModifier(origRequestUrl), headers) - } - - latch.await(TIMEOUT_SEC, TimeUnit.SECONDS) - - handler.postDelayed( - { webView?.destroy() }, - DELAY_MILLIS * (if (transparent) 2 else 1) - ) - - return if (transparent) { - response - } else { - response.newBuilder().body(ResponseBody.create(response.body()?.contentType(), jsInterface.payload)).build() - } - } - } - - class TagInterceptor : GenericInterceptor(true) { - override val jsScript: String = """ - let dispatched = false; - window.addEventListener('history-ready', function () { - if (!dispatched) { - dispatched = true; - Promise.all( - [globalHistoryHandler.getAllPinnedSeries(), globalHistoryHandler.getAllUnpinnedSeries()] - ).then(e => { - window.android.passPayload(JSON.stringify(e.flatMap(e => e))) - }); - } - }); - tag(); - """ - - override fun urlModifier(originalUrl: String): String { - return originalUrl.replace("/api/", "/").replace("/series/", "/") - } - } - - class HomeInterceptor : GenericInterceptor(false) { - override val jsScript: String = """ - let dispatched = false; - (function () { - if (!dispatched) { - dispatched = true; - Promise.all( - [globalHistoryHandler.getAllPinnedSeries(), globalHistoryHandler.getAllUnpinnedSeries()] - ).then(e => { - window.android.passPayload(JSON.stringify(e.flatMap(e => e) ) ) - }); - } - })(); - """ - - override fun urlModifier(originalUrl: String): String { - return originalUrl - } - } - - companion object { - const val TIMEOUT_SEC: Long = 10 - const val DELAY_MILLIS: Long = 10000 - } -} diff --git a/src/all/dragonball_multiverse/AndroidManifest.xml b/src/all/dragonball_multiverse/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/dragonball_multiverse/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/dragonball_multiverse/build.gradle b/src/all/dragonball_multiverse/build.gradle deleted file mode 100644 index b2dc0e724..000000000 --- a/src/all/dragonball_multiverse/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Dragon Ball Multiverse' - pkgNameSuffix = 'all.dragonball_multiverse' - extClass = '.DbMFactory' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/dragonball_multiverse/res/mipmap-hdpi/ic_launcher.png b/src/all/dragonball_multiverse/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e95d86d7c..000000000 Binary files a/src/all/dragonball_multiverse/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/dragonball_multiverse/res/mipmap-mdpi/ic_launcher.png b/src/all/dragonball_multiverse/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4cadc0850..000000000 Binary files a/src/all/dragonball_multiverse/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/dragonball_multiverse/res/mipmap-xhdpi/ic_launcher.png b/src/all/dragonball_multiverse/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bb9546dad..000000000 Binary files a/src/all/dragonball_multiverse/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/dragonball_multiverse/res/mipmap-xxhdpi/ic_launcher.png b/src/all/dragonball_multiverse/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ef4dd4b3f..000000000 Binary files a/src/all/dragonball_multiverse/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/dragonball_multiverse/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/dragonball_multiverse/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3ade0b35a..000000000 Binary files a/src/all/dragonball_multiverse/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/dragonball_multiverse/res/web_hi_res_512.png b/src/all/dragonball_multiverse/res/web_hi_res_512.png deleted file mode 100644 index 3aa681055..000000000 Binary files a/src/all/dragonball_multiverse/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/all/dragonball_multiverse/DbMFactory.kt b/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/all/dragonball_multiverse/DbMFactory.kt deleted file mode 100644 index cde0b0b2f..000000000 --- a/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/all/dragonball_multiverse/DbMFactory.kt +++ /dev/null @@ -1,83 +0,0 @@ -@file:Suppress("ClassName") - -package eu.kanade.tachiyomi.extension.all.dragonball_multiverse - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class DbMFactory : SourceFactory { - override fun createSources(): List = listOf( - DbMultiverseEN(), - DbMultiverseFR(), - DbMultiverseJP(), - DbMultiverseCN(), - DbMultiverseES(), - DbMultiverseIT(), - DbMultiversePT(), - DbMultiverseDE(), - DbMultiversePL(), - DbMultiverseNL(), - DbMultiverseFR_PA(), - DbMultiverseTR_TR(), - DbMultiversePT_BR(), - DbMultiverseHU_HU(), - DbMultiverseGA_ES(), - DbMultiverseCT_CT(), - DbMultiverseNO_NO(), - DbMultiverseRU_RU(), - DbMultiverseRO_RO(), - DbMultiverseEU_EH(), - DbMultiverseLT_LT(), - DbMultiverseHR_HR(), - DbMultiverseKR_KR(), - DbMultiverseFI_FI(), - DbMultiverseHE_HE(), - DbMultiverseBG_BG(), - DbMultiverseSV_SE(), - DbMultiverseGR_GR(), - DbMultiverseES_CO(), - DbMultiverseAR_JO(), - DbMultiverseTL_PI(), - DbMultiverseLA_LA(), - DbMultiverseDA_DK(), - DbMultiverseCO_FR(), - DbMultiverseBR_FR(), - DbMultiverseXX_VE() - ) -} - -class DbMultiverseFR : DbMultiverse("fr", "fr") -class DbMultiverseJP : DbMultiverse("ja", "jp") -class DbMultiverseCN : DbMultiverse("zh", "cn") -class DbMultiverseES : DbMultiverse("es", "es") -class DbMultiverseIT : DbMultiverse("it", "it") -class DbMultiversePT : DbMultiverse("pt", "pt") -class DbMultiverseDE : DbMultiverse("de", "de") -class DbMultiversePL : DbMultiverse("pl", "pl") -class DbMultiverseNL : DbMultiverse("nl", "nl") -class DbMultiverseFR_PA : DbMultiverse("fr", "fr_PA") -class DbMultiverseTR_TR : DbMultiverse("tr", "tr_TR") -class DbMultiversePT_BR : DbMultiverse("pt-BR", "pt_BR") -class DbMultiverseHU_HU : DbMultiverse("hu", "hu_HU") -class DbMultiverseGA_ES : DbMultiverse("ga", "ga_ES") -class DbMultiverseCT_CT : DbMultiverse("ca", "ct_CT") -class DbMultiverseNO_NO : DbMultiverse("no", "no_NO") -class DbMultiverseRU_RU : DbMultiverse("ru", "ru_RU") -class DbMultiverseRO_RO : DbMultiverse("ro", "ro_RO") -class DbMultiverseEU_EH : DbMultiverse("eu", "eu_EH") -class DbMultiverseLT_LT : DbMultiverse("lt", "lt_LT") -class DbMultiverseHR_HR : DbMultiverse("hr", "hr_HR") -class DbMultiverseKR_KR : DbMultiverse("ko", "kr_KR") -class DbMultiverseFI_FI : DbMultiverse("fi", "fi_FI") -class DbMultiverseHE_HE : DbMultiverse("he", "he_HE") -class DbMultiverseBG_BG : DbMultiverse("bg", "bg_BG") -class DbMultiverseSV_SE : DbMultiverse("sv", "sv_SE") -class DbMultiverseGR_GR : DbMultiverse("el", "gr_GR") -class DbMultiverseES_CO : DbMultiverse("es-419", "es_CO") -class DbMultiverseAR_JO : DbMultiverse("ar", "ar_JO") -class DbMultiverseTL_PI : DbMultiverse("fil", "tl_PI") -class DbMultiverseLA_LA : DbMultiverse("la", "la_LA") -class DbMultiverseDA_DK : DbMultiverse("da", "da_DK") -class DbMultiverseCO_FR : DbMultiverse("co", "co_FR") -class DbMultiverseBR_FR : DbMultiverse("br", "br_FR") -class DbMultiverseXX_VE : DbMultiverse("vec", "xx_VE") diff --git a/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/all/dragonball_multiverse/DbMultiverse.kt b/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/all/dragonball_multiverse/DbMultiverse.kt deleted file mode 100644 index eedf0186b..000000000 --- a/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/all/dragonball_multiverse/DbMultiverse.kt +++ /dev/null @@ -1,103 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.dragonball_multiverse - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -abstract class DbMultiverse(override val lang: String, private val internalLang: String) : ParsedHttpSource() { - - override val name = - if (internalLang.endsWith("_PA")) "Dragon Ball Multiverse Parody" - else "Dragon Ball Multiverse" - override val baseUrl = "https://www.dragonball-multiverse.com" - override val supportsLatest = false - - private fun chapterFromElement(element: Element, name: String): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.attr("abs:href")) - chapter.name = name + element.text().let { num -> - if (num.contains("-")) { - "Pages $num" - } else { - "Page $num" - } - } - return chapter - } - - override fun chapterListSelector(): String = "div.cadrelect.chapters a[href*=page-]" - - override fun chapterListParse(response: Response): List { - val chapters = mutableListOf() - val document = response.asJsoup() - - document.select("div[ch]").forEach { container -> - container.select(chapterListSelector()).mapIndexed { i, chapter -> - // Each page is its own chapter, add chapter name when a first page is mapped - val name = if (i == 0) container.select("h4").text() + " - " else "" - chapters.add(chapterFromElement(chapter, name)) - } - } - - return chapters.reversed() - } - - override fun pageListParse(document: Document): List { - return document.select("div#h_read img").mapIndexed { index, element -> - Page(index, "", element.attr("abs:src")) - } - } - - override fun mangaDetailsParse(document: Document): SManga = createManga(document) - - override fun fetchPopularManga(page: Int): Observable { - return Observable.just(MangasPage(listOf(createManga(null)), hasNextPage = false)) - } - - private fun createManga(document: Document?) = SManga.create().apply { - title = name - status = SManga.ONGOING - url = "/$internalLang/chapters.html" - description = "Dragon Ball Multiverse (DBM) is a free online comic, made by a whole team of fans. It's our personal sequel to DBZ." - thumbnail_url = document?.select("div[ch=\"1\"] img")?.attr("abs:src") - } - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException() - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() - - override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException() - - override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException() - - override fun popularMangaSelector(): String = throw UnsupportedOperationException() - - override fun popularMangaNextPageSelector(): String? = throw UnsupportedOperationException() - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable = Observable.just(MangasPage(emptyList(), false)) - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException() - - override fun searchMangaNextPageSelector(): String? = throw UnsupportedOperationException() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException() - - override fun searchMangaSelector(): String = throw UnsupportedOperationException() - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException() - - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException() - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException() - - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException() -} diff --git a/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/en/dragonball_multiverse/DbMultiverseEN.kt b/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/en/dragonball_multiverse/DbMultiverseEN.kt deleted file mode 100644 index 75f623e45..000000000 --- a/src/all/dragonball_multiverse/src/eu/kanade/tachiyomi/extension/en/dragonball_multiverse/DbMultiverseEN.kt +++ /dev/null @@ -1,3 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.dragonball_multiverse - -class DbMultiverseEN : DbMultiverse("en", "en") diff --git a/src/all/ehentai/AndroidManifest.xml b/src/all/ehentai/AndroidManifest.xml deleted file mode 100644 index 65f41e2df..000000000 --- a/src/all/ehentai/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/all/ehentai/build.gradle b/src/all/ehentai/build.gradle deleted file mode 100644 index 28883d511..000000000 --- a/src/all/ehentai/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'E-Hentai' - pkgNameSuffix = 'all.ehentai' - extClass = '.EHFactory' - extVersionCode = 13 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/ehentai/res/mipmap-hdpi/ic_launcher.png b/src/all/ehentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8d3d19a6a..000000000 Binary files a/src/all/ehentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ehentai/res/mipmap-mdpi/ic_launcher.png b/src/all/ehentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d7f9cd29f..000000000 Binary files a/src/all/ehentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ehentai/res/mipmap-xhdpi/ic_launcher.png b/src/all/ehentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 81800d329..000000000 Binary files a/src/all/ehentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ehentai/res/mipmap-xxhdpi/ic_launcher.png b/src/all/ehentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6b03694df..000000000 Binary files a/src/all/ehentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ehentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/ehentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0fa0128b0..000000000 Binary files a/src/all/ehentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ehentai/res/web_hi_res_512.png b/src/all/ehentai/res/web_hi_res_512.png deleted file mode 100644 index 161cdca75..000000000 Binary files a/src/all/ehentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHFactory.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHFactory.kt deleted file mode 100644 index 310ac33b5..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHFactory.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class EHFactory : SourceFactory { - override fun createSources(): List = listOf( - EHentai("ja", "japanese"), - EHentai("en", "english"), - EHentai("zh", "chinese"), - EHentai("nl", "dutch"), - EHentai("fr", "french"), - EHentai("de", "german"), - EHentai("hu", "hungarian"), - EHentai("it", "italian"), - EHentai("ko", "korean"), - EHentai("pl", "polish"), - EHentai("pt", "portuguese"), - EHentai("ru", "russian"), - EHentai("es", "spanish"), - EHentai("th", "thai"), - EHentai("vi", "vietnamese"), - EHentai("none", "n/a"), - EHentai("other", "other") - ) -} diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHUrlActivity.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHUrlActivity.kt deleted file mode 100644 index 5010414f3..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHUrlActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://e-hentai.net/g/xxxxx/yyyyy/ intents and redirects them to - * the main Tachiyomi process. - */ -class EHUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 2) { - val id = pathSegments[1] - val key = pathSegments[2] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${EHentai.PREFIX_ID_SEARCH}$id/$key") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("EHUrlActivity", e.toString()) - } - } else { - Log.e("EHUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHUtil.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHUtil.kt deleted file mode 100644 index 8db29e4ce..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHUtil.kt +++ /dev/null @@ -1,70 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import kotlin.math.ln -import kotlin.math.pow - -/** - * Various utility methods used in the E-Hentai source - */ - -/** - * Return null if String is blank, otherwise returns the original String - * @returns null if the String is blank, otherwise returns the original String - */ -fun String?.nullIfBlank(): String? = if (isNullOrBlank()) - null -else - this - -/** - * Ignores any exceptions thrown inside a block - */ -fun ignore(expr: () -> T): T? { - return try { - expr() - } catch (t: Throwable) { - null - } -} - -/** - * Use '+' to append Strings onto a StringBuilder - */ -operator fun StringBuilder.plusAssign(other: String) { - append(other) -} - -/** - * Converts bytes into a human readable String - */ -fun humanReadableByteCount(bytes: Long, si: Boolean): String { - val unit = if (si) 1000 else 1024 - if (bytes < unit) return "$bytes B" - val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() - val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i" - return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre) -} - -private const val KB_FACTOR = 1000 -private const val KIB_FACTOR = 1024 -private const val MB_FACTOR = 1000 * KB_FACTOR -private const val MIB_FACTOR = 1024 * KIB_FACTOR -private const val GB_FACTOR = 1000 * MB_FACTOR -private const val GIB_FACTOR = 1024 * MIB_FACTOR - -/** - * Parse human readable size Strings - */ -fun parseHumanReadableByteCount(arg0: String): Double? { - val spaceNdx = arg0.indexOf(" ") - val ret = arg0.substring(0 until spaceNdx).toDouble() - when (arg0.substring(spaceNdx + 1)) { - "GB" -> return ret * GB_FACTOR - "GiB" -> return ret * GIB_FACTOR - "MB" -> return ret * MB_FACTOR - "MiB" -> return ret * MIB_FACTOR - "KB" -> return ret * KB_FACTOR - "KiB" -> return ret * KIB_FACTOR - } - return null -} diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHentai.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHentai.kt deleted file mode 100644 index 4ad56d9de..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/EHentai.kt +++ /dev/null @@ -1,533 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import android.annotation.SuppressLint -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import androidx.preference.CheckBoxPreference -import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.Filter.CheckBox -import eu.kanade.tachiyomi.source.model.Filter.Group -import eu.kanade.tachiyomi.source.model.Filter.Select -import eu.kanade.tachiyomi.source.model.Filter.Text -import eu.kanade.tachiyomi.source.model.Filter.TriState -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.CookieJar -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.net.URLEncoder -import android.support.v7.preference.CheckBoxPreference as LegacyCheckBoxPreference -import android.support.v7.preference.PreferenceScreen as LegacyPreferenceScreen - -open class EHentai(override val lang: String, private val ehLang: String) : ConfigurableSource, HttpSource() { - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override val name = "E-Hentai" - - override val baseUrl = "https://e-hentai.org" - - override val supportsLatest = true - - // true if lang is a "natural human language" - private fun isLangNatural(): Boolean = lang !in listOf("none", "other") - - private fun genericMangaParse(response: Response): MangasPage { - val doc = response.asJsoup() - val parsedMangas = doc.select("table.itg td.glname") - .let { elements -> - if (isLangNatural() && getEnforceLanguagePref()) { - elements.filter { element -> - // only accept elements with a language tag matching ehLang or without a language tag - // could make this stricter and not accept elements without a language tag, possibly add a sharedpreference for it - element.select("div[title^=language]").firstOrNull()?.let { it.text() == ehLang } ?: true - } - } else { - elements - } - } - .map { - SManga.create().apply { - // Get title - it.select("a")?.first()?.apply { - title = this.select(".glink").text() - url = ExGalleryMetadata.normalizeUrl(attr("href")) - } - // Get image - it.parent().select(".glthumb img")?.first().apply { - thumbnail_url = this?.attr("data-src")?.nullIfBlank() - ?: this?.attr("src") - } - } - } - - // Add to page if required - val hasNextPage = doc.select("a[onclick=return false]").last()?.text() == ">" - - return MangasPage(parsedMangas, hasNextPage) - } - - override fun fetchChapterList(manga: SManga): Observable> = Observable.just( - listOf( - SChapter.create().apply { - url = manga.url - name = "Chapter" - chapter_number = 1f - } - ) - ) - - override fun fetchPageList(chapter: SChapter) = fetchChapterPage(chapter, "$baseUrl/${chapter.url}").map { - it.mapIndexed { i, s -> - Page(i, s) - } - }!! - - /** - * Recursively fetch chapter pages - */ - private fun fetchChapterPage( - chapter: SChapter, - np: String, - pastUrls: List = emptyList() - ): Observable> { - val urls = ArrayList(pastUrls) - return chapterPageCall(np).flatMap { - val jsoup = it.asJsoup() - urls += parseChapterPage(jsoup) - nextPageUrl(jsoup)?.let { string -> - fetchChapterPage(chapter, string, urls) - } ?: Observable.just(urls) - } - } - - private fun parseChapterPage(response: Element) = with(response) { - select(".gdtm a").map { - Pair(it.child(0).attr("alt").toInt(), it.attr("href")) - }.sortedBy(Pair::first).map { it.second } - } - - private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess() - private fun chapterPageRequest(np: String) = exGet(np, null, headers) - - private fun nextPageUrl(element: Element) = element.select("a[onclick=return false]").last()?.let { - if (it.text() == ">") it.attr("href") else null - } - - private fun languageTag(enforceLanguageFilter: Boolean = false): String { - return if (enforceLanguageFilter || getEnforceLanguagePref()) "language:$ehLang" else "" - } - - override fun popularMangaRequest(page: Int) = if (isLangNatural()) { - exGet("$baseUrl/?f_search=${languageTag()}&f_srdd=5&f_sr=on", page) - } else { - latestUpdatesRequest(page) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val enforceLanguageFilter = filters.find { it is EnforceLanguageFilter }?.state == true - val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon() - var modifiedQuery = when { - !isLangNatural() -> query - query.isBlank() -> languageTag(enforceLanguageFilter) - else -> languageTag(enforceLanguageFilter).let { if (it.isNotEmpty()) "$query,$it" else query } - } - modifiedQuery += filters.filterIsInstance() - .flatMap { it.markedTags() } - .joinToString(",") - .let { if (it.isNotEmpty()) ",$it" else it } - uri.appendQueryParameter("f_search", modifiedQuery) - filters.forEach { - if (it is UriFilter) it.addToUri(uri) - } - return exGet(uri.toString(), page) - } - - override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page) - - override fun popularMangaParse(response: Response) = genericMangaParse(response) - override fun searchMangaParse(response: Response) = genericMangaParse(response) - override fun latestUpdatesParse(response: Response) = genericMangaParse(response) - - private fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true): Request { - return GET( - page?.let { - addParam(url, "page", (page - 1).toString()) - } ?: url, - additionalHeaders?.let { header -> - val headers = headers.newBuilder() - header.toMultimap().forEach { (t, u) -> - u.forEach { - headers.add(t, it) - } - } - headers.build() - } ?: headers - ).let { - if (!cache) { - it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build() - } else { - it - } - } - } - - /** - * Parse gallery page to metadata model - */ - @SuppressLint("DefaultLocale") - override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) { - with(ExGalleryMetadata()) { - url = response.request().url().encodedPath() - title = select("#gn").text().nullIfBlank()?.trim() - - altTitle = select("#gj").text().nullIfBlank()?.trim() - - // Thumbnail is set as background of element in style attribute - thumbnailUrl = select("#gd1 div").attr("style").nullIfBlank()?.let { - it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')')) - } - genre = select("#gdc div").text().nullIfBlank()?.trim()?.toLowerCase() - - uploader = select("#gdn").text().nullIfBlank()?.trim() - - // Parse the table - select("#gdd tr").forEach { - it.select(".gdt1") - .text() - .nullIfBlank() - ?.trim() - ?.let { left -> - it.select(".gdt2") - .text() - .nullIfBlank() - ?.trim() - ?.let { right -> - ignore { - when ( - left.removeSuffix(":") - .toLowerCase() - ) { - "posted" -> datePosted = EX_DATE_FORMAT.parse(right)?.time ?: 0 - "visible" -> visible = right.nullIfBlank() - "language" -> { - language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank() - translated = right.endsWith(TR_SUFFIX, true) - } - "file size" -> size = parseHumanReadableByteCount(right)?.toLong() - "length" -> length = right.removeSuffix("pages").trim().nullIfBlank()?.toInt() - "favorited" -> favorites = right.removeSuffix("times").trim().nullIfBlank()?.toInt() - } - } - } - } - } - - // Parse ratings - ignore { - averageRating = select("#rating_label") - .text() - .removePrefix("Average:") - .trim() - .nullIfBlank() - ?.toDouble() - ratingCount = select("#rating_count") - .text() - .trim() - .nullIfBlank() - ?.toInt() - } - - // Parse tags - tags.clear() - select("#taglist tr").forEach { - val namespace = it.select(".tc").text().removeSuffix(":") - val currentTags = it.select("div").map { element -> - Tag( - element.text().trim(), - element.hasClass("gtl") - ) - } - tags[namespace] = currentTags - } - - // Copy metadata to manga - SManga.create().apply { - copyTo(this) - } - } - } - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/g/$id", headers) - - private fun searchMangaByIdParse(response: Response, id: String): MangasPage { - val details = mangaDetailsParse(response) - details.url = "/g/$id/" - return MangasPage(listOf(details), false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(searchMangaByIdRequest(id)) - .asObservableSuccess() - .map { response -> searchMangaByIdParse(response, id) } - } else { - super.fetchSearchManga(page, query, filters) - } - } - - override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Unused method was called somehow!") - - override fun pageListParse(response: Response) = throw UnsupportedOperationException("Unused method was called somehow!") - - override fun imageUrlParse(response: Response): String = response.asJsoup().select("#img").attr("abs:src") - - private val cookiesHeader by lazy { - val cookies = mutableMapOf() - - // Setup settings - val settings = mutableListOf() - - // Do not show popular right now pane as we can't parse it - settings += "prn_n" - - // Exclude every other language except the one we have selected - settings += "xl_" + languageMappings.filter { it.first != ehLang } - .flatMap { it.second } - .joinToString("x") - - cookies["uconfig"] = buildSettings(settings) - - // Bypass "Offensive For Everyone" content warning - cookies["nw"] = "1" - - buildCookies(cookies) - } - - // Headers - override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader)!! - - private fun buildSettings(settings: List) = settings.filterNotNull().joinToString(separator = "-") - - private fun buildCookies(cookies: Map) = cookies.entries.joinToString(separator = "; ", postfix = ";") { - "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" - } - - @Suppress("SameParameterValue") - private fun addParam(url: String, param: String, value: String) = Uri.parse(url) - .buildUpon() - .appendQueryParameter(param, value) - .toString() - - override val client = network.client.newBuilder() - .cookieJar(CookieJar.NO_COOKIES) - .addInterceptor { chain -> - val newReq = chain - .request() - .newBuilder() - .removeHeader("Cookie") - .addHeader("Cookie", cookiesHeader) - .build() - - chain.proceed(newReq) - }.build()!! - - // Filters - override fun getFilterList() = FilterList( - EnforceLanguageFilter(getEnforceLanguagePref()), - Watched(), - GenreGroup(), - TagFilter("Misc Tags", triStateBoxesFrom(miscTags), ""), - TagFilter("Female Tags", triStateBoxesFrom(femaleTags), "female"), - TagFilter("Male Tags", triStateBoxesFrom(maleTags), "male"), - AdvancedGroup() - ) - - class Watched : CheckBox("Watched List"), UriFilter { - override fun addToUri(builder: Uri.Builder) { - if (state) - builder.appendPath("watched") - } - } - - class GenreOption(name: String, private val genreId: String) : CheckBox(name, false), UriFilter { - override fun addToUri(builder: Uri.Builder) { - builder.appendQueryParameter("f_$genreId", if (state) "1" else "0") - } - } - - class GenreGroup : UriGroup( - "Genres", - listOf( - GenreOption("Dōjinshi", "doujinshi"), - GenreOption("Manga", "manga"), - GenreOption("Artist CG", "artistcg"), - GenreOption("Game CG", "gamecg"), - GenreOption("Western", "western"), - GenreOption("Non-H", "non-h"), - GenreOption("Image Set", "imageset"), - GenreOption("Cosplay", "cosplay"), - GenreOption("Asian Porn", "asianporn"), - GenreOption("Misc", "misc") - ) - ) - - class AdvancedOption(name: String, private val param: String, defValue: Boolean = false) : CheckBox(name, defValue), UriFilter { - override fun addToUri(builder: Uri.Builder) { - if (state) - builder.appendQueryParameter(param, "on") - } - } - - open class PageOption(name: String, private val queryKey: String) : Text(name), UriFilter { - override fun addToUri(builder: Uri.Builder) { - if (state.isNotBlank()) { - if (builder.build().getQueryParameters("f_sp").isEmpty()) { - builder.appendQueryParameter("f_sp", "on") - } - - builder.appendQueryParameter(queryKey, state.trim()) - } - } - } - - class MinPagesOption : PageOption("Minimum Pages", "f_spf") - class MaxPagesOption : PageOption("Maximum Pages", "f_spt") - - class RatingOption : - Select( - "Minimum Rating", - arrayOf( - "Any", - "2 stars", - "3 stars", - "4 stars", - "5 stars" - ) - ), - UriFilter { - override fun addToUri(builder: Uri.Builder) { - if (state > 0) { - builder.appendQueryParameter("f_srdd", (state + 1).toString()) - builder.appendQueryParameter("f_sr", "on") - } - } - } - - // Explicit type arg for listOf() to workaround this: KT-16570 - class AdvancedGroup : UriGroup>( - "Advanced Options", - listOf( - AdvancedOption("Search Gallery Name", "f_sname", true), - AdvancedOption("Search Gallery Tags", "f_stags", true), - AdvancedOption("Search Gallery Description", "f_sdesc"), - AdvancedOption("Search Torrent Filenames", "f_storr"), - AdvancedOption("Only Show Galleries With Torrents", "f_sto"), - AdvancedOption("Search Low-Power Tags", "f_sdt1"), - AdvancedOption("Search Downvoted Tags", "f_sdt2"), - AdvancedOption("Show Expunged Galleries", "f_sh"), - RatingOption(), - MinPagesOption(), - MaxPagesOption() - ) - ) - - private class EnforceLanguageFilter(default: Boolean) : CheckBox("Enforce language", default) - - private val miscTags = "3d, already uploaded, anaglyph, animal on animal, animated, anthology, arisa mizuhara, artbook, ashiya noriko, bailey jay, body swap, caption, chouzuki maryou, christian godard, comic, compilation, dakimakura, fe galvao, ffm threesome, figure, forbidden content, full censorship, full color, game sprite, goudoushi, group, gunyou mikan, harada shigemitsu, hardcore, helly von valentine, higurashi rin, hololive, honey select, how to, incest, incomplete, ishiba yoshikazu, jessica nigri, kalinka fox, kanda midori, kira kira, kitami eri, kuroi hiroki, lenfried, lincy leaw, marie claude bourbonnais, matsunaga ayaka, me me me, missing cover, mmf threesome, mmt threesome, mosaic censorship, mtf threesome, multi-work series, no penetration, non-nude, novel, nudity only, oakazaki joe, out of order, paperchild, pm02 colon 20, poor grammar, radio comix, realporn, redraw, replaced, sakaki kasa, sample, saotome love, scanmark, screenshots, sinful goddesses, sketch lines, stereoscopic, story arc, takeuti ken, tankoubon, themeless, tikuma jukou, time stop, tsubaki zakuro, ttm threesome, twins, uncensored, vandych alex, variant set, watermarked, webtoon, western cg, western imageset, western non-h, yamato nadeshiko club, yui okada, yukkuri, zappa go" - private val femaleTags = "ahegao, anal, angel, apron, bandages, bbw, bdsm, beauty mark, big areolae, big ass, big breasts, big clit, big lips, big nipples, bikini, blackmail, bloomers, blowjob, bodysuit, bondage, breast expansion, bukkake, bunny girl, business suit, catgirl, centaur, cheating, chinese dress, christmas, collar, corset, cosplaying, cowgirl, crossdressing, cunnilingus, dark skin, daughter, deepthroat, defloration, demon girl, double penetration, dougi, dragon, drunk, elf, exhibitionism, farting, females only, femdom, filming, fingering, fishnets, footjob, fox girl, furry, futanari, garter belt, ghost, giantess, glasses, gloves, goblin, gothic lolita, growth, guro, gyaru, hair buns, hairy, hairy armpits, handjob, harem, hidden sex, horns, huge breasts, humiliation, impregnation, incest, inverted nipples, kemonomimi, kimono, kissing, lactation, latex, leg lock, leotard, lingerie, lizard girl, maid, masked face, masturbation, midget, miko, milf, mind break, mind control, monster girl, mother, muscle, nakadashi, netorare, nose hook, nun, nurse, oil, paizuri, panda girl, pantyhose, piercing, pixie cut, policewoman, ponytail, pregnant, rape, rimjob, robot, scat, schoolgirl uniform, sex toys, shemale, sister, small breasts, smell, sole dickgirl, sole female, squirting, stockings, sundress, sweating, swimsuit, swinging, tail, tall girl, teacher, tentacles, thigh high boots, tomboy, transformation, twins, twintails, unusual pupils, urination, vore, vtuber, widow, wings, witch, wolf girl, x-ray, yuri, zombie" - private val maleTags = "anal, bbm, big ass, big penis, bikini, blood, blowjob, bondage, catboy, cheating, chikan, condom, crab, crossdressing, dark skin, deepthroat, demon, dickgirl on male, dilf, dog boy, double anal, double penetration, dragon, drunk, exhibitionism, facial hair, feminization, footjob, fox boy, furry, glasses, group, guro, hairy, handjob, hidden sex, horns, huge penis, human on furry, kimono, lingerie, lizard guy, machine, maid, males only, masturbation, mmm threesome, monster, muscle, nakadashi, ninja, octopus, oni, pillory, policeman, possession, prostate massage, public use, schoolboy uniform, schoolgirl uniform, sex toys, shotacon, sleeping, snuff, sole male, stockings, sunglasses, swimsuit, tall man, tentacles, tomgirl, unusual pupils, virginity, waiter, x-ray, yaoi, zombie" - - private fun triStateBoxesFrom(tagString: String): List = tagString.split(", ").map { TagTriState(it) } - - class TagTriState(tag: String) : TriState(tag) - class TagFilter(name: String, private val triStateBoxes: List, private val nameSpace: String) : Group(name, triStateBoxes) { - fun markedTags() = triStateBoxes.filter { it.isIncluded() }.map { "$nameSpace:${it.name}" } + triStateBoxes.filter { it.isExcluded() }.map { "-$nameSpace:${it.name}" } - } - - // map languages to their internal ids - private val languageMappings = listOf( - Pair("japanese", listOf("0", "1024", "2048")), - Pair("english", listOf("1", "1025", "2049")), - Pair("chinese", listOf("10", "1034", "2058")), - Pair("dutch", listOf("20", "1044", "2068")), - Pair("french", listOf("30", "1054", "2078")), - Pair("german", listOf("40", "1064", "2088")), - Pair("hungarian", listOf("50", "1074", "2098")), - Pair("italian", listOf("60", "1084", "2108")), - Pair("korean", listOf("70", "1094", "2118")), - Pair("polish", listOf("80", "1104", "2128")), - Pair("portuguese", listOf("90", "1114", "2138")), - Pair("russian", listOf("100", "1124", "2148")), - Pair("spanish", listOf("110", "1134", "2158")), - Pair("thai", listOf("120", "1144", "2168")), - Pair("vietnamese", listOf("130", "1154", "2178")), - Pair("n/a", listOf("254", "1278", "2302")), - Pair("other", listOf("255", "1279", "2303")) - ) - - companion object { - const val QUERY_PREFIX = "?f_apply=Apply+Filter" - const val PREFIX_ID_SEARCH = "id:" - const val TR_SUFFIX = "TR" - - // Preferences vals - private const val ENFORCE_LANGUAGE_PREF_KEY = "ENFORCE_LANGUAGE" - private const val ENFORCE_LANGUAGE_PREF_TITLE = "Enforce Language" - private const val ENFORCE_LANGUAGE_PREF_SUMMARY = "If checked, forces browsing of manga matching a language tag" - private const val ENFORCE_LANGUAGE_PREF_DEFAULT_VALUE = false - } - - // Preferences - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val enforceLanguagePref = CheckBoxPreference(screen.context).apply { - key = "${ENFORCE_LANGUAGE_PREF_KEY}_$lang" - title = ENFORCE_LANGUAGE_PREF_TITLE - summary = ENFORCE_LANGUAGE_PREF_SUMMARY - setDefaultValue(ENFORCE_LANGUAGE_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${ENFORCE_LANGUAGE_PREF_KEY}_$lang", checkValue).commit() - } - } - screen.addPreference(enforceLanguagePref) - } - - override fun setupPreferenceScreen(screen: LegacyPreferenceScreen) { - val enforceLanguagePref = LegacyCheckBoxPreference(screen.context).apply { - key = "${ENFORCE_LANGUAGE_PREF_KEY}_$lang" - title = ENFORCE_LANGUAGE_PREF_TITLE - summary = ENFORCE_LANGUAGE_PREF_SUMMARY - setDefaultValue(ENFORCE_LANGUAGE_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${ENFORCE_LANGUAGE_PREF_KEY}_$lang", checkValue).commit() - } - } - screen.addPreference(enforceLanguagePref) - } - - private fun getEnforceLanguagePref(): Boolean = preferences.getBoolean("${ENFORCE_LANGUAGE_PREF_KEY}_$lang", ENFORCE_LANGUAGE_PREF_DEFAULT_VALUE) -} diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/ExGalleryMetadata.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/ExGalleryMetadata.kt deleted file mode 100644 index f209cb9bb..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/ExGalleryMetadata.kt +++ /dev/null @@ -1,52 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import android.net.Uri - -/** - * Gallery metadata storage model - */ - -class ExGalleryMetadata { - var url: String? = null - - var thumbnailUrl: String? = null - - var title: String? = null - var altTitle: String? = null - - var genre: String? = null - - var datePosted: Long? = null - var parent: String? = null - var visible: String? = null // Not a boolean - var language: String? = null - var translated: Boolean? = null - var size: Long? = null - var length: Int? = null - var favorites: Int? = null - var ratingCount: Int? = null - var averageRating: Double? = null - - var uploader: String? = null - - val tags: MutableMap> = mutableMapOf() - - companion object { - private fun splitGalleryUrl(url: String) = url.let { - // Only parse URL if is full URL - val pathSegments = if (it.startsWith("http")) - Uri.parse(it).pathSegments - else - it.split('/') - pathSegments.filterNot(String::isNullOrBlank) - } - - private fun galleryId(url: String) = splitGalleryUrl(url)[1] - - private fun galleryToken(url: String) = splitGalleryUrl(url)[2] - - private fun normalizeUrl(id: String, token: String) = "/g/$id/$token/?nw=always" - - fun normalizeUrl(url: String) = normalizeUrl(galleryId(url), galleryToken(url)) - } -} diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/MetadataCopier.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/MetadataCopier.kt deleted file mode 100644 index fef785b69..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/MetadataCopier.kt +++ /dev/null @@ -1,84 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import eu.kanade.tachiyomi.source.model.SManga -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -private const val EH_ARTIST_NAMESPACE = "artist" -private const val EH_AUTHOR_NAMESPACE = "author" - -private val ONGOING_SUFFIX = arrayOf( - "[ongoing]", - "(ongoing)", - "{ongoing}" -) - -val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) - -fun ExGalleryMetadata.copyTo(manga: SManga) { - url?.let { manga.url = it } - thumbnailUrl?.let { manga.thumbnail_url = it } - - (title ?: altTitle)?.let { manga.title = it } - - // Set artist (if we can find one) - tags[EH_ARTIST_NAMESPACE]?.let { - if (it.isNotEmpty()) manga.artist = it.joinToString(transform = Tag::name) - } - // Set author (if we can find one) - tags[EH_AUTHOR_NAMESPACE]?.let { - if (it.isNotEmpty()) manga.author = it.joinToString(transform = Tag::name) - } - // Set genre - genre?.let { manga.genre = it } - - // Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes - // We default to completed - manga.status = SManga.COMPLETED - title?.let { t -> - if (ONGOING_SUFFIX.any { - t.endsWith(it, ignoreCase = true) - } - ) manga.status = SManga.ONGOING - } - - // Build a nice looking description out of what we know - val titleDesc = StringBuilder() - title?.let { titleDesc += "Title: $it\n" } - altTitle?.let { titleDesc += "Alternate Title: $it\n" } - - val detailsDesc = StringBuilder() - uploader?.let { detailsDesc += "Uploader: $it\n" } - datePosted?.let { detailsDesc += "Posted: ${EX_DATE_FORMAT.format(Date(it))}\n" } - visible?.let { detailsDesc += "Visible: $it\n" } - language?.let { - detailsDesc += "Language: $it" - if (translated == true) detailsDesc += " TR" - detailsDesc += "\n" - } - size?.let { detailsDesc += "File Size: ${humanReadableByteCount(it, true)}\n" } - length?.let { detailsDesc += "Length: $it pages\n" } - favorites?.let { detailsDesc += "Favorited: $it times\n" } - averageRating?.let { - detailsDesc += "Rating: $it" - ratingCount?.let { count -> detailsDesc += " ($count)" } - detailsDesc += "\n" - } - - val tagsDesc = buildTagsDescription(this) - - manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") -} - -private fun buildTagsDescription(metadata: ExGalleryMetadata) = StringBuilder("Tags:\n").apply { - // BiConsumer only available in Java 8, we have to use destructuring here - metadata.tags.forEach { (namespace, tags) -> - if (tags.isNotEmpty()) { - val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" }) - this += "▪ $namespace: $joinedTags\n" - } - } -} diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/Tag.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/Tag.kt deleted file mode 100644 index c5a673848..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/Tag.kt +++ /dev/null @@ -1,6 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -/** - * Simple tag model - */ -data class Tag(val name: String, val light: Boolean) diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/UriFilter.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/UriFilter.kt deleted file mode 100644 index e51416900..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/UriFilter.kt +++ /dev/null @@ -1,10 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import android.net.Uri - -/** - * Uri filter - */ -interface UriFilter { - fun addToUri(builder: Uri.Builder) -} diff --git a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/UriGroup.kt b/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/UriGroup.kt deleted file mode 100644 index 431462f68..000000000 --- a/src/all/ehentai/src/eu/kanade/tachiyomi/extension/all/ehentai/UriGroup.kt +++ /dev/null @@ -1,15 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ehentai - -import android.net.Uri -import eu.kanade.tachiyomi.source.model.Filter - -/** - * UriGroup - */ -open class UriGroup(name: String, state: List) : Filter.Group(name, state), UriFilter { - override fun addToUri(builder: Uri.Builder) { - state.forEach { - if (it is UriFilter) it.addToUri(builder) - } - } -} diff --git a/src/all/hentaihand/AndroidManifest.xml b/src/all/hentaihand/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/hentaihand/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/hentaihand/build.gradle b/src/all/hentaihand/build.gradle deleted file mode 100644 index 8dd081b07..000000000 --- a/src/all/hentaihand/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HentaiHand' - pkgNameSuffix = 'all.hentaihand' - extClass = '.HentaiHandFactory' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/hentaihand/res/mipmap-hdpi/ic_launcher.png b/src/all/hentaihand/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 336381de2..000000000 Binary files a/src/all/hentaihand/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hentaihand/res/mipmap-mdpi/ic_launcher.png b/src/all/hentaihand/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c90b88eae..000000000 Binary files a/src/all/hentaihand/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hentaihand/res/mipmap-xhdpi/ic_launcher.png b/src/all/hentaihand/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c8b45fbb8..000000000 Binary files a/src/all/hentaihand/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hentaihand/res/mipmap-xxhdpi/ic_launcher.png b/src/all/hentaihand/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 739c509e2..000000000 Binary files a/src/all/hentaihand/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hentaihand/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/hentaihand/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d01b1eeee..000000000 Binary files a/src/all/hentaihand/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hentaihand/res/web_hi_res_512.png b/src/all/hentaihand/res/web_hi_res_512.png deleted file mode 100644 index 9a2c58d6d..000000000 Binary files a/src/all/hentaihand/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHand.kt b/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHand.kt deleted file mode 100644 index 9bd165f46..000000000 --- a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHand.kt +++ /dev/null @@ -1,396 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hentaihand - -import android.annotation.SuppressLint -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.text.InputType -import android.widget.Toast -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.nullObj -import com.github.salomonbrys.kotson.nullString -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.json.JSONException -import org.json.JSONObject -import rx.Observable -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat - -@Nsfw -class HentaiHand( - override val lang: String, - private val hhLangId: Int? = null, - extraName: String = "" -) : ConfigurableSource, HttpSource() { - - override val baseUrl: String = "https://hentaihand.com" - override val name: String = "HentaiHand$extraName" - override val supportsLatest = true - - private val gson = Gson() - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor { authIntercept(it) } - .build() - - private fun parseGenericResponse(response: Response): MangasPage { - val data = gson.fromJson(response.body()!!.string()) - return MangasPage( - data.getAsJsonArray("data").map { - SManga.create().apply { - url = "/en/comic/${it["slug"].asString}" - title = it["title"].asString - thumbnail_url = it["thumb_url"].asString - } - }, - !data["next_page_url"].isJsonNull - ) - } - - // Popular - - override fun popularMangaParse(response: Response): MangasPage = parseGenericResponse(response) - - override fun popularMangaRequest(page: Int): Request { - val url = "$baseUrl/api/comics?page=$page&sort=popularity&order=desc&duration=all" - return GET(if (hhLangId == null) url else ("$url&languages=$hhLangId")) - } - - // Latest - - override fun latestUpdatesParse(response: Response): MangasPage = parseGenericResponse(response) - - override fun latestUpdatesRequest(page: Int): Request { - val url = "$baseUrl/api/comics?page=$page&sort=uploaded_at&order=desc&duration=week" - return GET(if (hhLangId == null) url else ("$url&languages=$hhLangId")) - } - - // Search - - override fun searchMangaParse(response: Response): MangasPage = parseGenericResponse(response) - - private fun lookupFilterId(query: String, uri: String): Int? { - // filter query needs to be resolved to an ID - return client.newCall(GET("$baseUrl/api/$uri?q=$query")) - .asObservableSuccess() - .subscribeOn(Schedulers.io()) - .map { - val data = gson.fromJson(it.body()!!.string()) - // only the first tag will be used - data.getAsJsonArray("data").firstOrNull()?.let { t -> t["id"].asInt } - }.toBlocking().first() - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - - val url = HttpUrl.parse("$baseUrl/api/comics")!!.newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("q", query) - - if (hhLangId != null) - url.addQueryParameter("languages", hhLangId.toString()) - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is SortFilter -> url.addQueryParameter("sort", getSortPairs()[filter.state].second) - is OrderFilter -> url.addQueryParameter("order", getOrderPairs()[filter.state].second) - is DurationFilter -> url.addQueryParameter("duration", getDurationPairs()[filter.state].second) - is AttributesGroupFilter -> filter.state.forEach { - if (it.state) url.addQueryParameter("attributes", it.value) - } - is LookupFilter -> { - filter.state.split(",").map { it.trim() }.filter { it.isNotBlank() }.map { - lookupFilterId(it, filter.uri) ?: throw Exception("No ${filter.singularName} \"$it\" was found") - }.forEach { - if (!(filter.uri == "languages" && it == hhLangId)) - url.addQueryParameter(filter.uri, it.toString()) - } - } - else -> {} - } - } - - return GET(url.toString()) - } - - // Details - - private fun tagArrayToString(array: JsonArray, key: String = "name"): String? { - if (array.size() == 0) - return null - return array.joinToString { it[key].asString } - } - - private fun mangaDetailsApiRequest(manga: SManga): Request { - val slug = manga.url.removePrefix("/en/comic/") - return GET("$baseUrl/api/comics/$slug") - } - - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(mangaDetailsApiRequest(manga)) - .asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - } - - override fun mangaDetailsParse(response: Response): SManga { - val data = gson.fromJson(response.body()!!.string()) - return SManga.create().apply { - - artist = tagArrayToString(data.getAsJsonArray("artists")) - author = tagArrayToString(data.getAsJsonArray("authors")) ?: artist - - genre = listOf("tags", "relationships").map { - data.getAsJsonArray(it).map { t -> t["name"].asString } - }.flatten().distinct().joinToString() - - status = SManga.COMPLETED - - description = listOf( - Pair("Alternative Title", data["alternative_title"].nullString), - Pair("Groups", tagArrayToString(data.getAsJsonArray("groups"))), - Pair("Description", data["description"].nullString), - Pair("Pages", data["pages"].asInt.toString()), - Pair("Category", data["category"].nullObj?.get("name")?.asString), - Pair("Language", data["language"].nullObj?.get("name")?.asString), - Pair("Parodies", tagArrayToString(data.getAsJsonArray("parodies"))), - Pair("Characters", tagArrayToString(data.getAsJsonArray("characters"))) - ).filter { !it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}:\n${it.second}" } - } - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga) - - override fun chapterListParse(response: Response): List { - val data = gson.fromJson(response.body()!!.string()) - return listOf( - SChapter.create().apply { - url = "/en/comic/${data["slug"].asString}/reader/1" - name = "Chapter" - date_upload = DATE_FORMAT.parse(data["uploaded_at"].asString)?.time ?: 0 - chapter_number = 1f - } - ) - } - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - val slug = chapter.url.removePrefix("/en/comic/").removeSuffix("/reader/1") - return GET("$baseUrl/api/comics/$slug/images") - } - - override fun pageListParse(response: Response): List { - val data = gson.fromJson(response.body()!!.string()) - return data.getAsJsonArray("images").mapIndexed { i, it -> - Page(i, "/en/comic/${data["comic"]["slug"].asString}/reader/${it["page"].asInt}", it["source_url"].asString) - } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Authorization - - private fun authIntercept(chain: Interceptor.Chain): Response { - val request = chain.request() - if (username.isEmpty() or password.isEmpty()) { - return chain.proceed(request) - } - - if (token.isEmpty()) { - token = this.login(chain, username, password) - } - val authRequest = request.newBuilder() - .addHeader("Authorization", "Bearer $token") - .build() - return chain.proceed(authRequest) - } - - private fun login(chain: Interceptor.Chain, username: String, password: String): String { - val jsonObject = JSONObject().apply { - this.put("username", username) - this.put("password", password) - this.put("remember_me", true) - } - val body = RequestBody.create(MEDIA_TYPE, jsonObject.toString()) - val response = chain.proceed(POST("$baseUrl/api/login", headers, body)) - if (response.code() == 401) { - throw Exception("Failed to login, check if username and password are correct") - } - - if (response.body() == null) - throw Exception("Login response body is empty") - try { - return JSONObject(response.body()!!.string()) - .getJSONObject("auth") - .getString("access_token") - } catch (e: JSONException) { - throw Exception("Cannot parse login response body") - } - } - - private var token: String = "" - private val username by lazy { getPrefUsername() } - private val password by lazy { getPrefPassword() } - - // Preferences - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true)) - } - - private fun androidx.preference.PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference { - return androidx.preference.EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - - if (isPassword) { - setOnBindEditTextListener { - it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - } - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - screen.addPreference(screen.supportEditTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.supportEditTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password)) - } - - private fun PreferenceScreen.supportEditTextPreference(title: String, default: String, value: String): EditTextPreference { - return EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - private fun getPrefUsername(): String = preferences.getString(USERNAME_TITLE, USERNAME_DEFAULT)!! - private fun getPrefPassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!! - - // Filters - - private class SortFilter(sortPairs: List>) : Filter.Select("Sort By", sortPairs.map { it.first }.toTypedArray()) - private class OrderFilter(orderPairs: List>) : Filter.Select("Order By", orderPairs.map { it.first }.toTypedArray()) - private class DurationFilter(durationPairs: List>) : Filter.Select("Duration", durationPairs.map { it.first }.toTypedArray()) - private class AttributeFilter(name: String, val value: String) : Filter.CheckBox(name) - private class AttributesGroupFilter(attributePairs: List>) : Filter.Group("Attributes", attributePairs.map { AttributeFilter(it.first, it.second) }) - - private class CategoriesFilter : LookupFilter("Categories", "categories", "category") - private class TagsFilter : LookupFilter("Tags", "tags", "tag") - private class ArtistsFilter : LookupFilter("Artists", "artists", "artist") - private class GroupsFilter : LookupFilter("Groups", "groups", "group") - private class CharactersFilter : LookupFilter("Characters", "characters", "character") - private class ParodiesFilter : LookupFilter("Parodies", "parodies", "parody") - private class LanguagesFilter : LookupFilter("Other Languages", "languages", "language") - open class LookupFilter(name: String, val uri: String, val singularName: String) : Filter.Text(name) - - override fun getFilterList() = FilterList( - SortFilter(getSortPairs()), - OrderFilter(getOrderPairs()), - DurationFilter(getDurationPairs()), - Filter.Header("Separate terms with commas (,)"), - CategoriesFilter(), - TagsFilter(), - ArtistsFilter(), - GroupsFilter(), - CharactersFilter(), - ParodiesFilter(), - LanguagesFilter(), - AttributesGroupFilter(getAttributePairs()) - ) - - private fun getSortPairs() = listOf( - Pair("Upload Date", "uploaded_at"), - Pair("Title", "title"), - Pair("Pages", "pages"), - Pair("Favorites", "favorites"), - Pair("Popularity", "popularity") - ) - - private fun getOrderPairs() = listOf( - Pair("Descending", "desc"), - Pair("Ascending", "asc") - ) - - private fun getDurationPairs() = listOf( - Pair("Today", "day"), - Pair("This Week", "week"), - Pair("This Month", "month"), - Pair("This Year", "year"), - Pair("All Time", "all") - ) - - private fun getAttributePairs() = listOf( - Pair("Translated", "translated"), - Pair("Speechless", "speechless"), - Pair("Rewritten", "rewritten") - ) - - companion object { - @SuppressLint("SimpleDateFormat") - private val DATE_FORMAT = SimpleDateFormat("yyyy-dd-MM") - private val MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8") - private const val USERNAME_TITLE = "Username" - private const val USERNAME_DEFAULT = "" - private const val PASSWORD_TITLE = "Password" - private const val PASSWORD_DEFAULT = "" - } -} diff --git a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHandFactory.kt b/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHandFactory.kt deleted file mode 100644 index cc8309969..000000000 --- a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHandFactory.kt +++ /dev/null @@ -1,51 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hentaihand - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class HentaiHandFactory : SourceFactory { - - override fun createSources(): List = listOf( - // https://hentaihand.com/api/languages?per_page=50 - HentaiHand("other", extraName = " (Unfiltered)"), - HentaiHand("en", 1), - HentaiHand("zh", 2), - HentaiHand("ja", 3), - HentaiHand("other", 4, extraName = " (Text Cleaned)"), - HentaiHand("eo", 5), - HentaiHand("ceb", 6), - HentaiHand("cs", 7), - HentaiHand("ar", 8), - HentaiHand("sk", 9), - HentaiHand("mn", 10), - HentaiHand("uk", 11), - HentaiHand("la", 12), - HentaiHand("tl", 13), - HentaiHand("es", 14), - HentaiHand("it", 15), - HentaiHand("ko", 16), - HentaiHand("th", 17), - HentaiHand("pl", 18), - HentaiHand("fr", 19), - HentaiHand("pt", 20), - HentaiHand("de", 21), - HentaiHand("fi", 22), - HentaiHand("ru", 23), - HentaiHand("sv", 24), - HentaiHand("hu", 25), - HentaiHand("id", 26), - HentaiHand("vi", 27), - HentaiHand("da", 28), - HentaiHand("ro", 29), - HentaiHand("et", 30), - HentaiHand("nl", 31), - HentaiHand("ca", 32), - HentaiHand("tr", 33), - HentaiHand("el", 34), - HentaiHand("no", 35), - HentaiHand("sq", 1501), - HentaiHand("bg", 1502), - ) -} diff --git a/src/all/hitomi/AndroidManifest.xml b/src/all/hitomi/AndroidManifest.xml deleted file mode 100644 index a7c4ada40..000000000 --- a/src/all/hitomi/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/all/hitomi/build.gradle b/src/all/hitomi/build.gradle deleted file mode 100644 index 108ea0e09..000000000 --- a/src/all/hitomi/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Hitomi.la' - pkgNameSuffix = 'all.hitomi' - extClass = '.HitomiFactory' - extVersionCode = 5 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/hitomi/res/mipmap-hdpi/ic_launcher.png b/src/all/hitomi/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a534633b8..000000000 Binary files a/src/all/hitomi/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hitomi/res/mipmap-mdpi/ic_launcher.png b/src/all/hitomi/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5c6f3b8f8..000000000 Binary files a/src/all/hitomi/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hitomi/res/mipmap-xhdpi/ic_launcher.png b/src/all/hitomi/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f2477f79a..000000000 Binary files a/src/all/hitomi/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hitomi/res/mipmap-xxhdpi/ic_launcher.png b/src/all/hitomi/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 48549d3f4..000000000 Binary files a/src/all/hitomi/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hitomi/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/hitomi/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 97f74cc71..000000000 Binary files a/src/all/hitomi/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/hitomi/res/web_hi_res_512.png b/src/all/hitomi/res/web_hi_res_512.png deleted file mode 100644 index a851e8798..000000000 Binary files a/src/all/hitomi/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/ByteCursor.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/ByteCursor.kt deleted file mode 100644 index 1c6b03061..000000000 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/ByteCursor.kt +++ /dev/null @@ -1,93 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hitomi - -import java.nio.ByteBuffer - -/** - * Simple cursor for use on byte arrays - * @author nulldev - */ -class ByteCursor(val content: ByteArray) { - var index = -1 - private set - private var mark = -1 - - fun mark() { - mark = index - } - - fun jumpToMark() { - index = mark - } - - fun jumpToIndex(index: Int) { - this.index = index - } - - fun next(): Byte { - return content[++index] - } - - fun next(count: Int): ByteArray { - val res = content.sliceArray(index + 1..index + count) - skip(count) - return res - } - - // Used to perform conversions - private fun byteBuffer(count: Int): ByteBuffer { - return ByteBuffer.wrap(next(count)) - } - - // Epic hack to get an unsigned short properly... - fun fakeNextShortInt(): Int = ByteBuffer - .wrap(arrayOf(0x00, 0x00, *next(2).toTypedArray()).toByteArray()) - .getInt(0) - - // fun nextShort(): Short = byteBuffer(2).getShort(0) - fun nextInt(): Int = byteBuffer(4).getInt(0) - fun nextLong(): Long = byteBuffer(8).getLong(0) - fun nextFloat(): Float = byteBuffer(4).getFloat(0) - fun nextDouble(): Double = byteBuffer(8).getDouble(0) - - fun skip(count: Int) { - index += count - } - - fun expect(vararg bytes: Byte) { - if (bytes.size > remaining()) { - throw IllegalStateException("Unexpected end of content!") - } - - for (i in 0..bytes.lastIndex) { - val expected = bytes[i] - val actual = content[index + i + 1] - - if (expected != actual) { - throw IllegalStateException("Unexpected content (expected: $expected, actual: $actual)!") - } - } - - index += bytes.size - } - - fun checkEqual(vararg bytes: Byte): Boolean { - if (bytes.size > remaining()) { - return false - } - - for (i in 0..bytes.lastIndex) { - val expected = bytes[i] - val actual = content[index + i + 1] - - if (expected != actual) { - return false - } - } - - return true - } - - fun atEnd() = index >= content.size - 1 - - fun remaining() = content.size - index - 1 -} diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt deleted file mode 100644 index e579ebf2c..000000000 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/Hitomi.kt +++ /dev/null @@ -1,406 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hitomi - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.PreferenceScreen -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import rx.Observable -import rx.Single -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.Locale -import androidx.preference.CheckBoxPreference as AndroidXCheckBoxPreference -import androidx.preference.PreferenceScreen as AndroidXPreferenceScreen - -/** - * Ported from TachiyomiSy - * Original work by NerdNumber9 for TachiyomiEH - */ - -open class Hitomi(override val lang: String, private val nozomiLang: String) : HttpSource(), ConfigurableSource { - - override val supportsLatest = true - - override val name = if (nozomiLang == "all") "Hitomi.la unfiltered" else "Hitomi.la" - - override val baseUrl = BASE_URL - - // Popular - - override fun fetchPopularManga(page: Int): Observable { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .flatMap { responseToMangas(it) } - } - - override fun popularMangaRequest(page: Int) = HitomiNozomi.rangedGet( - "$LTN_BASE_URL/popular-$nozomiLang.nozomi", - 100L * (page - 1), - 99L + 100 * (page - 1) - ) - - private fun responseToMangas(response: Response): Observable { - val range = response.header("Content-Range")!! - val total = range.substringAfter('/').toLong() - val end = range.substringBefore('/').substringAfter('-').toLong() - val body = response.body()!! - return parseNozomiPage(body.bytes()) - .map { - MangasPage(it, end < total - 1) - } - } - - private fun parseNozomiPage(array: ByteArray): Observable> { - val cursor = ByteCursor(array) - val ids = (1..array.size / 4).map { - cursor.nextInt() - } - - return nozomiIdsToMangas(ids).toObservable() - } - - private fun nozomiIdsToMangas(ids: List): Single> { - return Single.zip( - ids.map { int -> - client.newCall(GET("$LTN_BASE_URL/galleryblock/$int.html")) - .asObservableSuccess() - .subscribeOn(Schedulers.io()) // Perform all these requests in parallel - .map { parseGalleryBlock(it) } - .toSingle() - } - ) { it.map { m -> m as SManga } } - } - - private fun Document.selectFirst(selector: String) = this.select(selector).first() - - private fun parseGalleryBlock(response: Response): SManga { - val doc = response.asJsoup() - return SManga.create().apply { - val titleElement = doc.selectFirst("h1") - title = titleElement.text() - thumbnail_url = "https:" + if (useHqThumbPref()) { - doc.selectFirst("img").attr("srcset").substringBefore(' ') - } else { - doc.selectFirst("img").attr("src") - } - url = titleElement.child(0).attr("href") - } - } - - override fun popularMangaParse(response: Response) = throw UnsupportedOperationException("Not used") - - // Latest - - override fun fetchLatestUpdates(page: Int): Observable { - return client.newCall(latestUpdatesRequest(page)) - .asObservableSuccess() - .flatMap { responseToMangas(it) } - } - - override fun latestUpdatesRequest(page: Int) = HitomiNozomi.rangedGet( - "$LTN_BASE_URL/index-$nozomiLang.nozomi", - 100L * (page - 1), - 99L + 100 * (page - 1) - ) - - override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not used") - - // Search - - private var cachedTagIndexVersion: Long? = null - private var tagIndexVersionCacheTime: Long = 0 - private fun tagIndexVersion(): Single { - val sCachedTagIndexVersion = cachedTagIndexVersion - return if (sCachedTagIndexVersion == null || - tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis() - ) { - HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext { - cachedTagIndexVersion = it - tagIndexVersionCacheTime = System.currentTimeMillis() - }.toSingle() - } else { - Single.just(sCachedTagIndexVersion) - } - } - - private var cachedGalleryIndexVersion: Long? = null - private var galleryIndexVersionCacheTime: Long = 0 - private fun galleryIndexVersion(): Single { - val sCachedGalleryIndexVersion = cachedGalleryIndexVersion - return if (sCachedGalleryIndexVersion == null || - galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis() - ) { - HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext { - cachedGalleryIndexVersion = it - galleryIndexVersionCacheTime = System.currentTimeMillis() - }.toSingle() - } else { - Single.just(sCachedGalleryIndexVersion) - } - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(GET("$baseUrl/cg/$id", headers)).asObservableSuccess() - .map { MangasPage(listOf(mangaDetailsParse(it).apply { url = "/cg/$id" }), false) } - } else { - val splitQuery = query.toLowerCase(Locale.ENGLISH).split(" ") - - val positive = splitQuery.filter { !it.startsWith('-') }.toMutableList() - if (nozomiLang != "all") positive += "language:$nozomiLang" - val negative = (splitQuery - positive).map { it.removePrefix("-") } - - // TODO Cache the results coming out of HitomiNozomi (this TODO dates back to TachiyomiEH) - val hn = Single.zip(tagIndexVersion(), galleryIndexVersion()) { tv, gv -> tv to gv } - .map { HitomiNozomi(client, it.first, it.second) } - - var base = if (positive.isEmpty()) { - hn.flatMap { n -> n.getGalleryIdsFromNozomi(null, "index", "all").map { n to it.toSet() } } - } else { - val q = positive.removeAt(0) - hn.flatMap { n -> n.getGalleryIdsForQuery(q).map { n to it.toSet() } } - } - - base = positive.fold(base) { acc, q -> - acc.flatMap { (nozomi, mangas) -> - nozomi.getGalleryIdsForQuery(q).map { - nozomi to mangas.intersect(it) - } - } - } - - base = negative.fold(base) { acc, q -> - acc.flatMap { (nozomi, mangas) -> - nozomi.getGalleryIdsForQuery(q).map { - nozomi to (mangas - it) - } - } - } - - base.flatMap { (_, ids) -> - val chunks = ids.chunked(PAGE_SIZE) - - nozomiIdsToMangas(chunks[page - 1]).map { mangas -> - MangasPage(mangas, page < chunks.size) - } - }.toObservable() - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not used") - override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - fun String.replaceSpaces() = this.replace(" ", "_") - - return SManga.create().apply { - thumbnail_url = document.select("div.cover img").attr("abs:src") - author = document.select("div.gallery h2 a").joinToString { it.text() } - val tableInfo = document.select("table tr") - .map { tr -> - val key = tr.select("td:first-child").text() - val value = with(tr.select("td:last-child a")) { - when (key) { - "Series", "Characters" -> { - if (text().isNotEmpty()) - joinToString { "${attr("href").removePrefix("/").substringBefore("/")}:${it.text().replaceSpaces()}" } else null - } - "Tags" -> joinToString { element -> - element.text().let { - when { - it.contains("♀") -> "female:${it.substringBeforeLast(" ").replaceSpaces()}" - it.contains("♂") -> "male:${it.substringBeforeLast(" ").replaceSpaces()}" - else -> it - } - } - } - else -> joinToString { it.text() } - } - } - Pair(key, value) - } - .plus(Pair("Date uploaded", document.select("div.gallery span.date").text())) - .toMap() - description = tableInfo.filterNot { it.value.isNullOrEmpty() || it.key in listOf("Series", "Characters", "Tags") }.entries.joinToString("\n") { "${it.key}: ${it.value}" } - genre = listOfNotNull(tableInfo["Series"], tableInfo["Characters"], tableInfo["Tags"]).joinToString() - } - } - - // Chapters - - override fun fetchChapterList(manga: SManga): Observable> { - return Observable.just( - listOf( - SChapter.create().apply { - url = manga.url - name = "Chapter" - chapter_number = 0.0f - } - ) - ) - } - - override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Not used") - - // Pages - - private fun hlIdFromUrl(url: String) = - url.split('/').last().split('-').last().substringBeforeLast('.') - - override fun pageListRequest(chapter: SChapter): Request { - return GET("$LTN_BASE_URL/galleries/${hlIdFromUrl(chapter.url)}.js") - } - - private val jsonParser = JsonParser() - - override fun pageListParse(response: Response): List { - val str = response.body()!!.string() - val json = jsonParser.parse(str.removePrefix("var galleryinfo = ")) - return json["files"].array.mapIndexed { i, jsonElement -> - val hash = jsonElement["hash"].string - val ext = if (jsonElement["haswebp"].string == "0" || !hitomiAlwaysWebp()) jsonElement["name"].string.split('.').last() else "webp" - val path = if (jsonElement["haswebp"].string == "0" || !hitomiAlwaysWebp()) "images" else "webp" - val hashPath1 = hash.takeLast(1) - val hashPath2 = hash.takeLast(3).take(2) - - // https://ltn.hitomi.la/reader.js - // function make_image_element() - val secondSubdomain = if (jsonElement["haswebp"].string == "0" && jsonElement["hasavif"].string == "0") "b" else "a" - - Page(i, "", "https://${firstSubdomainFromGalleryId(hashPath2)}$secondSubdomain.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext") - } - } - - // https://ltn.hitomi.la/common.js - private fun firstSubdomainFromGalleryId(pathSegment: String): Char { - var numberOfFrontends = 3 - var g = pathSegment.toInt(16) - if (g < 0x30) numberOfFrontends = 2 - if (g < 0x09) g = 1 - - return (97 + g.rem(numberOfFrontends)).toChar() - } - - override fun imageRequest(page: Page): Request { - val request = super.imageRequest(page) - val hlId = request.url().pathSegments().let { - it[it.lastIndex - 1] - } - return request.newBuilder() - .header("Referer", "$BASE_URL/reader/$hlId.html") - .build() - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used") - - companion object { - private const val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10 - private const val PAGE_SIZE = 25 - - const val PREFIX_ID_SEARCH = "id:" - - // From HitomiSearchMetaData - const val LTN_BASE_URL = "https://ltn.hitomi.la" - const val BASE_URL = "https://hitomi.la" - - // Preferences - private const val WEBP_PREF_KEY = "HITOMI_WEBP" - private const val WEBP_PREF_TITLE = "Webp pages" - private const val WEBP_PREF_SUMMARY = "Download webp pages instead of jpeg (when available)" - private const val WEBP_PREF_DEFAULT_VALUE = true - - private const val COVER_PREF_KEY = "HITOMI_COVERS" - private const val COVER_PREF_TITLE = "Use HQ covers" - private const val COVER_PREF_SUMMARY = "See HQ covers while browsing" - private const val COVER_PREF_DEFAULT_VALUE = true - } - - // Preferences - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val webpPref = CheckBoxPreference(screen.context).apply { - key = "${WEBP_PREF_KEY}_$lang" - title = WEBP_PREF_TITLE - summary = WEBP_PREF_SUMMARY - setDefaultValue(WEBP_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${WEBP_PREF_KEY}_$lang", checkValue).commit() - } - } - - val coverPref = CheckBoxPreference(screen.context).apply { - key = "${COVER_PREF_KEY}_$lang" - title = COVER_PREF_TITLE - summary = COVER_PREF_SUMMARY - setDefaultValue(COVER_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${COVER_PREF_KEY}_$lang", checkValue).commit() - } - } - - screen.addPreference(webpPref) - screen.addPreference(coverPref) - } - - override fun setupPreferenceScreen(screen: AndroidXPreferenceScreen) { - val webpPref = AndroidXCheckBoxPreference(screen.context).apply { - key = "${WEBP_PREF_KEY}_$lang" - title = WEBP_PREF_TITLE - summary = WEBP_PREF_SUMMARY - setDefaultValue(WEBP_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${WEBP_PREF_KEY}_$lang", checkValue).commit() - } - } - - val coverPref = AndroidXCheckBoxPreference(screen.context).apply { - key = "${COVER_PREF_KEY}_$lang" - title = COVER_PREF_TITLE - summary = COVER_PREF_SUMMARY - setDefaultValue(COVER_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${COVER_PREF_KEY}_$lang", checkValue).commit() - } - } - - screen.addPreference(webpPref) - screen.addPreference(coverPref) - } - - private fun hitomiAlwaysWebp(): Boolean = preferences.getBoolean("${WEBP_PREF_KEY}_$lang", WEBP_PREF_DEFAULT_VALUE) - private fun useHqThumbPref(): Boolean = preferences.getBoolean("${COVER_PREF_KEY}_$lang", COVER_PREF_DEFAULT_VALUE) -} diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiActivity.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiActivity.kt deleted file mode 100644 index 324595d34..000000000 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hitomi - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://hitomi.la/cg/xxxx intents - * and redirects them to the main Tachiyomi process. - */ -class HitomiActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${Hitomi.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("HitomiActivity", e.toString()) - } - } else { - Log.e("HitomiActivity", "Could not parse URI from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiFactory.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiFactory.kt deleted file mode 100644 index bdb47cd42..000000000 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiFactory.kt +++ /dev/null @@ -1,52 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hitomi - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class HitomiFactory : SourceFactory { - override fun createSources(): List = languageList - .map { Hitomi(it.first, it.second) } -} - -private val languageList = listOf( - Pair("other", "all"), // all languages - Pair("id", "indonesian"), - Pair("ca", "catalan"), - Pair("ceb", "cebuano"), - Pair("cs", "czech"), - Pair("da", "danish"), - Pair("de", "german"), - Pair("et", "estonian"), - Pair("en", "english"), - Pair("es", "spanish"), - Pair("eo", "esperanto"), - Pair("fr", "french"), - Pair("it", "italian"), - Pair("la", "latin"), - Pair("hu", "hungarian"), - Pair("nl", "dutch"), - Pair("no", "norwegian"), - Pair("pl", "polish"), - Pair("pt-BR", "portuguese"), - Pair("ro", "romanian"), - Pair("sq", "albanian"), - Pair("sk", "slovak"), - Pair("fi", "finnish"), - Pair("sv", "swedish"), - Pair("tl", "tagalog"), - Pair("vi", "vietnamese"), - Pair("tr", "turkish"), - Pair("el", "greek"), - Pair("mn", "mongolian"), - Pair("ru", "russian"), - Pair("uk", "ukrainian"), - Pair("he", "hebrew"), - Pair("ar", "arabic"), - Pair("fa", "persian"), - Pair("th", "thai"), - Pair("ko", "korean"), - Pair("zh", "chinese"), - Pair("ja", "japanese") -) diff --git a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiNozomi.kt b/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiNozomi.kt deleted file mode 100644 index 43a397e40..000000000 --- a/src/all/hitomi/src/eu/kanade/tachiyomi/extension/all/hitomi/HitomiNozomi.kt +++ /dev/null @@ -1,257 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hitomi - -import eu.kanade.tachiyomi.extension.all.hitomi.Hitomi.Companion.LTN_BASE_URL -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.network.asObservableSuccess -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import rx.Observable -import rx.Single -import java.security.MessageDigest - -private typealias HashedTerm = ByteArray - -private data class DataPair(val offset: Long, val length: Int) -private data class Node( - val keys: List, - val datas: List, - val subnodeAddresses: List -) - -/** - * Kotlin port of the hitomi.la search algorithm - * @author NerdNumber9 - */ -class HitomiNozomi( - private val client: OkHttpClient, - private val tagIndexVersion: Long, - private val galleriesIndexVersion: Long -) { - fun getGalleryIdsForQuery(query: String): Single> { - val replacedQuery = query.replace('_', ' ') - - if (':' in replacedQuery) { - val sides = replacedQuery.split(':') - val namespace = sides[0] - var tag = sides[1] - - var area: String? = namespace - var language = "all" - if (namespace == "female" || namespace == "male") { - area = "tag" - tag = replacedQuery - } else if (namespace == "language") { - area = null - language = tag - tag = "index" - } - - return getGalleryIdsFromNozomi(area, tag, language) - } - - val key = hashTerm(query) - val field = "galleries" - - return getNodeAtAddress(field, 0).flatMap { node -> - if (node == null) { - Single.just(null) - } else { - BSearch(field, key, node).flatMap { data -> - if (data == null) { - Single.just(null) - } else { - getGalleryIdsFromData(data) - } - } - } - } - } - - private fun getGalleryIdsFromData(data: DataPair?): Single> { - if (data == null) { - return Single.just(emptyList()) - } - - val url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.data" - val (offset, length) = data - if (length > 100000000 || length <= 0) { - return Single.just(emptyList()) - } - - return client.newCall(rangedGet(url, offset, offset + length - 1)) - .asObservable() - .map { - it.body()?.bytes() ?: ByteArray(0) - } - .onErrorReturn { ByteArray(0) } - .map { inbuf -> - if (inbuf.isEmpty()) { - return@map emptyList() - } - - val view = ByteCursor(inbuf) - val numberOfGalleryIds = view.nextInt() - - val expectedLength = numberOfGalleryIds * 4 + 4 - - if (numberOfGalleryIds > 10000000 || - numberOfGalleryIds <= 0 || - inbuf.size != expectedLength - ) { - return@map emptyList() - } - - (1..numberOfGalleryIds).map { - view.nextInt() - } - }.toSingle() - } - - @Suppress("FunctionName") - private fun BSearch(field: String, key: ByteArray, node: Node?): Single { - fun compareByteArrays(dv1: ByteArray, dv2: ByteArray): Int { - val top = dv1.size.coerceAtMost(dv2.size) - for (i in 0 until top) { - val dv1i = dv1[i].toInt() and 0xFF - val dv2i = dv2[i].toInt() and 0xFF - if (dv1i < dv2i) { - return -1 - } else if (dv1i > dv2i) { - return 1 - } - } - return 0 - } - - fun locateKey(key: ByteArray, node: Node): Pair { - var cmpResult = -1 - var lastI = 0 - for (nodeKey in node.keys) { - cmpResult = compareByteArrays(key, nodeKey) - if (cmpResult <= 0) break - lastI++ - } - return (cmpResult == 0) to lastI - } - - fun isLeaf(node: Node): Boolean { - return !node.subnodeAddresses.any { - it != 0L - } - } - - if (node == null || node.keys.isEmpty()) { - return Single.just(null) - } - - val (there, where) = locateKey(key, node) - if (there) { - return Single.just(node.datas[where]) - } else if (isLeaf(node)) { - return Single.just(null) - } - - return getNodeAtAddress(field, node.subnodeAddresses[where]).flatMap { newNode -> - BSearch(field, key, newNode) - } - } - - private fun decodeNode(data: ByteArray): Node { - val view = ByteCursor(data) - - val numberOfKeys = view.nextInt() - - val keys = (1..numberOfKeys).map { - val keySize = view.nextInt() - view.next(keySize) - } - - val numberOfDatas = view.nextInt() - val datas = (1..numberOfDatas).map { - val offset = view.nextLong() - val length = view.nextInt() - DataPair(offset, length) - } - - val numberOfSubnodeAddresses = B + 1 - val subnodeAddresses = (1..numberOfSubnodeAddresses).map { - view.nextLong() - } - - return Node(keys, datas, subnodeAddresses) - } - - private fun getNodeAtAddress(field: String, address: Long): Single { - var url = "$LTN_BASE_URL/$INDEX_DIR/$field.$tagIndexVersion.index" - if (field == "galleries") { - url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.index" - } - - return client.newCall(rangedGet(url, address, address + MAX_NODE_SIZE - 1)) - .asObservableSuccess() - .map { - it.body()?.bytes() ?: ByteArray(0) - } - .onErrorReturn { ByteArray(0) } - .map { nodedata -> - if (nodedata.isNotEmpty()) { - decodeNode(nodedata) - } else null - }.toSingle() - } - - fun getGalleryIdsFromNozomi(area: String?, tag: String, language: String): Single> { - var nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$tag-$language$NOZOMI_EXTENSION" - if (area != null) { - nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$area/$tag-$language$NOZOMI_EXTENSION" - } - - return client.newCall( - Request.Builder() - .url(nozomiAddress) - .build() - ) - .asObservableSuccess() - .map { resp -> - val body = resp.body()!!.bytes() - val cursor = ByteCursor(body) - (1..body.size / 4).map { - cursor.nextInt() - } - }.toSingle() - } - - private fun hashTerm(query: String): HashedTerm { - val md = MessageDigest.getInstance("SHA-256") - md.update(query.toByteArray(HASH_CHARSET)) - return md.digest().copyOf(4) - } - - companion object { - private const val INDEX_DIR = "tagindex" - private const val GALLERIES_INDEX_DIR = "galleriesindex" - private const val COMPRESSED_NOZOMI_PREFIX = "n" - private const val NOZOMI_EXTENSION = ".nozomi" - private const val MAX_NODE_SIZE = 464 - private const val B = 16 - - private val HASH_CHARSET = Charsets.UTF_8 - - fun rangedGet(url: String, rangeBegin: Long, rangeEnd: Long?): Request { - return GET( - url, - Headers.Builder() - .add("Range", "bytes=$rangeBegin-${rangeEnd ?: ""}") - .build() - ) - } - - fun getIndexVersion(httpClient: OkHttpClient, name: String): Observable { - return httpClient.newCall(GET("$LTN_BASE_URL/$name/version?_=${System.currentTimeMillis()}")) - .asObservableSuccess() - .map { it.body()!!.string().toLong() } - } - } -} diff --git a/src/all/imhentai/AndroidManifest.xml b/src/all/imhentai/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/imhentai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/imhentai/build.gradle b/src/all/imhentai/build.gradle deleted file mode 100644 index 55e554b3e..000000000 --- a/src/all/imhentai/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'IMHentai' - pkgNameSuffix = 'all.imhentai' - extClass = '.IMHentaiFactory' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/imhentai/res/mipmap-hdpi/ic_launcher.png b/src/all/imhentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a29fb9208..000000000 Binary files a/src/all/imhentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/imhentai/res/mipmap-mdpi/ic_launcher.png b/src/all/imhentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2d3866bd1..000000000 Binary files a/src/all/imhentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/imhentai/res/mipmap-xhdpi/ic_launcher.png b/src/all/imhentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 57eac862e..000000000 Binary files a/src/all/imhentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/imhentai/res/mipmap-xxhdpi/ic_launcher.png b/src/all/imhentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 66b298b74..000000000 Binary files a/src/all/imhentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/imhentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/imhentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6d4d71984..000000000 Binary files a/src/all/imhentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/imhentai/res/web_hi_res_512.png b/src/all/imhentai/res/web_hi_res_512.png deleted file mode 100644 index cfe8b5960..000000000 Binary files a/src/all/imhentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt deleted file mode 100644 index 695d9a286..000000000 --- a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt +++ /dev/null @@ -1,267 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.imhentai - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import rx.Observable - -class IMHentai(override val lang: String, private val imhLang: String) : ParsedHttpSource() { - - private val pageLoadHeaders: Headers = Headers.Builder().apply { - add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - add("X-Requested-With", "XMLHttpRequest") - }.build() - - override val baseUrl: String = "https://imhentai.xxx" - override val name: String = "IMHentai" - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - thumbnail_url = element.select(".inner_thumb img").attr("src") - with(element.select(".caption a")) { - url = this.attr("href") - title = this.text() - } - } - } - - override fun popularMangaNextPageSelector(): String = ".pagination li a:contains(Next):not([tabindex])" - - override fun popularMangaSelector(): String = ".thumbs_container .thumb" - - override fun popularMangaRequest(page: Int): Request = searchMangaRequest(page, "", getFilterList(SORT_ORDER_POPULAR)) - - // Latest - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() - - override fun latestUpdatesRequest(page: Int): Request = searchMangaRequest(page, "", getFilterList(SORT_ORDER_LATEST)) - - override fun latestUpdatesSelector(): String = popularMangaSelector() - - // Search - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector() - - private fun toBinary(boolean: Boolean) = if (boolean) "1" else "0" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("key", query) - .addQueryParameter("page", page.toString()) - .addQueryParameter(getLanguageURIByName(imhLang).uri, toBinary(true)) // main language always enabled - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is LanguageFilters -> { - filter.state.forEach { - url.addQueryParameter(it.uri, toBinary(it.state)) - } - } - is CategoryFilters -> { - filter.state.forEach { - url.addQueryParameter(it.uri, toBinary(it.state)) - } - } - is SortOrderFilter -> { - getSortOrderURIs().forEachIndexed { index, pair -> - url.addQueryParameter(pair.second, toBinary(filter.state == index)) - } - } - else -> { } - } - } - - return GET(url.toString()) - } - - override fun searchMangaSelector(): String = popularMangaSelector() - - // Details - - private fun Elements.csvText(splitTagSeparator: String = ", "): String { - return this.joinToString { - listOf( - it.ownText(), - it.select(".split_tag")?.text() - ?.trim() - ?.removePrefix("| ") - ) - .filter { s -> !s.isNullOrBlank() } - .joinToString(splitTagSeparator) - } - } - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val mangaInfoElement = document.select(".galleries_info") - val infoMap = mangaInfoElement.select("li:not(.pages)").map { - it.select("span.tags_text").text().removeSuffix(":") to it.select(".tag") - }.toMap() - - artist = infoMap["Artists"]?.csvText(" | ") - - author = artist - - genre = infoMap["Tags"]?.csvText() - - status = SManga.COMPLETED - - val pages = mangaInfoElement.select("li.pages").text().substringAfter("Pages: ") - val altTitle = document.select(".subtitle").text().ifBlank { null } - - description = listOf( - "Parodies", - "Characters", - "Groups", - "Languages", - "Category" - ).map { it to infoMap[it]?.csvText() } - .let { listOf(Pair("Alternate Title", altTitle)) + it + listOf(Pair("Pages", pages)) } - .filter { !it.second.isNullOrEmpty() } - .joinToString("\n\n") { "${it.first}:\n${it.second}" } - } - - // Chapters - - private fun pageLoadMetaParse(document: Document): String { - return document.select(".gallery_divider ~ input[type=\"hidden\"]").map { m -> - m.attr("id") to m.attr("value") - }.toMap().let { - listOf( - Pair("server", "load_server"), - Pair("u_id", "gallery_id"), - Pair("g_id", "load_id"), - Pair("img_dir", "load_dir"), - Pair("total_pages", "load_pages") - ).map { meta -> "${meta.first}=${it[meta.second]}" } - .let { payload -> payload + listOf("type=2", "visible_pages=0") } - .joinToString("&") - } - } - - override fun chapterListParse(response: Response): List { - return listOf( - SChapter.create().apply { - setUrlWithoutDomain(response.request().url().toString()) - name = "Chapter" - chapter_number = 1f - } - ) - } - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - override fun chapterListSelector(): String = throw UnsupportedOperationException("Not used") - - // Pages - - override fun fetchPageList(chapter: SChapter): Observable> { - return client.newCall(GET("$baseUrl${chapter.url}")) - .asObservableSuccess() - .map { pageLoadMetaParse(it.asJsoup()) } - .map { RequestBody.create(MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8"), it) } - .concatMap { client.newCall(POST(PAEG_LOAD_URL, pageLoadHeaders, it)).asObservableSuccess() } - .map { pageListParse(it) } - } - - override fun pageListParse(document: Document): List { - return document.select("a").mapIndexed { i, element -> - Page(i, element.attr("href"), element.select(".lazy.preloader[src]").attr("src").replace("t.", ".")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - private class SortOrderFilter(sortOrderURIs: List>, state: Int) : - Filter.Select("Sort By", sortOrderURIs.map { it.first }.toTypedArray(), state) - private open class SearchFlagFilter(name: String, val uri: String, state: Boolean = true) : Filter.CheckBox(name, state) - private class LanguageFilter(name: String, uri: String = name) : SearchFlagFilter(name, uri, false) - private class LanguageFilters(flags: List) : Filter.Group("Other Languages", flags) - private class CategoryFilters(flags: List) : Filter.Group("Categories", flags) - - override fun getFilterList() = getFilterList(SORT_ORDER_DEFAULT) - - private fun getFilterList(sortOrderState: Int) = FilterList( - SortOrderFilter(getSortOrderURIs(), sortOrderState), - CategoryFilters(getCategoryURIs()), - LanguageFilters(getLanguageURIs().filter { it.name != imhLang }) // exclude main lang - ) - - private fun getCategoryURIs() = listOf( - SearchFlagFilter("Manga", "manga"), - SearchFlagFilter("Doujinshi", "doujinshi"), - SearchFlagFilter("Western", "western"), - SearchFlagFilter("Image Set", "imageset"), - SearchFlagFilter("Artist CG", "artistcg"), - SearchFlagFilter("Game CG", "gamecg") - ) - - // update sort order indices in companion object if order is changed - private fun getSortOrderURIs() = listOf( - Pair("Popular", "pp"), - Pair("Latest", "lt"), - Pair("Downloads", "dl"), - Pair("Top Rated", "tr") - ) - - private fun getLanguageURIs() = listOf( - LanguageFilter(LANGUAGE_ENGLISH, "en"), - LanguageFilter(LANGUAGE_JAPANESE, "jp"), - LanguageFilter(LANGUAGE_SPANISH, "es"), - LanguageFilter(LANGUAGE_FRENCH, "fr"), - LanguageFilter(LANGUAGE_KOREAN, "kr"), - LanguageFilter(LANGUAGE_GERMAN, "de"), - LanguageFilter(LANGUAGE_RUSSIAN, "ru") - ) - - private fun getLanguageURIByName(name: String): LanguageFilter { - return getLanguageURIs().first { it.name == name } - } - - companion object { - - // references to sort order indices - private const val SORT_ORDER_POPULAR = 0 - private const val SORT_ORDER_LATEST = 1 - private const val SORT_ORDER_DEFAULT = SORT_ORDER_POPULAR - - // references to be used in factory - const val LANGUAGE_ENGLISH = "English" - const val LANGUAGE_JAPANESE = "Japanese" - const val LANGUAGE_SPANISH = "Spanish" - const val LANGUAGE_FRENCH = "French" - const val LANGUAGE_KOREAN = "Korean" - const val LANGUAGE_GERMAN = "German" - const val LANGUAGE_RUSSIAN = "Russian" - - private const val PAEG_LOAD_URL: String = "https://imhentai.xxx/inc/thumbs_loader.php" - } -} diff --git a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentaiFactory.kt b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentaiFactory.kt deleted file mode 100644 index 38541894d..000000000 --- a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentaiFactory.kt +++ /dev/null @@ -1,19 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.imhentai - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class IMHentaiFactory : SourceFactory { - - override fun createSources(): List = listOf( - IMHentai("en", IMHentai.LANGUAGE_ENGLISH), - IMHentai("ja", IMHentai.LANGUAGE_JAPANESE), - IMHentai("es", IMHentai.LANGUAGE_SPANISH), - IMHentai("fr", IMHentai.LANGUAGE_FRENCH), - IMHentai("ko", IMHentai.LANGUAGE_KOREAN), - IMHentai("de", IMHentai.LANGUAGE_GERMAN), - IMHentai("ru", IMHentai.LANGUAGE_RUSSIAN) - ) -} diff --git a/src/all/komga/AndroidManifest.xml b/src/all/komga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/komga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/komga/CHANGELOG.md b/src/all/komga/CHANGELOG.md deleted file mode 100644 index cebf3d0c6..000000000 --- a/src/all/komga/CHANGELOG.md +++ /dev/null @@ -1,175 +0,0 @@ -## 1.2.23 - -Minimum Komga version required: `0.75.0` - -### Features - -* ignore DNS over HTTPS so it can reach IP addresses - -## 1.2.22 - -Minimum Komga version required: `0.75.0` - -### Features - -* add error logs and better catch exceptions - -## 1.2.21 - -Minimum Komga version required: `0.75.0` - -### Features - -* browse read lists (from the filter menu) -* filter by collection, respecting the collection's ordering - -## 1.2.20 - -Minimum Komga version required: `0.75.0` - -### Features - -* filter by authors, grouped by role - -## 1.2.19 - -Minimum Komga version required: `0.68.0` - -### Features - -* display Series authors -* display Series summary from books if no summary exists for Series - -## 1.2.18 - -Minimum Komga version required: `0.63.2` - -### Fix - -* use metadata.releaseDate or fileLastModified for chapter date - -## 1.2.17 - -Minimum Komga version required: `0.63.2` - -### Fix - -* list of collections for filtering could be empty in some conditions - -## 1.2.16 - -Minimum Komga version required: `0.59.0` - -### Features - -* filter by genres, tags and publishers - -## 1.2.15 - -Minimum Komga version required: `0.56.0` - -### Features - -* remove the 1000 chapters limit -* display series description and tags (genres + tags) - -## 1.2.14 - -Minimum Komga version required: `0.41.0` - -### Features - -* change chapter display name to use the display number instead of the sort number - -## 1.2.13 - -Minimum Komga version required: `0.41.0` - -### Features - -* compatibility for the upcoming version of Komga which have changes in the API (IDs are String instead of Long) - -## 1.2.12 - -Minimum Komga version required: `0.41.0` - -### Features - -* filter by collection - -## 1.2.11 - -Minimum Komga version required: `0.35.2` - -### Features - -* Set password preferences inputTypes - -## 1.2.10 - -Minimum Komga version required: `0.35.2` - -### Features - -* unread only filter (closes gotson/komga#180) -* prefix book titles with number (closes gotson/komga#169) - -## 1.2.9 - -Minimum Komga version required: `0.22.0` - -### Features - -* use SourceFactory to have multiple Komga servers (3 for the moment) - -## 1.2.8 - -Minimum Komga version required: `0.22.0` - -### Features - -* use book metadata title for chapter display name -* use book metadata sort number for chapter number - -## 1.2.7 - -### Features - -* use series metadata title for display name -* filter on series status - -## 1.2.6 - -### Features - -* Add support for AndroidX preferences - -## 1.2.5 - -### Features - -* add sort options in filter - -## 1.2.4 - -### Features - -* better handling of authentication - -## 1.2.3 - -### Features - -* filters by library - -## 1.2.2 - -### Features - -* request converted image from server if format is not supported - -## 1.2.1 - -### Features - -* first version \ No newline at end of file diff --git a/src/all/komga/build.gradle b/src/all/komga/build.gradle deleted file mode 100644 index 26615b355..000000000 --- a/src/all/komga/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Komga' - pkgNameSuffix = 'all.komga' - extClass = '.KomgaFactory' - extVersionCode = 23 - libVersion = '1.2' -} - -dependencies { - implementation 'io.reactivex:rxandroid:1.2.1' - implementation 'io.reactivex:rxjava:1.3.8' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/komga/res/mipmap-hdpi/ic_launcher.png b/src/all/komga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 4a5514218..000000000 Binary files a/src/all/komga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/komga/res/mipmap-mdpi/ic_launcher.png b/src/all/komga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index af95fb6ca..000000000 Binary files a/src/all/komga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/komga/res/mipmap-xhdpi/ic_launcher.png b/src/all/komga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 76c5112ce..000000000 Binary files a/src/all/komga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/komga/res/mipmap-xxhdpi/ic_launcher.png b/src/all/komga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 80894b006..000000000 Binary files a/src/all/komga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/komga/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/komga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 798b30128..000000000 Binary files a/src/all/komga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/komga/res/web_hi_res_512.png b/src/all/komga/res/web_hi_res_512.png deleted file mode 100755 index d39f53c61..000000000 Binary files a/src/all/komga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt deleted file mode 100644 index e0e7ca622..000000000 --- a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt +++ /dev/null @@ -1,559 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.komga - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.text.InputType -import android.util.Log -import android.widget.Toast -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.extension.all.komga.dto.AuthorDto -import eu.kanade.tachiyomi.extension.all.komga.dto.BookDto -import eu.kanade.tachiyomi.extension.all.komga.dto.CollectionDto -import eu.kanade.tachiyomi.extension.all.komga.dto.LibraryDto -import eu.kanade.tachiyomi.extension.all.komga.dto.PageDto -import eu.kanade.tachiyomi.extension.all.komga.dto.PageWrapperDto -import eu.kanade.tachiyomi.extension.all.komga.dto.ReadListDto -import eu.kanade.tachiyomi.extension.all.komga.dto.SeriesDto -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.Credentials -import okhttp3.Dns -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Single -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -open class Komga(suffix: String = "") : ConfigurableSource, HttpSource() { - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/api/v1/series?page=${page - 1}", headers) - - override fun popularMangaParse(response: Response): MangasPage = - processSeriesPage(response) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/api/v1/series/latest?page=${page - 1}", headers) - - override fun latestUpdatesParse(response: Response): MangasPage = - processSeriesPage(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val collectionId = (filters.find { it is CollectionSelect } as? CollectionSelect)?.let { - it.values[it.state].id - } - - val type = when { - collectionId != null -> "collections/$collectionId/series" - filters.find { it is TypeSelect }?.state == 1 -> "readlists" - else -> "series" - } - - val url = HttpUrl.parse("$baseUrl/api/v1/$type?search=$query&page=${page - 1}")!!.newBuilder() - - filters.forEach { filter -> - when (filter) { - is UnreadOnly -> { - if (filter.state) { - url.addQueryParameter("read_status", "UNREAD") - } - } - is LibraryGroup -> { - val libraryToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.state) { - libraryToInclude.add(content.id) - } - } - if (libraryToInclude.isNotEmpty()) { - url.addQueryParameter("library_id", libraryToInclude.joinToString(",")) - } - } - is StatusGroup -> { - val statusToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.state) { - statusToInclude.add(content.name.toUpperCase(Locale.ROOT)) - } - } - if (statusToInclude.isNotEmpty()) { - url.addQueryParameter("status", statusToInclude.joinToString(",")) - } - } - is GenreGroup -> { - val genreToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.state) { - genreToInclude.add(content.name) - } - } - if (genreToInclude.isNotEmpty()) { - url.addQueryParameter("genre", genreToInclude.joinToString(",")) - } - } - is TagGroup -> { - val tagToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.state) { - tagToInclude.add(content.name) - } - } - if (tagToInclude.isNotEmpty()) { - url.addQueryParameter("tag", tagToInclude.joinToString(",")) - } - } - is PublisherGroup -> { - val publisherToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.state) { - publisherToInclude.add(content.name) - } - } - if (publisherToInclude.isNotEmpty()) { - url.addQueryParameter("publisher", publisherToInclude.joinToString(",")) - } - } - is AuthorGroup -> { - val authorToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.state) { - authorToInclude.add(content.author) - } - } - authorToInclude.forEach { - url.addQueryParameter("author", "${it.name},${it.role}") - } - } - is Filter.Sort -> { - var sortCriteria = when (filter.state?.index) { - 0 -> "metadata.titleSort" - 1 -> "createdDate" - 2 -> "lastModifiedDate" - else -> "" - } - if (sortCriteria.isNotEmpty()) { - sortCriteria += "," + if (filter.state?.ascending!!) "asc" else "desc" - url.addQueryParameter("sort", sortCriteria) - } - } - } - } - - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage = - processSeriesPage(response) - - override fun mangaDetailsRequest(manga: SManga): Request = - GET(baseUrl + manga.url, headers) - - override fun mangaDetailsParse(response: Response): SManga = - if (response.fromReadList()) { - val readList = gson.fromJson(response.body()?.charStream()!!) - readList.toSManga() - } else { - val series = gson.fromJson(response.body()?.charStream()!!) - series.toSManga() - } - - override fun chapterListRequest(manga: SManga): Request = - GET("$baseUrl${manga.url}/books?unpaged=true&media_status=READY", headers) - - override fun chapterListParse(response: Response): List { - val page = gson.fromJson>(response.body()?.charStream()!!).content - - val r = page.map { book -> - SChapter.create().apply { - chapter_number = book.metadata.numberSort - name = "${if (!response.fromReadList()) "${book.metadata.number} - " else ""}${book.metadata.title} (${book.size})" - url = "$baseUrl/api/v1/books/${book.id}" - date_upload = book.metadata.releaseDate?.let { parseDate(it) } - ?: parseDateTime(book.fileLastModified) - } - } - return if (!response.fromReadList()) r.sortedByDescending { it.chapter_number } else r.reversed() - } - - override fun pageListRequest(chapter: SChapter): Request = - GET("${chapter.url}/pages") - - override fun pageListParse(response: Response): List { - val pages = gson.fromJson>(response.body()?.charStream()!!) - return pages.map { - val url = "${response.request().url()}/${it.number}" + - if (!supportedImageTypes.contains(it.mediaType)) { - "?convert=png" - } else { - "" - } - Page( - index = it.number - 1, - imageUrl = url - ) - } - } - - private fun processSeriesPage(response: Response): MangasPage { - if (response.fromReadList()) { - with(gson.fromJson>(response.body()?.charStream()!!)) { - return MangasPage(content.map { it.toSManga() }, !last) - } - } else { - with(gson.fromJson>(response.body()?.charStream()!!)) { - return MangasPage(content.map { it.toSManga() }, !last) - } - } - } - - private fun SeriesDto.toSManga(): SManga = - SManga.create().apply { - title = metadata.title - url = "/api/v1/series/$id" - thumbnail_url = "$baseUrl/api/v1/series/$id/thumbnail" - status = when (metadata.status) { - "ONGOING" -> SManga.ONGOING - "ENDED" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - genre = (metadata.genres + metadata.tags).joinToString(", ") - description = metadata.summary.ifBlank { booksMetadata.summary } - booksMetadata.authors.groupBy { it.role }.let { map -> - author = map["writer"]?.map { it.name }?.distinct()?.joinToString() - artist = map["penciller"]?.map { it.name }?.distinct()?.joinToString() - } - } - - private fun ReadListDto.toSManga(): SManga = - SManga.create().apply { - title = name - url = "/api/v1/readlists/$id" - thumbnail_url = "$baseUrl/api/v1/readlists/$id/thumbnail" - status = SManga.UNKNOWN - } - - private fun Response.fromReadList() = request().url().toString().contains("/api/v1/readlists") - - private fun parseDate(date: String?): Long = - if (date == null) - Date().time - else { - try { - SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(date).time - } catch (ex: Exception) { - Date().time - } - } - - private fun parseDateTime(date: String?): Long = - if (date == null) - Date().time - else { - try { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US).parse(date).time - } catch (ex: Exception) { - try { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S", Locale.US).parse(date).time - } catch (ex: Exception) { - Date().time - } - } - } - - override fun imageUrlParse(response: Response): String = "" - - private class TypeSelect : Filter.Select("Search for", arrayOf(TYPE_SERIES, TYPE_READLISTS)) - private class LibraryFilter(val id: String, name: String) : Filter.CheckBox(name, false) - private class LibraryGroup(libraries: List) : Filter.Group("Libraries", libraries) - private class CollectionSelect(collections: List) : Filter.Select("Collection", collections.toTypedArray()) - private class SeriesSort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date added", "Date updated"), Selection(0, true)) - private class StatusFilter(name: String) : Filter.CheckBox(name, false) - private class StatusGroup(filters: List) : Filter.Group("Status", filters) - private class UnreadOnly : Filter.CheckBox("Unread only", false) - private class GenreFilter(genre: String) : Filter.CheckBox(genre, false) - private class GenreGroup(genres: List) : Filter.Group("Genres", genres) - private class TagFilter(tag: String) : Filter.CheckBox(tag, false) - private class TagGroup(tags: List) : Filter.Group("Tags", tags) - private class PublisherFilter(publisher: String) : Filter.CheckBox(publisher, false) - private class PublisherGroup(publishers: List) : Filter.Group("Publishers", publishers) - private class AuthorFilter(val author: AuthorDto) : Filter.CheckBox(author.name, false) - private class AuthorGroup(role: String, authors: List) : Filter.Group(role, authors) - - private data class CollectionFilterEntry( - val name: String, - val id: String? = null - ) { - override fun toString() = name - } - - override fun getFilterList(): FilterList { - val filters = try { - mutableListOf>( - UnreadOnly(), - TypeSelect(), - CollectionSelect(listOf(CollectionFilterEntry("None")) + collections.map { CollectionFilterEntry(it.name, it.id) }), - LibraryGroup(libraries.map { LibraryFilter(it.id, it.name) }.sortedBy { it.name.toLowerCase() }), - StatusGroup(listOf("Ongoing", "Ended", "Abandoned", "Hiatus").map { StatusFilter(it) }), - GenreGroup(genres.map { GenreFilter(it) }), - TagGroup(tags.map { TagFilter(it) }), - PublisherGroup(publishers.map { PublisherFilter(it) }) - ).also { - it.addAll(authors.map { (role, authors) -> AuthorGroup(role, authors.map { AuthorFilter(it) }) }) - it.add(SeriesSort()) - } - } catch (e: Exception) { - Log.e(LOG_TAG, "error while creating filter list", e) - emptyList() - } - - return FilterList(filters) - } - - private var libraries = emptyList() - private var collections = emptyList() - private var genres = emptySet() - private var tags = emptySet() - private var publishers = emptySet() - private var authors = emptyMap>() // roles to list of authors - - override val name = "Komga${if (suffix.isNotBlank()) " ($suffix)" else ""}" - override val lang = "en" - override val supportsLatest = true - private val LOG_TAG = "extension.all.komga${if (suffix.isNotBlank()) ".$suffix" else ""}" - - override val baseUrl by lazy { getPrefBaseUrl() } - private val username by lazy { getPrefUsername() } - private val password by lazy { getPrefPassword() } - private val gson by lazy { Gson() } - - override fun headersBuilder(): Headers.Builder = - Headers.Builder() - .add("User-Agent", "Tachiyomi Komga v${BuildConfig.VERSION_NAME}") - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override val client: OkHttpClient = - network.client.newBuilder() - .authenticator { _, response -> - if (response.request().header("Authorization") != null) { - null // Give up, we've already failed to authenticate. - } else { - response.request().newBuilder() - .addHeader("Authorization", Credentials.basic(username, password)) - .build() - } - } - .dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing - .build() - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - screen.addPreference(screen.editTextPreference(ADDRESS_TITLE, ADDRESS_DEFAULT, baseUrl)) - screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true)) - } - - private fun androidx.preference.PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference { - return androidx.preference.EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - - if (isPassword) { - setOnBindEditTextListener { - it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - } - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - screen.addPreference(screen.supportEditTextPreference(ADDRESS_TITLE, ADDRESS_DEFAULT, baseUrl)) - screen.addPreference(screen.supportEditTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.supportEditTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password)) - } - - private fun PreferenceScreen.supportEditTextPreference(title: String, default: String, value: String): EditTextPreference { - return EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - private fun getPrefBaseUrl(): String = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!! - private fun getPrefUsername(): String = preferences.getString(USERNAME_TITLE, USERNAME_DEFAULT)!! - private fun getPrefPassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!! - - init { - if (baseUrl.isNotBlank()) { - Single.fromCallable { - client.newCall(GET("$baseUrl/api/v1/libraries", headers)).execute() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { response -> - libraries = try { - gson.fromJson(response.body()?.charStream()!!) - } catch (e: Exception) { - emptyList() - } - }, - { tr -> - Log.e(LOG_TAG, "error while loading libraries for filters", tr) - } - ) - - Single.fromCallable { - client.newCall(GET("$baseUrl/api/v1/collections?unpaged=true", headers)).execute() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { response -> - collections = try { - gson.fromJson>(response.body()?.charStream()!!).content - } catch (e: Exception) { - emptyList() - } - }, - { tr -> - Log.e(LOG_TAG, "error while loading collections for filters", tr) - } - ) - - Single.fromCallable { - client.newCall(GET("$baseUrl/api/v1/genres", headers)).execute() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { response -> - genres = try { - gson.fromJson(response.body()?.charStream()!!) - } catch (e: Exception) { - emptySet() - } - }, - { tr -> - Log.e(LOG_TAG, "error while loading genres for filters", tr) - } - ) - - Single.fromCallable { - client.newCall(GET("$baseUrl/api/v1/tags", headers)).execute() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { response -> - tags = try { - gson.fromJson(response.body()?.charStream()!!) - } catch (e: Exception) { - emptySet() - } - }, - { tr -> - Log.e(LOG_TAG, "error while loading tags for filters", tr) - } - ) - - Single.fromCallable { - client.newCall(GET("$baseUrl/api/v1/publishers", headers)).execute() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { response -> - publishers = try { - gson.fromJson(response.body()?.charStream()!!) - } catch (e: Exception) { - emptySet() - } - }, - { tr -> - Log.e(LOG_TAG, "error while loading publishers for filters", tr) - } - ) - - Single.fromCallable { - client.newCall(GET("$baseUrl/api/v1/authors", headers)).execute() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { response -> - authors = try { - val list: List = gson.fromJson(response.body()?.charStream()!!) - list.groupBy { it.role } - } catch (e: Exception) { - emptyMap() - } - }, - { tr -> - Log.e(LOG_TAG, "error while loading authors for filters", tr) - } - ) - } - } - - companion object { - private const val ADDRESS_TITLE = "Address" - private const val ADDRESS_DEFAULT = "" - private const val USERNAME_TITLE = "Username" - private const val USERNAME_DEFAULT = "" - private const val PASSWORD_TITLE = "Password" - private const val PASSWORD_DEFAULT = "" - - private val supportedImageTypes = listOf("image/jpeg", "image/png", "image/gif", "image/webp") - - private const val TYPE_SERIES = "Series" - private const val TYPE_READLISTS = "Read lists" - } -} diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/KomgaFactory.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/KomgaFactory.kt deleted file mode 100644 index 87f29e8a6..000000000 --- a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/KomgaFactory.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.komga - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class KomgaFactory : SourceFactory { - - override fun createSources(): List = - listOf( - Komga(), - Komga("2"), - Komga("3") - ) -} diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/Dto.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/Dto.kt deleted file mode 100644 index 41a2c4cfb..000000000 --- a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/Dto.kt +++ /dev/null @@ -1,115 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.komga.dto - -data class LibraryDto( - val id: String, - val name: String -) - -data class SeriesDto( - val id: String, - val libraryId: String, - val name: String, - val created: String?, - val lastModified: String?, - val fileLastModified: String, - val booksCount: Int, - val metadata: SeriesMetadataDto, - val booksMetadata: BookMetadataAggregationDto -) - -data class SeriesMetadataDto( - val status: String, - val created: String?, - val lastModified: String?, - val title: String, - val titleSort: String, - val summary: String, - val summaryLock: Boolean, - val readingDirection: String, - val readingDirectionLock: Boolean, - val publisher: String, - val publisherLock: Boolean, - val ageRating: Int?, - val ageRatingLock: Boolean, - val language: String, - val languageLock: Boolean, - val genres: Set, - val genresLock: Boolean, - val tags: Set, - val tagsLock: Boolean -) - -data class BookMetadataAggregationDto( - val authors: List = emptyList(), - val releaseDate: String?, - val summary: String, - val summaryNumber: String, - - val created: String, - val lastModified: String -) - -data class BookDto( - val id: String, - val seriesId: String, - val name: String, - val number: Float, - val created: String?, - val lastModified: String?, - val fileLastModified: String, - val sizeBytes: Long, - val size: String, - val media: MediaDto, - val metadata: BookMetadataDto -) - -data class MediaDto( - val status: String, - val mediaType: String, - val pagesCount: Int -) - -data class PageDto( - val number: Int, - val fileName: String, - val mediaType: String -) - -data class BookMetadataDto( - val title: String, - val titleLock: Boolean, - val summary: String, - val summaryLock: Boolean, - val number: String, - val numberLock: Boolean, - val numberSort: Float, - val numberSortLock: Boolean, - val releaseDate: String?, - val releaseDateLock: Boolean, - val authors: List, - val authorsLock: Boolean -) - -data class AuthorDto( - val name: String, - val role: String -) - -data class CollectionDto( - val id: String, - val name: String, - val ordered: Boolean, - val seriesIds: List, - val createdDate: String, - val lastModifiedDate: String, - val filtered: Boolean -) - -data class ReadListDto( - val id: String, - val name: String, - val bookIds: List, - val createdDate: String, - val lastModifiedDate: String, - val filtered: Boolean -) diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/PageWrapperDto.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/PageWrapperDto.kt deleted file mode 100644 index 2b1683ba8..000000000 --- a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/PageWrapperDto.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.komga.dto - -data class PageWrapperDto( - val content: List, - val empty: Boolean, - val first: Boolean, - val last: Boolean, - val number: Long, - val numberOfElements: Long, - val size: Long, - val totalElements: Long, - val totalPages: Long -) diff --git a/src/all/lanraragi/AndroidManifest.xml b/src/all/lanraragi/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/lanraragi/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/lanraragi/build.gradle b/src/all/lanraragi/build.gradle deleted file mode 100644 index 9201f365c..000000000 --- a/src/all/lanraragi/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'LANraragi' - pkgNameSuffix = 'all.lanraragi' - extClass = '.LANraragi' - extVersionCode = 6 - libVersion = '1.2' -} - -dependencies { - implementation 'io.reactivex:rxandroid:1.2.1' - implementation 'io.reactivex:rxjava:1.3.6' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/lanraragi/res/mipmap-hdpi/ic_launcher.png b/src/all/lanraragi/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index fd5da2645..000000000 Binary files a/src/all/lanraragi/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/lanraragi/res/mipmap-mdpi/ic_launcher.png b/src/all/lanraragi/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d6c4ff69b..000000000 Binary files a/src/all/lanraragi/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/lanraragi/res/mipmap-xhdpi/ic_launcher.png b/src/all/lanraragi/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8e4b170b5..000000000 Binary files a/src/all/lanraragi/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/lanraragi/res/mipmap-xxhdpi/ic_launcher.png b/src/all/lanraragi/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 083804647..000000000 Binary files a/src/all/lanraragi/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/lanraragi/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/lanraragi/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index fae8e4285..000000000 Binary files a/src/all/lanraragi/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/lanraragi/res/web_hi_res_512.png b/src/all/lanraragi/res/web_hi_res_512.png deleted file mode 100644 index 0ddd28e9b..000000000 Binary files a/src/all/lanraragi/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/LANraragi.kt b/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/LANraragi.kt deleted file mode 100644 index ab824428b..000000000 --- a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/LANraragi.kt +++ /dev/null @@ -1,569 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.lanraragi - -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.util.Base64 -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import eu.kanade.tachiyomi.extension.all.lanraragi.model.Archive -import eu.kanade.tachiyomi.extension.all.lanraragi.model.ArchivePage -import eu.kanade.tachiyomi.extension.all.lanraragi.model.ArchiveSearchResult -import eu.kanade.tachiyomi.extension.all.lanraragi.model.Category -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.CacheControl -import okhttp3.Dns -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import rx.Single -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -open class LANraragi : ConfigurableSource, HttpSource() { - - override val baseUrl: String - get() = preferences.getString("hostname", "http://127.0.0.1:3000")!! - - override val lang = "all" - - override val name = "LANraragi" - - override val supportsLatest = true - - private val apiKey: String - get() = preferences.getString("apiKey", "")!! - - private val latestNamespacePref: String - get() = preferences.getString("latestNamespacePref", DEFAULT_SORT_BY_NS)!! - - private val gson: Gson = Gson() - - private var randomArchiveID: String = "" - - override fun fetchMangaDetails(manga: SManga): Observable { - val id = if (manga.url == "/random") randomArchiveID else getReaderId(manga.url) - val uri = getApiUriBuilder("/api/archives/$id/metadata").build() - - if (manga.url == "/random") randomArchiveID = getRandomID(getRandomIDResponse()) - - return client.newCall(GET(uri.toString(), headers)) - .asObservable().doOnNext { - if (!it.isSuccessful && it.code() == 404) error("Log in with WebView then try again.") - } - .map { mangaDetailsParse(it).apply { initialized = true } } - } - - override fun mangaDetailsRequest(manga: SManga): Request { - // Catch-all that includes /random's ID via thumbnail - val id = getThumbnailId(manga.thumbnail_url!!) - - return GET("$baseUrl/reader?id=$id", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val archive = gson.fromJson(response.body()!!.string()) - - return archiveToSManga(archive) - } - - override fun chapterListRequest(manga: SManga): Request { - val id = if (manga.url == "/random") randomArchiveID else getReaderId(manga.url) - val uri = getApiUriBuilder("/api/archives/$id/metadata").build() - - return GET(uri.toString(), headers) - } - - override fun chapterListParse(response: Response): List { - val archive = gson.fromJson(response.body()!!.string()) - val uri = getApiUriBuilder("/api/archives/${archive.arcid}/extract") - - // Replicate old behavior and unset "isnew" for the archive. - if (archive.isnew == "true") { - val clearNew = Request.Builder() - .url("$baseUrl/api/archives/${archive.arcid}/isnew") - .delete() - .build() - - client.newCall(clearNew).execute() - } - - return listOf( - SChapter.create().apply { - val uriBuild = uri.build() - - url = uriBuild.toString() - chapter_number = 1F - name = "Chapter" - - getDateAdded(archive.tags).toLongOrNull()?.let { - date_upload = it - } - } - ) - } - - override fun pageListRequest(chapter: SChapter): Request { - return POST(chapter.url, headers) - } - - override fun pageListParse(response: Response): List { - val archivePage = gson.fromJson(response.body()!!.string()) - - return archivePage.pages.mapIndexed { index, url -> - val uri = Uri.parse("${baseUrl}${url.trimStart('.')}") - Page(index, uri.toString(), uri.toString(), uri) - } - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("imageUrlParse is unused") - - override fun popularMangaRequest(page: Int): Request { - return searchMangaRequest(page, "", FilterList()) - } - - override fun popularMangaParse(response: Response): MangasPage { - return searchMangaParse(response) - } - - override fun latestUpdatesRequest(page: Int): Request { - val filters = mutableListOf>() - val prefNewOnly = preferences.getBoolean("latestNewOnly", false) - - if (prefNewOnly) filters.add(NewArchivesOnly(true)) - - if (latestNamespacePref.isNotBlank()) { - filters.add(SortByNamespace(latestNamespacePref)) - filters.add(DescendingOrder(true)) - } - - return searchMangaRequest(page, "", FilterList(filters)) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - return searchMangaParse(response) - } - - private var lastResultCount: Int = 100 - private var lastRecordsFiltered: Int = 0 - private var maxResultCount: Int = 0 - private var totalRecords: Int = 0 - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = getApiUriBuilder("/api/search") - var startPageOffset = 0 - - filters.forEach { filter -> - when (filter) { - is StartingPage -> { - startPageOffset = filter.state.toIntOrNull() ?: 1 - - // Exception for API wrapping around and user input of 0 - if (startPageOffset > 0) { - startPageOffset -= 1 - } - } - is NewArchivesOnly -> if (filter.state) uri.appendQueryParameter("newonly", "true") - is UntaggedArchivesOnly -> if (filter.state) uri.appendQueryParameter("untaggedonly", "true") - is DescendingOrder -> if (filter.state) uri.appendQueryParameter("order", "desc") - is SortByNamespace -> if (filter.state.isNotEmpty()) uri.appendQueryParameter("sortby", filter.state.trim()) - is CategorySelect -> if (filter.state > 0) uri.appendQueryParameter("category", filter.toUriPart()) - } - } - - uri.appendQueryParameter("start", ((page - 1 + startPageOffset) * maxResultCount).toString()) - - if (query.isNotEmpty()) { - uri.appendQueryParameter("filter", query) - } - - return GET(uri.toString(), headers, CacheControl.FORCE_NETWORK) - } - - override fun searchMangaParse(response: Response): MangasPage { - val jsonResult = gson.fromJson(response.body()!!.string()) - val currentStart = getStart(response) - val archives = arrayListOf() - - lastResultCount = jsonResult.data.size - maxResultCount = if (lastResultCount >= maxResultCount) lastResultCount else maxResultCount - lastRecordsFiltered = jsonResult.recordsFiltered - totalRecords = jsonResult.recordsTotal - - if (canShowRandom(response)) { - archives.add( - SManga.create().apply { - url = "/random" - title = "Random" - description = "Refresh for a random archive." - // Get the server's "noThumb" thumbnail by default - thumbnail_url = getThumbnailUri("tachiyomi") - } - ) - } - - jsonResult.data.map { - archives.add(archiveToSManga(it)) - } - - return MangasPage(archives, currentStart + jsonResult.data.size < jsonResult.recordsFiltered) - } - - private fun canShowRandom(response: Response): Boolean { - // Server has archives, not paginating, no meaningful filtering. querySize check would have - // to be broken intentionally as it goes through searchMangaRequest. Likely for an API change. - return ( - totalRecords > 0 && - getStart(response) == 0 && - response.request().url().querySize() == 1 // ?start= - ) - } - - private fun archiveToSManga(archive: Archive) = SManga.create().apply { - url = "/reader?id=${archive.arcid}" - title = archive.title - description = archive.title - thumbnail_url = getThumbnailUri(archive.arcid) - genre = archive.tags - artist = getArtist(archive.tags) - author = artist - } - - override fun headersBuilder() = Headers.Builder().apply { - if (apiKey.isNotEmpty()) { - val apiKey64 = Base64.encodeToString(apiKey.toByteArray(), Base64.DEFAULT).trim() - add("Authorization", "Bearer $apiKey64") - } - } - - private class DescendingOrder(overrideState: Boolean = false) : Filter.CheckBox("Descending Order", overrideState) - private class NewArchivesOnly(overrideState: Boolean = false) : Filter.CheckBox("New Archives Only", overrideState) - private class UntaggedArchivesOnly : Filter.CheckBox("Untagged Archives Only", false) - private class StartingPage(stats: String) : Filter.Text("Starting Page$stats", "") - private class SortByNamespace(defaultText: String = "") : Filter.Text("Sort by (namespace)", defaultText) - private class CategorySelect(categories: Array>) : UriPartFilter("Category", categories) - - override fun getFilterList() = FilterList( - CategorySelect(getCategoryPairs(categories)), - Filter.Separator(), - DescendingOrder(), - NewArchivesOnly(), - UntaggedArchivesOnly(), - StartingPage(startingPageStats()), - SortByNamespace() - ) - - private var categories = emptyList() - - // Preferences - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val hostnamePref = EditTextPreference(screen.context).apply { - key = "Hostname" - title = "Hostname" - text = baseUrl - summary = baseUrl - dialogTitle = "Hostname" - - setOnPreferenceChangeListener { _, newValue -> - var hostname = newValue as String - if (!hostname.startsWith("http://") && !hostname.startsWith("https://")) { - hostname = "http://$hostname" - } - - this.apply { - text = hostname - summary = hostname - } - - preferences.edit().putString("hostname", hostname).commit() - } - } - - val apiKeyPref = EditTextPreference(screen.context).apply { - key = "API Key" - title = "API Key" - text = apiKey - summary = "Required if No-Fun Mode is enabled." - dialogTitle = "API Key" - - setOnPreferenceChangeListener { _, newValue -> - val apiKey = newValue as String - - this.apply { - text = apiKey - summary = "Required if No-Fun Mode is enabled." - } - - preferences.edit().putString("apiKey", newValue).commit() - } - } - - val latestNewOnlyPref = CheckBoxPreference(screen.context).apply { - key = "latestNewOnly" - title = "Latest - New Only" - setDefaultValue(true) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("latestNewOnly", checkValue).commit() - } - } - - val latestNamespacePref = EditTextPreference(screen.context).apply { - key = "latestNamespacePref" - title = "Latest - Sort by Namespace" - text = latestNamespacePref - summary = "Sort by the given namespace for Latest, such as date_added." - dialogTitle = "Latest - Sort by Namespace" - setDefaultValue(DEFAULT_SORT_BY_NS) - - setOnPreferenceChangeListener { _, newValue -> - val latestNamespacePref = newValue as String - - this.apply { - text = latestNamespacePref - } - - preferences.edit().putString("latestNamespacePref", newValue).commit() - } - } - - screen.addPreference(hostnamePref) - screen.addPreference(apiKeyPref) - screen.addPreference(latestNewOnlyPref) - screen.addPreference(latestNamespacePref) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val hostnamePref = androidx.preference.EditTextPreference(screen.context).apply { - key = "Hostname" - title = "Hostname" - text = baseUrl - summary = baseUrl - dialogTitle = "Hostname" - - setOnPreferenceChangeListener { _, newValue -> - var hostname = newValue as String - if (!hostname.startsWith("http://") && !hostname.startsWith("https://")) { - hostname = "http://$hostname" - } - - this.apply { - text = hostname - summary = hostname - } - - preferences.edit().putString("hostname", hostname).commit() - } - } - - val apiKeyPref = androidx.preference.EditTextPreference(screen.context).apply { - key = "API Key" - title = "API Key" - text = apiKey - summary = "Required if No-Fun Mode is enabled." - dialogTitle = "API Key" - - setOnPreferenceChangeListener { _, newValue -> - val apiKey = newValue as String - - this.apply { - text = apiKey - summary = "Required if No-Fun Mode is enabled." - } - - preferences.edit().putString("apiKey", newValue).commit() - } - } - - val latestNewOnlyPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = "latestNewOnly" - title = "Latest - New Only" - setDefaultValue(true) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("latestNewOnly", checkValue).commit() - } - } - - val latestNamespacePref = androidx.preference.EditTextPreference(screen.context).apply { - key = "latestNamespacePref" - title = "Latest - Sort by Namespace" - text = latestNamespacePref - summary = "Sort by the given namespace for Latest, such as date_added." - dialogTitle = "Latest - Sort by Namespace" - setDefaultValue(DEFAULT_SORT_BY_NS) - - setOnPreferenceChangeListener { _, newValue -> - val latestNamespacePref = newValue as String - - this.apply { - text = latestNamespacePref - } - - preferences.edit().putString("latestNamespacePref", newValue).commit() - } - } - - screen.addPreference(hostnamePref) - screen.addPreference(apiKeyPref) - screen.addPreference(latestNewOnlyPref) - screen.addPreference(latestNamespacePref) - } - - // Helper - private fun getRandomID(response: Response): String { - return response.headers("Location").firstOrNull()?.split("=")?.last() ?: "" - } - - private fun getRandomIDResponse(): Response { - // Separate function for init and Library - // /random 301's to the ID so the request is over as quickly as it starts - return clientNoFollow.newCall(GET("$baseUrl/random", headers)).execute() - } - - protected open class UriPartFilter(displayName: String, val vals: Array>) : - Filter.Select(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first - } - - private fun getCategories() { - Single.fromCallable { - client.newCall(GET("$baseUrl/api/categories", headers)).execute() - } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .subscribe( - { - categories = try { - gson.fromJson(it.body()?.charStream()!!) - } catch (e: Exception) { - emptyList() - } - }, - {} - ) - } - - private fun getCategoryPairs(categories: List): Array> { - // Empty pair to disable. Sort by pinned status then name for convenience. - // Web client sort is pinned > last_used but reflects between page changes. - - val pin = "\uD83D\uDCCC " - - // Maintain categories sync for next FilterList reset. If there's demand for it, it's now - // possible to sort by last_used similar to the web client. Maybe an option toggle? - getCategories() - - return listOf(Pair("", "")) - .plus( - categories - .sortedWith(compareByDescending { it.pinned }.thenBy { it.name }) - .map { - val pinned = if (it.pinned == "1") pin else "" - Pair(it.id, "$pinned${it.name}") - } - ) - .toTypedArray() - } - - private fun startingPageStats(): String { - return if (maxResultCount > 0 && totalRecords > 0) " ($maxResultCount / $lastRecordsFiltered items)" else "" - } - - private fun getApiUriBuilder(path: String): Uri.Builder { - val uri = Uri.parse("$baseUrl$path").buildUpon() - - return uri - } - - private fun getThumbnailUri(id: String): String { - val uri = getApiUriBuilder("/api/archives/$id/thumbnail") - - return uri.toString() - } - - private fun getTopResponse(response: Response): Response { - return if (response.priorResponse() == null) response else getTopResponse(response.priorResponse()!!) - } - - private fun getId(response: Response): String { - return getTopResponse(response).request().url().queryParameter("id").toString() - } - - private fun getStart(response: Response): Int { - return getTopResponse(response).request().url().queryParameter("start")!!.toInt() - } - - private fun getReaderId(url: String): String { - return Regex("""\/reader\?id=(\w{40})""").find(url)?.groupValues?.get(1) ?: "" - } - - private fun getThumbnailId(url: String): String { - return Regex("""\/(\w{40})\/thumbnail""").find(url)?.groupValues?.get(1) ?: "" - } - - private fun getNSTag(tags: String?, tag: String): List? { - tags?.split(',')?.forEach { - if (it.contains(':')) { - val temp = it.trim().split(":", limit = 2) - if (temp[0].equals(tag, true)) return temp - } - } - - return null - } - - private fun getArtist(tags: String?): String = getNSTag(tags, "artist")?.get(1) ?: "N/A" - - private fun getDateAdded(tags: String?): String { - // Pad Date Added NS to milliseconds - return getNSTag(tags, "date_added")?.get(1)?.padEnd(13, '0') ?: "" - } - - // Headers (currently auth) are done in headersBuilder - override val client: OkHttpClient = network.client.newBuilder().dns(Dns.SYSTEM).build() - // Specifically for /random to grab IDs without triggering a server-side extract - private val clientNoFollow: OkHttpClient = client.newBuilder().dns(Dns.SYSTEM).followRedirects(false).build() - - init { - if (baseUrl.isNotBlank()) { - // Save a FilterList reset - getCategories() - - // Save users a Random refresh in the extension and from Library - Single.fromCallable { getRandomIDResponse() } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .subscribe( - { randomArchiveID = getRandomID(it) }, - {} - ) - } - } - - companion object { - private const val DEFAULT_SORT_BY_NS = "date_added" - } -} diff --git a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/Archive.kt b/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/Archive.kt deleted file mode 100644 index 4e507ba0e..000000000 --- a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/Archive.kt +++ /dev/null @@ -1,8 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.lanraragi.model - -data class Archive( - val arcid: String, - val isnew: String, - val tags: String, - val title: String -) diff --git a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/ArchivePage.kt b/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/ArchivePage.kt deleted file mode 100644 index b620ca4e1..000000000 --- a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/ArchivePage.kt +++ /dev/null @@ -1,5 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.lanraragi.model - -data class ArchivePage( - val pages: List -) diff --git a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/ArchiveSearchResult.kt b/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/ArchiveSearchResult.kt deleted file mode 100644 index c9046f18f..000000000 --- a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/ArchiveSearchResult.kt +++ /dev/null @@ -1,8 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.lanraragi.model - -data class ArchiveSearchResult( - val data: List, - val draw: Int, - val recordsFiltered: Int, - val recordsTotal: Int -) diff --git a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/Category.kt b/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/Category.kt deleted file mode 100644 index 8cafd247c..000000000 --- a/src/all/lanraragi/src/eu/kanade/tachiyomi/extension/all/lanraragi/model/Category.kt +++ /dev/null @@ -1,8 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.lanraragi.model - -data class Category( - val id: String, - val last_used: String, - val name: String, - val pinned: String -) diff --git a/src/all/mangadex/AndroidManifest.xml b/src/all/mangadex/AndroidManifest.xml deleted file mode 100644 index d7b6f4f5a..000000000 --- a/src/all/mangadex/AndroidManifest.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/all/mangadex/build.gradle b/src/all/mangadex/build.gradle deleted file mode 100644 index 0e54b398b..000000000 --- a/src/all/mangadex/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaDex' - pkgNameSuffix = 'all.mangadex' - extClass = '.MangaDexFactory' - extVersionCode = 105 - libVersion = '1.2' - containsNsfw = true -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/mangadex/res/mipmap-hdpi/ic_launcher.png b/src/all/mangadex/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1a7f25b74..000000000 Binary files a/src/all/mangadex/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangadex/res/mipmap-mdpi/ic_launcher.png b/src/all/mangadex/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ab536517d..000000000 Binary files a/src/all/mangadex/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangadex/res/mipmap-xhdpi/ic_launcher.png b/src/all/mangadex/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f5c650b4d..000000000 Binary files a/src/all/mangadex/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangadex/res/mipmap-xxhdpi/ic_launcher.png b/src/all/mangadex/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 49da94a01..000000000 Binary files a/src/all/mangadex/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangadex/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/mangadex/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7fd2132ec..000000000 Binary files a/src/all/mangadex/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangadex/res/web_hi_res_512.png b/src/all/mangadex/res/web_hi_res_512.png deleted file mode 100644 index 6b845b7ef..000000000 Binary files a/src/all/mangadex/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt deleted file mode 100644 index 50ede068b..000000000 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDex.kt +++ /dev/null @@ -1,1135 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangadex - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import android.util.Log -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.bool -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.long -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.parser.Parser -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.net.URLEncoder -import java.util.Date -import java.util.concurrent.TimeUnit -import kotlin.collections.set - -abstract class MangaDex( - override val lang: String, - private val internalLang: String -) : ConfigurableSource, ParsedHttpSource() { - - override val name = "MangaDex" - - override val baseUrl = "https://www.mangadex.org" - - private val cdnUrl = "https://mangadex.org" // "https://s0.mangadex.org" - - override val supportsLatest = true - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - private val mangadexDescription: MangadexDescription by lazy { - MangadexDescription(internalLang) - } - - private val rateLimitInterceptor = MdRateLimitInterceptor() - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor) - .addInterceptor(CoverInterceptor()) - .addInterceptor(MdAtHomeReportInterceptor(network.client, headersBuilder().build())) - .build() - - private fun clientBuilder(): OkHttpClient = clientBuilder(getShowR18()) - - private fun clientBuilder(r18Toggle: Int): OkHttpClient = network.client.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addNetworkInterceptor(rateLimitInterceptor) - .addNetworkInterceptor { chain -> - val originalCookies = chain.request().header("Cookie") ?: "" - val newReq = chain - .request() - .newBuilder() - .header("Cookie", "$originalCookies; ${cookiesHeader(r18Toggle)}") - .build() - chain.proceed(newReq) - }.build()!! - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Tachiyomi " + System.getProperty("http.agent")) - } - - private fun cookiesHeader(r18Toggle: Int): String { - val cookies = mutableMapOf() - cookies["mangadex_h_toggle"] = r18Toggle.toString() - return buildCookies(cookies) - } - - private fun buildCookies(cookies: Map) = - cookies.entries.joinToString(separator = "; ", postfix = ";") { - "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" - } - - override fun popularMangaSelector() = "div.manga-entry" - - override fun latestUpdatesSelector() = "tr a.manga_title" - - // url matches default SortFilter selection (Rating Descending) - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/titles/7/$page/", headersBuilder().build(), CacheControl.FORCE_NETWORK) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/updates/$page", headersBuilder().build(), CacheControl.FORCE_NETWORK) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.manga_title").first().let { - val url = modifyMangaUrl(it.attr("href")) - manga.setUrlWithoutDomain(url) - manga.title = it.text().trim() - } - manga.thumbnail_url = formThumbUrl(manga.url) - return manga - } - - private fun modifyMangaUrl(url: String): String = - url.replace("/title/", "/manga/").substringBeforeLast("/") + "/" - - private fun formThumbUrl(mangaUrl: String): String { - var ext = ".jpg" - - if (getShowThumbnail() == LOW_QUALITY) { - ext = ".thumb$ext" - } - - return cdnUrl + "/images/manga/" + getMangaId(mangaUrl) + ext - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.let { - manga.setUrlWithoutDomain(modifyMangaUrl(it.attr("href"))) - manga.title = it.text().trim() - } - manga.thumbnail_url = formThumbUrl(manga.url) - - return manga - } - - override fun popularMangaNextPageSelector() = - ".pagination li:not(.disabled) span[title*=last page]:not(disabled)" - - override fun latestUpdatesNextPageSelector() = - ".pagination li:not(.disabled) span[title*=last page]:not(disabled)" - - override fun searchMangaNextPageSelector() = - ".pagination li:not(.disabled) span[title*=last page]:not(disabled)" - - override fun fetchPopularManga(page: Int): Observable { - return clientBuilder().newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - popularMangaParse(response) - } - } - - override fun fetchLatestUpdates(page: Int): Observable { - return clientBuilder().newCall(latestUpdatesRequest(page)) - .asObservableSuccess() - .map { response -> - latestUpdatesParse(response) - } - } - - override fun fetchSearchManga( - page: Int, - query: String, - filters: FilterList - ): Observable { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(searchMangaByIdRequest(realQuery)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = "/manga/$realQuery/" - MangasPage(listOf(details), false) - } - } else { - getSearchClient(filters).newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - private fun getSearchClient(filters: FilterList): OkHttpClient { - filters.forEach { filter -> - when (filter) { - is R18 -> { - return when (filter.state) { - 1 -> clientBuilder(ALL) - 2 -> clientBuilder(ONLY_R18) - 3 -> clientBuilder(NO_R18) - else -> clientBuilder() - } - } - } - } - return clientBuilder() - } - - private var groupSearch: String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (page == 1) groupSearch = null - val genresToInclude = mutableListOf() - val genresToExclude = mutableListOf() - - // Do traditional search - val url = HttpUrl.parse("$baseUrl/?page=search")!!.newBuilder() - .addQueryParameter("p", page.toString()) - .addQueryParameter("title", query.replace(WHITESPACE_REGEX, " ")) - - filters.forEach { filter -> - when (filter) { - is TextField -> url.addQueryParameter(filter.key, filter.state) - is Demographic -> { - val demographicToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.isIncluded()) { - demographicToInclude.add(content.id) - } - } - if (demographicToInclude.isNotEmpty()) { - url.addQueryParameter("demos", demographicToInclude.joinToString(",")) - } - } - is PublicationStatus -> { - val publicationToInclude = mutableListOf() - filter.state.forEach { content -> - if (content.isIncluded()) { - publicationToInclude.add(content.id) - } - } - if (publicationToInclude.isNotEmpty()) { - url.addQueryParameter("statuses", publicationToInclude.joinToString(",")) - } - } - is OriginalLanguage -> { - if (filter.state != 0) { - val number: String = - SOURCE_LANG_LIST.first { it.first == filter.values[filter.state] }.second - url.addQueryParameter("lang_id", number) - } - } - is TagInclusionMode -> { - url.addQueryParameter("tag_mode_inc", arrayOf("all", "any")[filter.state]) - } - is TagExclusionMode -> { - url.addQueryParameter("tag_mode_exc", arrayOf("all", "any")[filter.state]) - } - is ContentList -> { - filter.state.forEach { content -> - if (content.isExcluded()) { - genresToExclude.add(content.id) - } else if (content.isIncluded()) { - genresToInclude.add(content.id) - } - } - } - is FormatList -> { - filter.state.forEach { format -> - if (format.isExcluded()) { - genresToExclude.add(format.id) - } else if (format.isIncluded()) { - genresToInclude.add(format.id) - } - } - } - is GenreList -> { - filter.state.forEach { genre -> - if (genre.isExcluded()) { - genresToExclude.add(genre.id) - } else if (genre.isIncluded()) { - genresToInclude.add(genre.id) - } - } - } - is ThemeList -> { - filter.state.forEach { theme -> - if (theme.isExcluded()) { - genresToExclude.add(theme.id) - } else if (theme.isIncluded()) { - genresToInclude.add(theme.id) - } - } - } - is SortFilter -> { - if (filter.state != null) { - if (filter.state!!.ascending) { - url.addQueryParameter( - "s", - sortables[filter.state!!.index].second.toString() - ) - } else { - url.addQueryParameter( - "s", - sortables[filter.state!!.index].third.toString() - ) - } - } - } - is ScanGroup -> { - groupSearch = when { - filter.state.isNotEmpty() && page == 1 -> "$baseUrl/groups/0/1/${filter.state}" - filter.state.isNotEmpty() && page > 1 -> "${groupSearch!!}/$page" - else -> null - } - } - } - } - - // Manually append genres list to avoid commas being encoded - var urlToUse = url.toString() - if (genresToInclude.isNotEmpty()) { - urlToUse += "&tags_inc=" + genresToInclude.joinToString(",") - } - if (genresToExclude.isNotEmpty()) { - urlToUse += "&tags_exc=" + genresToExclude.joinToString(",") - } - - return GET(groupSearch ?: urlToUse, headersBuilder().build(), CacheControl.FORCE_NETWORK) - } - - override fun searchMangaParse(response: Response): MangasPage { - return if (response.request().url().toString().contains("/groups/")) { - response.asJsoup() - .select(".table > tbody:nth-child(2) > tr:nth-child(1) > td:nth-child(2) > a") - .firstOrNull()?.attr("abs:href") - ?.let { - groupSearch = "$it/manga/0" - super.searchMangaParse( - client.newCall( - GET( - groupSearch!!, - headersBuilder().build() - ) - ).execute() - ) - } - ?: MangasPage(emptyList(), false) - } else { - val document = response.asJsoup() - if (document.select("#login_button") - .isNotEmpty() - ) throw Exception("Log in via WebView to enable search") - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - MangasPage(mangas, hasNextPage) - } - } - - override fun searchMangaSelector() = "div.manga-entry" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - element.select("a.manga_title").first().let { - val url = modifyMangaUrl(it.attr("href")) - manga.setUrlWithoutDomain(url) - manga.title = it.text().trim() - } - - manga.thumbnail_url = formThumbUrl(manga.url) - - return manga - } - - override fun fetchMangaDetails(manga: SManga): Observable { - return clientBuilder().newCall(apiRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun apiRequest(manga: SManga): Request { - return GET( - API_URL + API_MANGA + getMangaId(manga.url) + API_MANGA_INCLUDE_CHAPTERS, - headers, - CacheControl.FORCE_NETWORK - ) - } - - private fun searchMangaByIdRequest(id: String): Request { - return GET(API_URL + API_MANGA + id + API_MANGA_INCLUDE_CHAPTERS, headers, CacheControl.FORCE_NETWORK) - } - - private fun getMangaId(url: String): String { - val lastSection = url.trimEnd('/').substringAfterLast("/") - return if (lastSection.toIntOrNull() != null) { - lastSection - } else { - // this occurs if person has manga from before that had the id/name/ - url.trimEnd('/').substringBeforeLast("/").substringAfterLast("/") - } - } - - override fun mangaDetailsParse(response: Response): SManga { - val manga = SManga.create() - val jsonData = response.body()!!.string() - val json = JsonParser().parse(jsonData).asJsonObject["data"] - val mangaJson = json["manga"].asJsonObject - val chapterJson = json["chapters"].asJsonArray - manga.title = cleanString(mangaJson["title"].string) - manga.thumbnail_url = mangaJson["mainCover"].string - manga.description = - cleanString(mangadexDescription.clean(mangaJson["description"].string)) - manga.author = cleanString(mangaJson["author"].array.map { it.string }.joinToString()) - manga.artist = cleanString(mangaJson["artist"].array.map { it.string }.joinToString()) - val status = mangaJson["publication"]["status"].int - val finalChapterNumber = getFinalChapter(mangaJson) - if ((status == 2 || status == 3) && chapterJson != null && isMangaCompleted( - chapterJson, - finalChapterNumber - ) - ) { - manga.status = SManga.COMPLETED - } else if (status == 2 && chapterJson != null && isOneshot( - chapterJson, - finalChapterNumber - ) - ) { - manga.status = SManga.COMPLETED - } else { - manga.status = parseStatus(status) - } - - val genres = if (mangaJson["isHentai"].bool) { - listOf("Hentai") - } else { - listOf() - } + - mangaJson["tags"].array.mapNotNull { GENRES[it.string] } + - mangaJson["publication"]["language"].string - manga.genre = genres.joinToString(", ") - return manga - } - - // Remove bbcode tags as well as parses any html characters in description or chapter name to actual characters for example ♥ will show ♥ - private fun cleanString(string: String): String { - val bbRegex = - """\[(\w+)[^]]*](.*?)\[/\1]""".toRegex() - var intermediate = string - .replace("[list]", "") - .replace("[/list]", "") - .replace("[*]", "") - // Recursively remove nested bbcode - while (bbRegex.containsMatchIn(intermediate)) { - intermediate = intermediate.replace(bbRegex, "$2") - } - return Parser.unescapeEntities(intermediate, false) - } - - override fun mangaDetailsParse(document: Document) = throw Exception("Not Used") - - override fun chapterListSelector() = "" - - override fun fetchChapterList(manga: SManga): Observable> { - return clientBuilder().newCall(apiRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response) - } - } - - private fun getFinalChapter(jsonObj: JsonObject): String = - jsonObj.get("lastChapter").nullString?.trim() ?: "" - - private fun isOneshot(chapterJson: JsonArray, lastChapter: String): Boolean { - val chapter = - chapterJson.takeIf { it.size() > 0 }?.elementAt(0)?.asJsonObject?.get("title")?.string - return if (chapter != null) { - chapter == "Oneshot" || chapter.isEmpty() && lastChapter == "0" - } else { - false - } - } - - private fun isMangaCompleted(chapterJson: JsonArray, finalChapterNumber: String): Boolean { - val count = chapterJson - .filter { it.asJsonObject.get("language").string == internalLang } - .filter { doesFinalChapterExist(finalChapterNumber, it) }.count() - return count != 0 - } - - private fun doesFinalChapterExist(finalChapterNumber: String, chapterJson: JsonElement) = - finalChapterNumber.isNotEmpty() && finalChapterNumber == chapterJson["chapter"].string.trim() - - override fun chapterListParse(response: Response): List { - val now = Date().time - val jsonData = response.body()!!.string() - val json = JsonParser().parse(jsonData).asJsonObject["data"] - val mangaJson = json["manga"].asJsonObject - - val status = mangaJson["publication"]["status"].int - - val finalChapterNumber = getFinalChapter(mangaJson) - - val chapterJson = json["chapters"].asJsonArray - val chapters = mutableListOf() - - // Skip chapters that don't match the desired language, or are future releases - val groups = json["groups"].array.map { - val group = it.asJsonObject - Pair(group["id"].int, group["name"].string) - }.toMap() - - val hasMangaPlus = groups.containsKey(9097) - - chapterJson?.forEach { jsonElement -> - val chapterElement = jsonElement.asJsonObject - if (shouldKeepChapter(chapterElement, now)) { - chapters.add(chapterFromJson(chapterElement, finalChapterNumber, status, groups)) - } - } - return chapters.also { if (it.isEmpty() && hasMangaPlus) throw Exception("This only has MangaPlus chapters, use the MangaPlus extension") } - } - - /** - * Filter out the following chapters: - * language doesn't match the chosen language - * Future chapters - * Chapters from MangaPlus since they have to be read in MangaPlus extension - */ - private fun shouldKeepChapter(chapterJson: JsonObject, now: Long): Boolean { - return when { - chapterJson["language"].string != internalLang -> false - (chapterJson["timestamp"].asLong * 1000) > now -> false - chapterJson["groups"].array.map { it.string }.contains("9097") -> false - else -> true - } - } - - private fun chapterFromJson( - chapterJson: JsonObject, - finalChapterNumber: String, - status: Int, - groups: Map - ): SChapter { - val chapter = SChapter.create() - chapter.url = OLD_API_CHAPTER + chapterJson["id"].string - val chapterName = mutableListOf() - // Build chapter name - if (chapterJson["volume"].string.isNotBlank()) { - chapterName.add("Vol." + chapterJson.get("volume").string) - } - if (chapterJson["chapter"].string.isNotBlank()) { - chapterName.add("Ch." + chapterJson.get("chapter").string) - } - if (chapterJson["title"].string.isNotBlank()) { - if (chapterName.isNotEmpty()) { - chapterName.add("-") - } - chapterName.add(chapterJson["title"].string) - } - // if volume, chapter and title is empty its a oneshot - if (chapterName.isEmpty()) { - chapterName.add("Oneshot") - } - if ((status == 2 || status == 3) && doesFinalChapterExist( - finalChapterNumber, - chapterJson - ) - ) { - chapterName.add("[END]") - } - - chapter.name = cleanString(chapterName.joinToString(" ")) - // Convert from unix time - chapter.date_upload = chapterJson.get("timestamp").long * 1000 - val scanlatorName = chapterJson["groups"].asJsonArray.map { it.int }.map { - groups[it] - } - - chapter.scanlator = cleanString(scanlatorName.joinToString(" & ")) - - return chapter - } - - override fun chapterFromElement(element: Element) = throw Exception("Not used") - - override fun fetchPageList(chapter: SChapter): Observable> { - return client.newCall(pageListRequest(chapter)) - .asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - if (response.code() == 451) { - error("Error 451: Log in to view manga; contact MangaDex if error persists.") - } else { - throw Exception("HTTP error ${response.code()}") - } - } - } - .map { response -> - pageListParse(response) - } - } - - override fun pageListRequest(chapter: SChapter): Request { - if (chapter.scanlator == "MangaPlus") { - throw Exception("Chapter is licensed; use the MangaPlus extension") - } - - val server = getServer() - val saver = getUseDataSaver() - val newUrl = API_URL + chapter.url.replace(OLD_API_CHAPTER, NEW_API_CHAPTER) - return GET( - "$newUrl?server=$server&saver=$saver", - headers, - CacheControl.FORCE_NETWORK - ) - } - - override fun pageListParse(document: Document) = throw Exception("Not used") - - override fun pageListParse(response: Response): List { - val jsonData = response.body()!!.string() - val json = JsonParser().parse(jsonData).asJsonObject["data"] - - val hash = json["hash"].string - val server = json["server"].string - - return json["pages"].asJsonArray.mapIndexed { idx, it -> - val url = "$hash/${it.asString}" - val mdAtHomeMetadataUrl = "$server,${response.request().url()},${Date().time}" - Page(idx, mdAtHomeMetadataUrl, url) - } - } - - override fun imageRequest(page: Page): Request { - val url = when { - // Legacy - page.url.isEmpty() -> page.imageUrl!! - // Some images are hosted elsewhere - !page.url.startsWith("http") -> baseUrl + page.url.substringBefore(",") + page.imageUrl - // New chapters on MD servers - page.url.contains("https://mangadex.org/data") -> page.url.substringBefore(",") + page.imageUrl - // MD@Home token handling - else -> { - val tokenLifespan = 5 * 60 * 1000 - val data = page.url.split(",") - var tokenedServer = data[0] - if (Date().time - data[2].toLong() > tokenLifespan) { - val tokenRequestUrl = data[1] - val cacheControl = - if (Date().time - (tokenTracker[tokenRequestUrl] ?: 0) > tokenLifespan) { - tokenTracker[tokenRequestUrl] = Date().time - CacheControl.FORCE_NETWORK - } else { - CacheControl.FORCE_CACHE - } - val jsonData = - client.newCall(GET(tokenRequestUrl, headers, cacheControl)).execute() - .body()!!.string() - tokenedServer = - JsonParser().parse(jsonData).asJsonObject["data"]["server"].string - } - tokenedServer + page.imageUrl - } - } - - return GET(url, headers) - } - - // chapter url where we get the token, last request time - private val tokenTracker = hashMapOf() - - override fun imageUrlParse(document: Document): String = "" - - private fun parseStatus(status: Int) = when (status) { - 1 -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val r18Pref = androidx.preference.ListPreference(screen.context).apply { - key = SHOW_R18_PREF_Title - title = SHOW_R18_PREF_Title - - title = SHOW_R18_PREF_Title - entries = arrayOf("Show No R18+", "Show All", "Show Only R18+") - entryValues = arrayOf("0", "1", "2") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - preferences.edit().putInt(SHOW_R18_PREF, index).commit() - } - } - val thumbsPref = androidx.preference.ListPreference(screen.context).apply { - key = SHOW_THUMBNAIL_PREF_Title - title = SHOW_THUMBNAIL_PREF_Title - entries = arrayOf("Show high quality", "Show low quality") - entryValues = arrayOf("0", "1") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - preferences.edit().putInt(SHOW_THUMBNAIL_PREF, index).commit() - } - } - val serverPref = androidx.preference.ListPreference(screen.context).apply { - key = SERVER_PREF_Title - title = SERVER_PREF_Title - entries = SERVER_PREF_ENTRIES - entryValues = SERVER_PREF_ENTRY_VALUES - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SERVER_PREF, entry).commit() - } - } - val dataSaverPref = androidx.preference.ListPreference(screen.context).apply { - key = DATA_SAVER_PREF_Title - title = DATA_SAVER_PREF_Title - entries = arrayOf("Disable", "Enable") - entryValues = arrayOf("0", "1") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - preferences.edit().putInt(DATA_SAVER_PREF, index).commit() - } - } - - screen.addPreference(r18Pref) - screen.addPreference(thumbsPref) - screen.addPreference(serverPref) - screen.addPreference(dataSaverPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val r18Pref = ListPreference(screen.context).apply { - key = SHOW_R18_PREF_Title - title = SHOW_R18_PREF_Title - - title = SHOW_R18_PREF_Title - entries = arrayOf("Show No R18+", "Show All", "Show Only R18+") - entryValues = arrayOf("0", "1", "2") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - preferences.edit().putInt(SHOW_R18_PREF, index).commit() - } - } - val thumbsPref = ListPreference(screen.context).apply { - key = SHOW_THUMBNAIL_PREF_Title - title = SHOW_THUMBNAIL_PREF_Title - entries = arrayOf("Show high quality", "Show low quality") - entryValues = arrayOf("0", "1") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - preferences.edit().putInt(SHOW_THUMBNAIL_PREF, index).commit() - } - } - val serverPref = ListPreference(screen.context).apply { - key = SERVER_PREF_Title - title = SERVER_PREF_Title - entries = SERVER_PREF_ENTRIES - entryValues = SERVER_PREF_ENTRY_VALUES - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SERVER_PREF, entry).commit() - } - } - val dataSaverPref = ListPreference(screen.context).apply { - key = DATA_SAVER_PREF_Title - title = DATA_SAVER_PREF_Title - entries = arrayOf("Disable", "Enable") - entryValues = arrayOf("0", "1") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - preferences.edit().putInt(DATA_SAVER_PREF, index).commit() - } - } - - screen.addPreference(r18Pref) - screen.addPreference(thumbsPref) - screen.addPreference(serverPref) - screen.addPreference(dataSaverPref) - } - - private fun getShowR18(): Int = preferences.getInt(SHOW_R18_PREF, 0) - private fun getShowThumbnail(): Int = preferences.getInt(SHOW_THUMBNAIL_PREF, 0) - private fun getServer(): String { - val default = SERVER_PREF_ENTRY_VALUES.first() - return preferences.getString(SERVER_PREF, default).takeIf { it in SERVER_PREF_ENTRY_VALUES } - ?: default - } - - private fun getUseDataSaver(): Int = preferences.getInt(DATA_SAVER_PREF, 0) - - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Tag(val id: String, name: String) : Filter.TriState(name) - private class Demographic(demographics: List) : - Filter.Group("Demographic", demographics) - - private class PublicationStatus(publications: List) : - Filter.Group("Publication", publications) - - private class ContentList(contents: List) : Filter.Group("Content", contents) - private class FormatList(formats: List) : Filter.Group("Format", formats) - private class GenreList(genres: List) : Filter.Group("Genres", genres) - private class R18 : - Filter.Select("R18+", arrayOf("Default", "Show all", "Show only", "Show none")) - - private class ScanGroup(name: String) : Filter.Text(name) - - private fun getDemographic() = listOf( - Tag("1", "Shounen"), - Tag("2", "Shoujo"), - Tag("3", "Seinen"), - Tag("4", "Josei") - ).sortedWith(compareBy { it.name }) - - private fun getPublicationStatus() = listOf( - Tag("1", "Ongoing"), - Tag("2", "Completed"), - Tag("3", "Cancelled"), - Tag("4", "Hiatus") - ).sortedWith(compareBy { it.name }) - - private class ThemeList(themes: List) : Filter.Group("Themes", themes) - private class TagInclusionMode : - Filter.Select("Tag inclusion mode", arrayOf("All (and)", "Any (or)"), 0) - - private class TagExclusionMode : - Filter.Select("Tag exclusion mode", arrayOf("All (and)", "Any (or)"), 1) - - // default selection (Rating Descending) matches popularMangaRequest url - class SortFilter : Filter.Sort( - "Sort", - sortables.map { it.first }.toTypedArray(), - Selection(3, false) - ) - - private class OriginalLanguage : - Filter.Select("Original Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray()) - - override fun getFilterList() = FilterList( - TextField("Author", "author"), - TextField("Artist", "artist"), - R18(), - SortFilter(), - Demographic(getDemographic()), - PublicationStatus(getPublicationStatus()), - OriginalLanguage(), - ContentList(getContentList()), - FormatList(getFormatList()), - GenreList(getGenreList()), - ThemeList(getThemeList()), - TagInclusionMode(), - TagExclusionMode(), - Filter.Separator(), - Filter.Header("Group search ignores other inputs"), - ScanGroup("Search for manga by scanlator group") - ) - - private fun getContentList() = listOf( - Tag("9", "Ecchi"), - Tag("32", "Smut"), - Tag("49", "Gore"), - Tag("50", "Sexual Violence") - ).sortedWith(compareBy { it.name }) - - private fun getFormatList() = listOf( - Tag("1", "4-koma"), - Tag("4", "Award Winning"), - Tag("7", "Doujinshi"), - Tag("21", "Oneshot"), - Tag("36", "Long Strip"), - Tag("42", "Adaptation"), - Tag("43", "Anthology"), - Tag("44", "Web Comic"), - Tag("45", "Full Color"), - Tag("46", "User Created"), - Tag("47", "Official Colored"), - Tag("48", "Fan Colored") - ).sortedWith(compareBy { it.name }) - - private fun getGenreList() = listOf( - Tag("2", "Action"), - Tag("3", "Adventure"), - Tag("5", "Comedy"), - Tag("8", "Drama"), - Tag("10", "Fantasy"), - Tag("13", "Historical"), - Tag("14", "Horror"), - Tag("17", "Mecha"), - Tag("18", "Medical"), - Tag("20", "Mystery"), - Tag("22", "Psychological"), - Tag("23", "Romance"), - Tag("25", "Sci-Fi"), - Tag("28", "Shoujo Ai"), - Tag("30", "Shounen Ai"), - Tag("31", "Slice of Life"), - Tag("33", "Sports"), - Tag("35", "Tragedy"), - Tag("37", "Yaoi"), - Tag("38", "Yuri"), - Tag("41", "Isekai"), - Tag("51", "Crime"), - Tag("52", "Magical Girls"), - Tag("53", "Philosophical"), - Tag("54", "Superhero"), - Tag("55", "Thriller"), - Tag("56", "Wuxia") - ).sortedWith(compareBy { it.name }) - - private fun getThemeList() = listOf( - Tag("6", "Cooking"), - Tag("11", "Gyaru"), - Tag("12", "Harem"), - Tag("16", "Martial Arts"), - Tag("19", "Music"), - Tag("24", "School Life"), - Tag("34", "Supernatural"), - Tag("40", "Video Games"), - Tag("57", "Aliens"), - Tag("58", "Animals"), - Tag("59", "Crossdressing"), - Tag("60", "Demons"), - Tag("61", "Delinquents"), - Tag("62", "Genderswap"), - Tag("63", "Ghosts"), - Tag("64", "Monster Girls"), - Tag("65", "Loli"), - Tag("66", "Magic"), - Tag("67", "Military"), - Tag("68", "Monsters"), - Tag("69", "Ninja"), - Tag("70", "Office Workers"), - Tag("71", "Police"), - Tag("72", "Post-Apocalyptic"), - Tag("73", "Reincarnation"), - Tag("74", "Reverse Harem"), - Tag("75", "Samurai"), - Tag("76", "Shota"), - Tag("77", "Survival"), - Tag("78", "Time Travel"), - Tag("79", "Vampires"), - Tag("80", "Traditional Games"), - Tag("81", "Virtual Reality"), - Tag("82", "Zombies"), - Tag("83", "Incest"), - Tag("84", "Mafia"), - Tag("85", "Villainess") - ).sortedWith(compareBy { it.name }) - - private val GENRES = - (getContentList() + getFormatList() + getGenreList() + getThemeList()).map { it.id to it.name } - .toMap() - - companion object { - private val WHITESPACE_REGEX = "\\s".toRegex() - - // This number matches to the cookie - private const val NO_R18 = 0 - private const val ALL = 1 - private const val ONLY_R18 = 2 - - private const val SHOW_R18_PREF_Title = "Default R18 Setting" - private const val SHOW_R18_PREF = "showR18Default" - - private const val LOW_QUALITY = 1 - - private const val SHOW_THUMBNAIL_PREF_Title = "Default thumbnail quality" - private const val SHOW_THUMBNAIL_PREF = "showThumbnailDefault" - - private const val SERVER_PREF_Title = "Image server" - private const val SERVER_PREF = "imageServer" - private val SERVER_PREF_ENTRIES = - arrayOf("Automatic", "NA/EU 1", "NA/EU 2", "Rest of the world") - private val SERVER_PREF_ENTRY_VALUES = arrayOf("0", "na", "na2", "row") - - private const val DATA_SAVER_PREF_Title = "Data saver" - private const val DATA_SAVER_PREF = "dataSaver" - - private const val API_URL = "https://api.mangadex.org" - private const val API_MANGA = "/v2/manga/" - private const val API_MANGA_INCLUDE_CHAPTERS = "?include=chapters" - private const val OLD_API_CHAPTER = "/api/chapter/" - private const val NEW_API_CHAPTER = "/v2/chapter/" - - const val PREFIX_ID_SEARCH = "id:" - - private val sortables = listOf( - Triple("Update date", 0, 1), - Triple("Alphabetically", 2, 3), - Triple("Number of comments", 4, 5), - Triple("Rating", 6, 7), - Triple("Views", 8, 9), - Triple("Follows", 10, 11) - ) - - private val SOURCE_LANG_LIST = listOf( - Pair("All", "0"), - Pair("Japanese", "2"), - Pair("English", "1"), - Pair("Polish", "3"), - Pair("German", "8"), - Pair("French", "10"), - Pair("Vietnamese", "12"), - Pair("Chinese", "21"), - Pair("Indonesian", "27"), - Pair("Korean", "28"), - Pair("Spanish (LATAM)", "29"), - Pair("Thai", "32"), - Pair("Filipino", "34") - ) - } -} - -class CoverInterceptor : Interceptor { - private val coverRegex = Regex("""/images/.*\.jpg""") - - override fun intercept(chain: Interceptor.Chain): Response { - val originalRequest = chain.request() - - return chain.proceed(chain.request()).let { response -> - if (response.code() == 404 && originalRequest.url().toString().contains(coverRegex)) { - response.close() - chain.proceed( - originalRequest.newBuilder().url( - originalRequest.url().toString().substringBeforeLast(".") + ".thumb.jpg" - ).build() - ) - } else { - response - } - } - } -} - -class MdRateLimitInterceptor : Interceptor { - private val coverRegex = Regex("""/images/.*\.jpg""") - private val baseInterceptor = RateLimitInterceptor(2) - - override fun intercept(chain: Interceptor.Chain): Response = - if (chain.request().url().toString().contains(coverRegex)) - chain.proceed(chain.request()) - else - baseInterceptor.intercept(chain) -} - -class MdAtHomeReportInterceptor( - private val client: OkHttpClient, - private val headers: Headers -) : Interceptor { - - private val gson: Gson by lazy { Gson() } - private val mdAtHomeUrlRegex = Regex("""^https://[\w\d]+\.[\w\d]+\.mangadex\.network.*${'$'}""") - - override fun intercept(chain: Interceptor.Chain): Response { - val originalRequest = chain.request() - - return chain.proceed(chain.request()).let { response -> - val url = originalRequest.url().toString() - if (url.contains(mdAtHomeUrlRegex)) { - val jsonString = gson.toJson( - mapOf( - "url" to url, - "success" to response.isSuccessful, - "bytes" to response.peekBody(Long.MAX_VALUE).bytes().size - ) - ) - - val postResult = client.newCall( - POST( - "https://api.mangadex.network/report", - headers, - RequestBody.create(null, jsonString) - ) - ) - try { - postResult.execute() - } catch (e: Exception) { - Log.e("MangaDex", "Error trying to POST report to MD@Home: ${e.message}") - } - } - - response - } - } -} diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFactory.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFactory.kt deleted file mode 100644 index 0967ca916..000000000 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangaDexFactory.kt +++ /dev/null @@ -1,95 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangadex - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class MangaDexFactory : SourceFactory { - override fun createSources(): List = listOf( - MangaDexEnglish(), - MangaDexJapanese(), - MangaDexPolish(), - MangaDexSerboCroatian(), - MangaDexDutch(), - MangaDexItalian(), - MangaDexRussian(), - MangaDexGerman(), - MangaDexHungarian(), - MangaDexFrench(), - MangaDexFinnish(), - MangaDexVietnamese(), - MangaDexGreek(), - MangaDexBulgarian(), - MangaDexSpanishSpain(), - MangaDexPortugueseBrazil(), - MangaDexPortuguesePortugal(), - MangaDexSwedish(), - MangaDexArabic(), - MangaDexDanish(), - MangaDexChineseSimp(), - MangaDexBengali(), - MangaDexRomanian(), - MangaDexCzech(), - MangaDexMongolian(), - MangaDexTurkish(), - MangaDexIndonesian(), - MangaDexKorean(), - MangaDexSpanishLTAM(), - MangaDexPersian(), - MangaDexMalay(), - MangaDexThai(), - MangaDexCatalan(), - MangaDexFilipino(), - MangaDexChineseTrad(), - MangaDexUkrainian(), - MangaDexBurmese(), - MangaDexLithuanian(), - MangaDexHebrew(), - MangaDexHindi(), - MangaDexNorwegian(), - MangaDexOther() - ) -} -class MangaDexEnglish : MangaDex("en", "gb") -class MangaDexJapanese : MangaDex("ja", "jp") -class MangaDexPolish : MangaDex("pl", "pl") -class MangaDexSerboCroatian : MangaDex("sh", "rs") -class MangaDexDutch : MangaDex("nl", "nl") -class MangaDexItalian : MangaDex("it", "it") -class MangaDexRussian : MangaDex("ru", "ru") -class MangaDexGerman : MangaDex("de", "de") -class MangaDexHungarian : MangaDex("hu", "hu") -class MangaDexFrench : MangaDex("fr", "fr") -class MangaDexFinnish : MangaDex("fi", "fi") -class MangaDexVietnamese : MangaDex("vi", "vn") -class MangaDexGreek : MangaDex("el", "gr") -class MangaDexBulgarian : MangaDex("bg", "bg") -class MangaDexSpanishSpain : MangaDex("es", "es") -class MangaDexPortugueseBrazil : MangaDex("pt-BR", "br") -class MangaDexPortuguesePortugal : MangaDex("pt", "pt") -class MangaDexSwedish : MangaDex("sv", "se") -class MangaDexArabic : MangaDex("ar", "sa") -class MangaDexDanish : MangaDex("da", "dk") -class MangaDexChineseSimp : MangaDex("zh-Hans", "cn") -class MangaDexBengali : MangaDex("bn", "bd") -class MangaDexRomanian : MangaDex("ro", "ro") -class MangaDexCzech : MangaDex("cs", "cz") -class MangaDexMongolian : MangaDex("mn", "mn") -class MangaDexTurkish : MangaDex("tr", "tr") -class MangaDexIndonesian : MangaDex("id", "id") -class MangaDexKorean : MangaDex("ko", "kr") -class MangaDexSpanishLTAM : MangaDex("es-419", "mx") -class MangaDexPersian : MangaDex("fa", "ir") -class MangaDexMalay : MangaDex("ms", "my") -class MangaDexThai : MangaDex("th", "th") -class MangaDexCatalan : MangaDex("ca", "ct") -class MangaDexFilipino : MangaDex("fil", "ph") -class MangaDexChineseTrad : MangaDex("zh-Hant", "hk") -class MangaDexUkrainian : MangaDex("uk", "ua") -class MangaDexBurmese : MangaDex("my", "mm") -class MangaDexLithuanian : MangaDex("lt", "il") -class MangaDexHebrew : MangaDex("he", "il") -class MangaDexHindi : MangaDex("hi", "in") -class MangaDexNorwegian : MangaDex("no", "no") -class MangaDexOther : MangaDex("other", " ") diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexDescription.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexDescription.kt deleted file mode 100644 index 2ce0a8e6c..000000000 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexDescription.kt +++ /dev/null @@ -1,54 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangadex - -class MangadexDescription(internalLang: String) { - - private val listOfLangs = when (internalLang) { - "ru" -> RUSSIAN - "de" -> GERMAN - "it" -> ITALIAN - in "es", "mx" -> SPANISH - in "br", "pt" -> PORTUGESE - "tr" -> TURKISH - "fr" -> FRENCH - "sa" -> ARABIC - else -> emptyList() - } - - fun clean(description: String): String { - val langList = ALL_LANGS.toMutableList() - - // remove any languages before the ones provided in the langTextToCheck, if no matches or empty - // just uses the original description, also removes the potential lang from all lang list - var newDescription = description - listOfLangs.forEach { - newDescription = newDescription.substringAfter(it) - langList.remove(it) - } - - // remove any possible languages that remain to get the new description - langList.forEach { newDescription = newDescription.substringBefore(it) } - return newDescription - } - - companion object { - val ARABIC = listOf("[b][u]Arabic / العربية[/u][/b]") - val FRENCH = listOf( - "French - Français:", - "[b][u]French[/u][/b]", - "[b][u]French / Français[/u][/b]" - ) - val GERMAN = listOf("[b][u]German / Deutsch[/u][/b]", "German/Deutsch:") - val ITALIAN = listOf("[b][u]Italian / Italiano[/u][/b]") - val PORTUGESE = listOf( - "[b][u]Portuguese (BR) / Português (BR)[/u][/b]", - "[b][u]Português / Portuguese[/u][/b]", - "[b][u]Portuguese / Portugu[/u][/b]" - ) - val RUSSIAN = listOf("[b][u]Russian / Русский[/u][/b]") - val SPANISH = listOf("[b][u]Español / Spanish:[/u][/b]") - val TURKISH = listOf("[b][u]Turkish / Türkçe[/u][/b]") - - val ALL_LANGS = - listOf(ARABIC, FRENCH, GERMAN, ITALIAN, PORTUGESE, RUSSIAN, SPANISH, TURKISH).flatten() - } -} diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexUrlActivity.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexUrlActivity.kt deleted file mode 100644 index 5db4b10b1..000000000 --- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/MangadexUrlActivity.kt +++ /dev/null @@ -1,44 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangadex - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://mangadex.com/title/xxx intents and redirects them to - * the main tachiyomi process. The idea is to not install the intent filter unless - * you have this extension installed, but still let the main tachiyomi app control - * things. - * - * Main goal was to make it easier to open manga in Tachiyomi in spite of the DDoS blocking - * the usual search screen from working. - */ -class MangadexUrlActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val titleid = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${MangaDex.PREFIX_ID_SEARCH}$titleid") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("MangadexUrlActivity", e.toString()) - } - } else { - Log.e("MangadexUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/all/mangaplus/AndroidManifest.xml b/src/all/mangaplus/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/mangaplus/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/mangaplus/build.gradle b/src/all/mangaplus/build.gradle deleted file mode 100644 index b35174d9b..000000000 --- a/src/all/mangaplus/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'MANGA Plus by SHUEISHA' - pkgNameSuffix = 'all.mangaplus' - extClass = '.MangaPlusFactory' - extVersionCode = 16 - libVersion = '1.2' -} - -dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.1' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/mangaplus/res/mipmap-hdpi/ic_launcher.png b/src/all/mangaplus/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b45b3b731..000000000 Binary files a/src/all/mangaplus/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangaplus/res/mipmap-mdpi/ic_launcher.png b/src/all/mangaplus/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c5f3e011c..000000000 Binary files a/src/all/mangaplus/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangaplus/res/mipmap-xhdpi/ic_launcher.png b/src/all/mangaplus/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9d015bbd7..000000000 Binary files a/src/all/mangaplus/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangaplus/res/mipmap-xxhdpi/ic_launcher.png b/src/all/mangaplus/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7531908d2..000000000 Binary files a/src/all/mangaplus/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangaplus/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/mangaplus/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 601ff6a62..000000000 Binary files a/src/all/mangaplus/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangaplus/res/web_hi_res_512.png b/src/all/mangaplus/res/web_hi_res_512.png deleted file mode 100644 index 8382e05ea..000000000 Binary files a/src/all/mangaplus/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt deleted file mode 100644 index 5ecbe83bd..000000000 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlus.kt +++ /dev/null @@ -1,504 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangaplus - -import android.app.Application -import android.content.SharedPreferences -import android.os.Build -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import com.google.gson.Gson -import com.squareup.duktape.Duktape -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import kotlinx.serialization.protobuf.ProtoBuf -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.UUID -import androidx.preference.CheckBoxPreference as AndroidXCheckBoxPreference -import androidx.preference.ListPreference as AndroidXListPreference -import androidx.preference.PreferenceScreen as AndroidXPreferenceScreen - -abstract class MangaPlus( - override val lang: String, - private val internalLang: String, - private val langCode: Language -) : HttpSource(), ConfigurableSource { - - override val name = "MANGA Plus by SHUEISHA" - - override val baseUrl = "https://mangaplus.shueisha.co.jp" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Origin", baseUrl) - .add("Referer", baseUrl) - .add("User-Agent", USER_AGENT) - .add("Session-Token", UUID.randomUUID().toString()) - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor { imageIntercept(it) } - .addInterceptor { thumbnailIntercept(it) } - .build() - - private val protobufJs: String by lazy { - val request = GET(PROTOBUFJS_CDN, headers) - client.newCall(request).execute().body()!!.string() - } - - private val gson: Gson by lazy { Gson() } - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - private val imageResolution: String - get() = preferences.getString("${RESOLUTION_PREF_KEY}_$lang", RESOLUTION_PREF_DEFAULT_VALUE)!! - - private val splitImages: String - get() = if (preferences.getBoolean("${SPLIT_PREF_KEY}_$lang", SPLIT_PREF_DEFAULT_VALUE)) "yes" else "no" - - private var titleList: List? = null - - /** - * MANGA Plus recently started supporting other languages, but - * they are not defined by the API. This is a temporary fix - * to properly filter the titles while their API doesn't get an update. - */ - private val titlesToFix: Map<Int, Language> = mapOf( - // Thai - 100079 to Language.THAI, - 100080 to Language.THAI, - 100082 to Language.THAI, - 100120 to Language.THAI, - 100121 to Language.THAI, - - // Brazilian Portuguese - 100149 to Language.PORTUGUESE_BR, - 100150 to Language.PORTUGUESE_BR, - 100151 to Language.PORTUGUESE_BR - ) - - override fun popularMangaRequest(page: Int): Request { - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/manga_list/hot") - .build() - - return GET("$API_URL/title_list/ranking", newHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = response.asProto() - - if (result.success == null) - throw Exception(result.error!!.langPopup.body) - - titleList = result.success.titleRankingView!!.titles - .fixWrongLanguages() - .filter { it.language == langCode } - - val mangas = titleList!!.map { - SManga.create().apply { - title = it.name - thumbnail_url = it.portraitImageUrl - url = "#/titles/${it.titleId}" - } - } - - return MangasPage(mangas, false) - } - - override fun latestUpdatesRequest(page: Int): Request { - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/updates") - .build() - - return GET("$API_URL/web/web_home?lang=$internalLang", newHeaders) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.asProto() - - if (result.success == null) - throw Exception(result.error!!.langPopup.body) - - // Fetch all titles to get newer thumbnail urls at the interceptor. - val popularResponse = client.newCall(popularMangaRequest(1)).execute().asProto() - - if (popularResponse.success != null) { - titleList = popularResponse.success.titleRankingView!!.titles - .fixWrongLanguages() - .filter { it.language == langCode } - } - - val mangas = result.success.webHomeView!!.groups - .flatMap { it.titles } - .mapNotNull { it.title } - .fixWrongLanguages() - .filter { it.language == langCode } - .map { - SManga.create().apply { - title = it.name - thumbnail_url = it.portraitImageUrl - url = "#/titles/${it.titleId}" - } - } - .distinctBy { it.title } - - return MangasPage(mangas, false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return super.fetchSearchManga(page, query, filters) - .map { MangasPage(it.mangas.filter { m -> m.title.contains(query, true) }, it.hasNextPage) } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/manga_list/all") - .build() - - return GET("$API_URL/title_list/all", newHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = response.asProto() - - if (result.success == null) - throw Exception(result.error!!.langPopup.body) - - titleList = result.success.allTitlesView!!.titles - .fixWrongLanguages() - .filter { it.language == langCode } - - val mangas = titleList!!.map { - SManga.create().apply { - title = it.name - thumbnail_url = it.portraitImageUrl - url = "#/titles/${it.titleId}" - } - } - - return MangasPage(mangas, false) - } - - private fun titleDetailsRequest(manga: SManga): Request { - val titleId = manga.url.substringAfterLast("/") - - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/titles/$titleId") - .build() - - return GET("$API_URL/title_detail?title_id=$titleId", newHeaders) - } - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(titleDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - override fun mangaDetailsRequest(manga: SManga): Request { - // Remove the '#' and map to the new url format used in website. - return GET(baseUrl + manga.url.substring(1), headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val result = response.asProto() - - if (result.success == null) - throw Exception(result.error!!.langPopup.body) - - val details = result.success.titleDetailView!! - val title = details.title - val isCompleted = details.nonAppearanceInfo.contains(COMPLETE_REGEX) - - return SManga.create().apply { - author = title.author.replace(" / ", ", ") - artist = author - description = details.overview + "\n\n" + details.viewingPeriodDescription - status = if (isCompleted) SManga.COMPLETED else SManga.ONGOING - thumbnail_url = title.portraitImageUrl - } - } - - override fun chapterListRequest(manga: SManga): Request = titleDetailsRequest(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val result = response.asProto() - - if (result.success == null) - throw Exception(result.error!!.langPopup.body) - - val titleDetailView = result.success.titleDetailView!! - - val chapters = titleDetailView.firstChapterList + titleDetailView.lastChapterList - - return chapters.reversed() - // If the subTitle is null, then the chapter time expired. - .filter { it.subTitle != null } - .map { - SChapter.create().apply { - name = "${it.name} - ${it.subTitle}" - scanlator = "Shueisha" - date_upload = 1000L * it.startTimeStamp - url = "#/viewer/${it.chapterId}" - chapter_number = it.name.substringAfter("#").toFloatOrNull() ?: -1f - } - } - } - - override fun pageListRequest(chapter: SChapter): Request { - val chapterId = chapter.url.substringAfterLast("/") - - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/viewer/$chapterId") - .build() - - val url = HttpUrl.parse("$API_URL/manga_viewer")!!.newBuilder() - .addQueryParameter("chapter_id", chapterId) - .addQueryParameter("split", splitImages) - .addQueryParameter("img_quality", imageResolution) - .toString() - - return GET(url, newHeaders) - } - - override fun pageListParse(response: Response): List<Page> { - val result = response.asProto() - - if (result.success == null) - throw Exception(result.error!!.langPopup.body) - - val referer = response.request().header("Referer")!! - - return result.success.mangaViewer!!.pages - .mapNotNull { it.page } - .mapIndexed { i, page -> - val encryptionKey = if (page.encryptionKey == null) "" else "&encryptionKey=${page.encryptionKey}" - Page(i, referer, "${page.imageUrl}$encryptionKey") - } - } - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .removeAll("Origin") - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - override fun setupPreferenceScreen(screen: AndroidXPreferenceScreen) { - val resolutionPref = AndroidXListPreference(screen.context).apply { - key = "${RESOLUTION_PREF_KEY}_$lang" - title = RESOLUTION_PREF_TITLE - entries = RESOLUTION_PREF_ENTRIES - entryValues = RESOLUTION_PREF_ENTRY_VALUES - setDefaultValue(RESOLUTION_PREF_DEFAULT_VALUE) - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString("${RESOLUTION_PREF_KEY}_$lang", entry).commit() - } - } - val splitPref = AndroidXCheckBoxPreference(screen.context).apply { - key = "${SPLIT_PREF_KEY}_$lang" - title = SPLIT_PREF_TITLE - summary = SPLIT_PREF_SUMMARY - setDefaultValue(SPLIT_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${SPLIT_PREF_KEY}_$lang", checkValue).commit() - } - } - - screen.addPreference(resolutionPref) - screen.addPreference(splitPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val resolutionPref = ListPreference(screen.context).apply { - key = "${RESOLUTION_PREF_KEY}_$lang" - title = RESOLUTION_PREF_TITLE - entries = RESOLUTION_PREF_ENTRIES - entryValues = RESOLUTION_PREF_ENTRY_VALUES - setDefaultValue(RESOLUTION_PREF_DEFAULT_VALUE) - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString("${RESOLUTION_PREF_KEY}_$lang", entry).commit() - } - } - val splitPref = CheckBoxPreference(screen.context).apply { - key = "${SPLIT_PREF_KEY}_$lang" - title = SPLIT_PREF_TITLE - summary = SPLIT_PREF_SUMMARY - setDefaultValue(SPLIT_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("${SPLIT_PREF_KEY}_$lang", checkValue).commit() - } - } - - screen.addPreference(resolutionPref) - screen.addPreference(splitPref) - } - - private fun imageIntercept(chain: Interceptor.Chain): Response { - var request = chain.request() - - if (request.url().queryParameter("encryptionKey") == null) - return chain.proceed(request) - - val encryptionKey = request.url().queryParameter("encryptionKey")!! - - // Change the url and remove the encryptionKey to avoid detection. - val newUrl = request.url().newBuilder() - .removeAllQueryParameters("encryptionKey") - .build() - request = request.newBuilder() - .url(newUrl) - .build() - - val response = chain.proceed(request) - - val contentType = response.header("Content-Type", "image/jpeg")!! - val image = decodeImage(encryptionKey, response.body()!!.bytes()) - val body = ResponseBody.create(MediaType.parse(contentType), image) - - return response.newBuilder() - .body(body) - .build() - } - - private fun decodeImage(encryptionKey: String, image: ByteArray): ByteArray { - val keyStream = HEX_GROUP - .findAll(encryptionKey) - .toList() - .map { it.groupValues[1].toInt(16) } - - val content = image - .map { it.toInt() } - .toMutableList() - - val blockSizeInBytes = keyStream.size - - for ((i, value) in content.iterator().withIndex()) { - content[i] = value xor keyStream[i % blockSizeInBytes] - } - - return ByteArray(content.size) { pos -> content[pos].toByte() } - } - - private fun thumbnailIntercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val response = chain.proceed(request) - - // Check if it is 404 to maintain compatibility when the extension used Weserv. - val isBadCode = (response.code() == 401 || response.code() == 404) - - if (isBadCode && request.url().toString().contains(TITLE_THUMBNAIL_PATH)) { - val titleId = request.url().toString() - .substringBefore("/$TITLE_THUMBNAIL_PATH") - .substringAfterLast("/") - .toInt() - val title = titleList?.find { it.titleId == titleId } ?: return response - - response.close() - val thumbnailRequest = GET(title.portraitImageUrl, request.headers()) - return chain.proceed(thumbnailRequest) - } - - return response - } - - private fun List<Title>.fixWrongLanguages(): List<Title> = map { title -> - val correctLanguage = titlesToFix[title.titleId] - if (correctLanguage != null) title.copy(language = correctLanguage) else title - } - - private val ErrorResult.langPopup: Popup - get() = when (internalLang) { - "esp" -> spanishPopup - else -> englishPopup - } - - private fun Response.asProto(): MangaPlusResponse { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) - return ProtoBuf.decodeFromByteArray(MangaPlusSerializer, body()!!.bytes()) - - // The kotlinx.serialization library eventually always have some issues with - // devices with Android version below Nougat. So, if the device is running Marshmallow - // or lower, the deserialization is done using ProtobufJS + Duktape + Gson. - - val bytes = body()!!.bytes() - val messageBytes = "var BYTE_ARR = new Uint8Array([${bytes.joinToString()}]);" - - val res = Duktape.create().use { - // The current Kotlin version brokes Duktape's module feature, - // so we need to provide an workaround to prevent the usage of 'require'. - it.evaluate("var module = { exports: true };") - it.evaluate(protobufJs) - it.evaluate(messageBytes + DECODE_SCRIPT) as String - } - - return gson.fromJson(res, MangaPlusResponse::class.java) - } - - companion object { - private const val API_URL = "https://jumpg-webapi.tokyo-cdn.com/api" - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" - - private val HEX_GROUP = "(.{1,2})".toRegex() - - private const val PROTOBUFJS_CDN = "https://cdn.jsdelivr.net/npm/protobufjs@6.10.1/dist/light/protobuf.js" - - private const val RESOLUTION_PREF_KEY = "imageResolution" - private const val RESOLUTION_PREF_TITLE = "Image resolution" - private val RESOLUTION_PREF_ENTRIES = arrayOf("Low resolution", "Medium resolution", "High resolution") - private val RESOLUTION_PREF_ENTRY_VALUES = arrayOf("low", "high", "super_high") - private val RESOLUTION_PREF_DEFAULT_VALUE = RESOLUTION_PREF_ENTRY_VALUES[2] - - private const val SPLIT_PREF_KEY = "splitImage" - private const val SPLIT_PREF_TITLE = "Split double pages" - private const val SPLIT_PREF_SUMMARY = "Not all titles support disabling this." - private const val SPLIT_PREF_DEFAULT_VALUE = true - - private val COMPLETE_REGEX = "completado|complete".toRegex() - - private const val TITLE_THUMBNAIL_PATH = "title_thumbnail_portrait_list" - } -} diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusApi.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusApi.kt deleted file mode 100644 index 1586ea828..000000000 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusApi.kt +++ /dev/null @@ -1,288 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangaplus - -import com.google.gson.annotations.SerializedName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Serializer -import kotlinx.serialization.protobuf.ProtoNumber - -@Serializer(forClass = MangaPlusResponse::class) -object MangaPlusSerializer - -@Serializable -data class MangaPlusResponse( - @ProtoNumber(1) val success: SuccessResult? = null, - @ProtoNumber(2) val error: ErrorResult? = null -) - -@Serializable -data class ErrorResult( - @ProtoNumber(1) val action: Action, - @ProtoNumber(2) val englishPopup: Popup, - @ProtoNumber(3) val spanishPopup: Popup -) - -enum class Action { DEFAULT, UNAUTHORIZED, MAINTAINENCE, GEOIP_BLOCKING } - -@Serializable -data class Popup( - @ProtoNumber(1) val subject: String, - @ProtoNumber(2) val body: String -) - -@Serializable -data class SuccessResult( - @ProtoNumber(1) val isFeaturedUpdated: Boolean? = false, - @ProtoNumber(5) val allTitlesView: AllTitlesView? = null, - @ProtoNumber(6) val titleRankingView: TitleRankingView? = null, - @ProtoNumber(8) val titleDetailView: TitleDetailView? = null, - @ProtoNumber(10) val mangaViewer: MangaViewer? = null, - @ProtoNumber(11) val webHomeView: WebHomeView? = null -) - -@Serializable -data class TitleRankingView(@ProtoNumber(1) val titles: List<Title> = emptyList()) - -@Serializable -data class AllTitlesView(@ProtoNumber(1) val titles: List<Title> = emptyList()) - -@Serializable -data class WebHomeView(@ProtoNumber(2) val groups: List<UpdatedTitleGroup> = emptyList()) - -@Serializable -data class TitleDetailView( - @ProtoNumber(1) val title: Title, - @ProtoNumber(2) val titleImageUrl: String, - @ProtoNumber(3) val overview: String, - @ProtoNumber(4) val backgroundImageUrl: String, - @ProtoNumber(5) val nextTimeStamp: Int = 0, - @ProtoNumber(6) val updateTiming: UpdateTiming? = UpdateTiming.DAY, - @ProtoNumber(7) val viewingPeriodDescription: String = "", - @ProtoNumber(8) val nonAppearanceInfo: String = "", - @ProtoNumber(9) val firstChapterList: List<Chapter> = emptyList(), - @ProtoNumber(10) val lastChapterList: List<Chapter> = emptyList(), - @ProtoNumber(14) val isSimulReleased: Boolean = true, - @ProtoNumber(17) val chaptersDescending: Boolean = true -) - -enum class UpdateTiming { NOT_REGULARLY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, DAY } - -@Serializable -data class MangaViewer(@ProtoNumber(1) val pages: List<MangaPlusPage> = emptyList()) - -@Serializable -data class Title( - @ProtoNumber(1) val titleId: Int, - @ProtoNumber(2) val name: String, - @ProtoNumber(3) val author: String, - @ProtoNumber(4) val portraitImageUrl: String, - @ProtoNumber(5) val landscapeImageUrl: String, - @ProtoNumber(6) val viewCount: Int = 0, - @ProtoNumber(7) val language: Language? = Language.ENGLISH -) - -@Serializable -enum class Language(val id: Int) { - @ProtoNumber(0) - @SerializedName("0") - ENGLISH(0), - - @ProtoNumber(1) - @SerializedName("1") - SPANISH(1), - - // Temporary add the languages that are not present on the API yet. - // @ProtoNumber(2) - // @SerializedName("2") - THAI(2), - - // @ProtoNumber(3) - // @SerializedName("3") - PORTUGUESE_BR(3) -} - -@Serializable -data class UpdatedTitleGroup( - @ProtoNumber(1) val groupName: String, - @ProtoNumber(2) val titles: List<UpdatedTitle> = emptyList() -) - -@Serializable -data class UpdatedTitle( - @ProtoNumber(1) val title: Title? = null -) - -@Serializable -data class Chapter( - @ProtoNumber(1) val titleId: Int, - @ProtoNumber(2) val chapterId: Int, - @ProtoNumber(3) val name: String, - @ProtoNumber(4) val subTitle: String? = null, - @ProtoNumber(6) val startTimeStamp: Int, - @ProtoNumber(7) val endTimeStamp: Int -) - -@Serializable -data class MangaPlusPage(@ProtoNumber(1) val page: MangaPage? = null) - -@Serializable -data class MangaPage( - @ProtoNumber(1) val imageUrl: String, - @ProtoNumber(2) val width: Int, - @ProtoNumber(3) val height: Int, - @ProtoNumber(5) val encryptionKey: String? = null -) - -// Used for the deserialization on older devices. -const val DECODE_SCRIPT: String = - """ - var protobuf = module.exports; - - var Root = protobuf.Root; - var Type = protobuf.Type; - var Field = protobuf.Field; - var Enum = protobuf.Enum; - var OneOf = protobuf.OneOf; - - var Response = new Type("Response") - .add( - new OneOf("data") - .add(new Field("success", 1, "SuccessResult")) - .add(new Field("error", 2, "ErrorResult")) - ); - - var ErrorResult = new Type("ErrorResult") - .add(new Field("action", 1, "Action")) - .add(new Field("englishPopup", 2, "Popup")) - .add(new Field("spanishPopup", 3, "Popup")); - - var Action = new Enum("Action") - .add("DEFAULT", 0) - .add("UNAUTHORIZED", 1) - .add("MAINTAINENCE", 2) - .add("GEOIP_BLOCKING", 3); - - var Popup = new Type("Popup") - .add(new Field("subject", 1, "string")) - .add(new Field("body", 2, "string")); - - var SuccessResult = new Type("SuccessResult") - .add(new Field("isFeaturedUpdated", 1, "bool")) - .add( - new OneOf("data") - .add(new Field("allTitlesView", 5, "AllTitlesView")) - .add(new Field("titleRankingView", 6, "TitleRankingView")) - .add(new Field("titleDetailView", 8, "TitleDetailView")) - .add(new Field("mangaViewer", 10, "MangaViewer")) - .add(new Field("webHomeView", 11, "WebHomeView")) - ); - - var TitleRankingView = new Type("TitleRankingView") - .add(new Field("titles", 1, "Title", "repeated")); - - var AllTitlesView = new Type("AllTitlesView") - .add(new Field("titles", 1, "Title", "repeated")); - - var WebHomeView = new Type("WebHomeView") - .add(new Field("groups", 2, "UpdatedTitleGroup", "repeated")); - - var TitleDetailView = new Type("TitleDetailView") - .add(new Field("title", 1, "Title")) - .add(new Field("titleImageUrl", 2, "string")) - .add(new Field("overview", 3, "string")) - .add(new Field("backgroundImageUrl", 4, "string")) - .add(new Field("nextTimeStamp", 5, "uint32")) - .add(new Field("updateTiming", 6, "UpdateTiming")) - .add(new Field("viewingPeriodDescription", 7, "string")) - .add(new Field("nonAppearanceInfo", 8, "string", {"default": ""})) - .add(new Field("firstChapterList", 9, "Chapter", "repeated")) - .add(new Field("lastChapterList", 10, "Chapter", "repeated")) - .add(new Field("isSimulReleased", 14, "bool")) - .add(new Field("chaptersDescending", 17, "bool")); - - var UpdateTiming = new Enum("UpdateTiming") - .add("NOT_REGULARLY", 0) - .add("MONDAY", 1) - .add("TUESDAY", 2) - .add("WEDNESDAY", 3) - .add("THURSDAY", 4) - .add("FRIDAY", 5) - .add("SATURDAY", 6) - .add("SUNDAY", 7) - .add("DAY", 8); - - var MangaViewer = new Type("MangaViewer") - .add(new Field("pages", 1, "Page", "repeated")); - - var Title = new Type("Title") - .add(new Field("titleId", 1, "uint32")) - .add(new Field("name", 2, "string")) - .add(new Field("author", 3, "string")) - .add(new Field("portraitImageUrl", 4, "string")) - .add(new Field("landscapeImageUrl", 5, "string")) - .add(new Field("viewCount", 6, "uint32", {"default": 0})) - .add(new Field("language", 7, "Language", {"default": 0})); - - var Language = new Enum("Language") - .add("ENGLISH", 0) - .add("SPANISH", 1); - // .add("THAI", 2) - // .add("PORTUGUESE_BR", 3); - - var UpdatedTitleGroup = new Type("UpdatedTitleGroup") - .add(new Field("groupName", 1, "string")) - .add(new Field("titles", 2, "UpdatedTitle", "repeated")); - - var UpdatedTitle = new Type("UpdatedTitle") - .add(new Field("title", 1, "Title")) - .add(new Field("chapterId", 2, "uint32")) - .add(new Field("chapterName", 3, "string")) - .add(new Field("chapterSubtitle", 4, "string")); - - var Chapter = new Type("Chapter") - .add(new Field("titleId", 1, "uint32")) - .add(new Field("chapterId", 2, "uint32")) - .add(new Field("name", 3, "string")) - .add(new Field("subTitle", 4, "string", "optional")) - .add(new Field("startTimeStamp", 6, "uint32")) - .add(new Field("endTimeStamp", 7, "uint32")); - - var Page = new Type("Page") - .add(new Field("page", 1, "MangaPage")); - - var MangaPage = new Type("MangaPage") - .add(new Field("imageUrl", 1, "string")) - .add(new Field("width", 2, "uint32")) - .add(new Field("height", 3, "uint32")) - .add(new Field("encryptionKey", 5, "string", "optional")); - - var root = new Root() - .define("mangaplus") - .add(Response) - .add(ErrorResult) - .add(Action) - .add(Popup) - .add(SuccessResult) - .add(TitleRankingView) - .add(AllTitlesView) - .add(WebHomeView) - .add(TitleDetailView) - .add(UpdateTiming) - .add(MangaViewer) - .add(Title) - .add(Language) - .add(UpdatedTitleGroup) - .add(UpdatedTitle) - .add(Chapter) - .add(Page) - .add(MangaPage); - - function decode(arr) { - var Response = root.lookupType("Response"); - var message = Response.decode(arr); - return Response.toObject(message, {defaults: true}); - } - - (function () { - return JSON.stringify(decode(BYTE_ARR)).replace(/\,\{\}/g, ""); - })(); - """ diff --git a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusFactory.kt b/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusFactory.kt deleted file mode 100644 index 927900192..000000000 --- a/src/all/mangaplus/src/eu/kanade/tachiyomi/extension/all/mangaplus/MangaPlusFactory.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangaplus - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class MangaPlusFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - MangaPlusEnglish(), - MangaPlusSpanish(), - MangaPlusThai(), - MangaPlusPortuguese() - ) -} - -class MangaPlusEnglish : MangaPlus("en", "eng", Language.ENGLISH) -class MangaPlusSpanish : MangaPlus("es", "esp", Language.SPANISH) -class MangaPlusThai : MangaPlus("th", "eng", Language.THAI) - -// The titles have the Portugal flag in the thumbnail, but the text of the translations is Brazilian. -class MangaPlusPortuguese : MangaPlus("pt-BR", "eng", Language.PORTUGUESE_BR) diff --git a/src/all/mangatoon/AndroidManifest.xml b/src/all/mangatoon/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/mangatoon/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/mangatoon/build.gradle b/src/all/mangatoon/build.gradle deleted file mode 100644 index e47f898ef..000000000 --- a/src/all/mangatoon/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangatoon (Limited)' - pkgNameSuffix = 'all.mangatoon' - extClass = '.MangaToonFactory' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/mangatoon/res/mipmap-hdpi/ic_launcher.png b/src/all/mangatoon/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ce0334545..000000000 Binary files a/src/all/mangatoon/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangatoon/res/mipmap-mdpi/ic_launcher.png b/src/all/mangatoon/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3472ba965..000000000 Binary files a/src/all/mangatoon/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangatoon/res/mipmap-xhdpi/ic_launcher.png b/src/all/mangatoon/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 29290f243..000000000 Binary files a/src/all/mangatoon/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangatoon/res/mipmap-xxhdpi/ic_launcher.png b/src/all/mangatoon/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a6b86fe07..000000000 Binary files a/src/all/mangatoon/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangatoon/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/mangatoon/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9d3cd1b72..000000000 Binary files a/src/all/mangatoon/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mangatoon/res/web_hi_res_512.png b/src/all/mangatoon/res/web_hi_res_512.png deleted file mode 100644 index 801b02f16..000000000 Binary files a/src/all/mangatoon/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToon.kt b/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToon.kt deleted file mode 100644 index 873fdfe8e..000000000 --- a/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToon.kt +++ /dev/null @@ -1,118 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangatoon - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -open class MangaToon( - override val lang: String, - private val urllang: String -) : ParsedHttpSource() { - - override val name = "MangaToon (Limited)" - override val baseUrl = "https://mangatoon.mobi" - override val supportsLatest = true - - override fun popularMangaSelector() = "div.genre-content div.items a" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = "div.recommend-item" - override fun chapterListSelector() = "a.episode-item" - - override fun popularMangaNextPageSelector() = "span.next" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request { - val page0 = page - 1 - return GET("$baseUrl/$urllang/genre/hot?page=$page0", headers) - } - override fun latestUpdatesRequest(page: Int): Request { - val page0 = page - 1 - return GET("$baseUrl/$urllang/genre/new?page=$page0", headers) - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/$urllang/search?word=$query")?.newBuilder() - return GET(url.toString(), headers) - } - - // override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - // override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/episodes", headers) - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = (element.select("a").first().attr("href")) - manga.title = element.select("div.recommend-comics-title").text().trim() - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = (element.select("a").first().attr("href")) - manga.title = element.select("div.content-title").text().trim() - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.url = element.select("a").first().attr("href") - chapter.chapter_number = element.select("div.item-left").text().trim().toFloat() - val date = element.select("div.episode-date").text() - chapter.date_upload = parseDate(date) - chapter.name = if (chapter.chapter_number> 20) { "\uD83D\uDD12 " } else { "" } + element.select("div.episode-title").text().trim() - return chapter - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(date)?.time ?: 0L - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select("div.created-by").text().trim() - manga.artist = manga.author - manga.description = document.select("div.description").text().trim() - manga.thumbnail_url = document.select("div.detail-top-right img").attr("abs:src") - val glist = document.select("div.description-tag div.tag").map { it.text() } - manga.genre = glist.joinToString(", ") - manga.status = when (document.select("span.update-date")?.first()?.text()) { - "Update" -> SManga.ONGOING - "End", "完结" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - return manga - } - - override fun pageListParse(response: Response): List<Page> { - val body = response.asJsoup() - val pages = mutableListOf<Page>() - val elements = body.select("div.pictures img") - for (i in 0 until elements.size) { - pages.add(Page(i, "", elements[i].attr("abs:src"))) - } - if (pages.size == 1) throw Exception("Locked episode, download MangaToon APP and read for free!") - return pages - } - - override fun pageListParse(document: Document) = throw Exception("Not used") - override fun imageUrlRequest(page: Page) = throw Exception("Not used") - override fun imageUrlParse(document: Document) = throw Exception("Not used") -} diff --git a/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToonFactory.kt b/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToonFactory.kt deleted file mode 100644 index 59f28b41a..000000000 --- a/src/all/mangatoon/src/eu/kanade/tachiyomi/extension/all/mangatoon/MangaToonFactory.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mangatoon - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class MangaToonFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - ZH(), - EN(), - ID(), - VI(), - ES(), - PT(), - TH() - ) - - class ZH : MangaToon("zh", "cn") - class EN : MangaToon("en", "en") - class ID : MangaToon("id", "id") - class VI : MangaToon("vi", "vi") - class ES : MangaToon("es", "es") - class PT : MangaToon("pt", "pt") - class TH : MangaToon("th", "th") -} diff --git a/src/all/mango/AndroidManifest.xml b/src/all/mango/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/mango/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/mango/CHANGELOG.md b/src/all/mango/CHANGELOG.md deleted file mode 100644 index c6987889b..000000000 --- a/src/all/mango/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ - - -## 1.0.0 - -### Features - -* First version \ No newline at end of file diff --git a/src/all/mango/build.gradle b/src/all/mango/build.gradle deleted file mode 100644 index 4b5ce3295..000000000 --- a/src/all/mango/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mango' - pkgNameSuffix = 'all.mango' - extClass = '.Mango' - extVersionCode = 2 - libVersion = '1.2' -} - -dependencies { - implementation 'info.debatty:java-string-similarity:2.0.0' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/mango/res/ic_launcher-web.png b/src/all/mango/res/ic_launcher-web.png deleted file mode 100644 index 0123a0fbb..000000000 Binary files a/src/all/mango/res/ic_launcher-web.png and /dev/null differ diff --git a/src/all/mango/res/mipmap-hdpi/ic_launcher.png b/src/all/mango/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 553da24a1..000000000 Binary files a/src/all/mango/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mango/res/mipmap-ldpi/ic_launcher.png b/src/all/mango/res/mipmap-ldpi/ic_launcher.png deleted file mode 100644 index c0961357c..000000000 Binary files a/src/all/mango/res/mipmap-ldpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mango/res/mipmap-mdpi/ic_launcher.png b/src/all/mango/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fd970f8cf..000000000 Binary files a/src/all/mango/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mango/res/mipmap-xhdpi/ic_launcher.png b/src/all/mango/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8600237ec..000000000 Binary files a/src/all/mango/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mango/res/mipmap-xxhdpi/ic_launcher.png b/src/all/mango/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 960d7011e..000000000 Binary files a/src/all/mango/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mango/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/mango/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 307f16eca..000000000 Binary files a/src/all/mango/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mango/src/eu/kanade/tachiyomi/extension/all/mango/Mango.kt b/src/all/mango/src/eu/kanade/tachiyomi/extension/all/mango/Mango.kt deleted file mode 100644 index 75cf52e32..000000000 --- a/src/all/mango/src/eu/kanade/tachiyomi/extension/all/mango/Mango.kt +++ /dev/null @@ -1,325 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mango - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.text.InputType -import android.widget.Toast -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonObject -import com.google.gson.JsonSyntaxException -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import info.debatty.java.stringsimilarity.JaroWinkler -import info.debatty.java.stringsimilarity.Levenshtein -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.IOException - -class Mango : ConfigurableSource, HttpSource() { - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/api/library", headersBuilder().build()) - - // Our popular manga are just our library of manga - override fun popularMangaParse(response: Response): MangasPage { - val result = try { - gson.fromJson<JsonObject>(response.body()!!.string()) - } catch (e: JsonSyntaxException) { - apiCookies = "" - throw Exception("Login Likely Failed. Try Refreshing.") - } - val mangas = result["titles"].asJsonArray - return MangasPage( - mangas.asJsonArray.map { - SManga.create().apply { - url = "/book/" + it["id"].asString - title = it["display_name"].asString - thumbnail_url = baseUrl + it["cover_url"].asString - } - }, - false - ) - } - - override fun latestUpdatesRequest(page: Int): Request = - throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = - throw UnsupportedOperationException("Not used") - - // Default is to just return the whole library for searching - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(1) - - // Overridden fetch so that we use our overloaded method instead - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - // Here the best we can do is just match manga based on their titles - private fun searchMangaParse(response: Response, query: String): MangasPage { - - val queryLower = query.toLowerCase() - val mangas = popularMangaParse(response).mangas - val exactMatch = mangas.firstOrNull { it.title.toLowerCase() == queryLower } - if (exactMatch != null) { - return MangasPage(listOf(exactMatch), false) - } - - // Text distance algorithms - val textDistance = Levenshtein() - val textDistance2 = JaroWinkler() - - // Take results that potentially start the same - val results = mangas.filter { - val title = it.title.toLowerCase() - val query2 = queryLower.take(7) - (title.startsWith(query2, true) || title.contains(query2, true)) - }.sortedBy { textDistance.distance(queryLower, it.title.toLowerCase()) } - - // Take similar results - val results2 = mangas.map { Pair(textDistance2.distance(it.title.toLowerCase(), query), it) } - .filter { it.first < 0.3 }.sortedBy { it.first }.map { it.second } - val combinedResults = results.union(results2) - - // Finally return the list - return MangasPage(combinedResults.toList(), false) - } - - // Stub - override fun searchMangaParse(response: Response): MangasPage = - throw UnsupportedOperationException("Not used") - - override fun mangaDetailsRequest(manga: SManga): Request = - GET(baseUrl + "/api" + manga.url, headers) - - // This will just return the same thing as the main library endpoint - override fun mangaDetailsParse(response: Response): SManga { - val result = try { - gson.fromJson<JsonObject>(response.body()!!.string()) - } catch (e: JsonSyntaxException) { - apiCookies = "" - throw Exception("Login Likely Failed. Try Refreshing.") - } - return SManga.create().apply { - url = "/book/" + result["id"].asString - title = result["display_name"].asString - thumbnail_url = baseUrl + result["cover_url"].asString - } - } - - override fun chapterListRequest(manga: SManga): Request = - GET(baseUrl + "/api" + manga.url, headers) - - // The chapter url will contain how many pages the chapter contains for our page list endpoint - override fun chapterListParse(response: Response): List<SChapter> { - val result = try { - gson.fromJson<JsonObject>(response.body()!!.string()) - } catch (e: JsonSyntaxException) { - apiCookies = "" - throw Exception("Login Likely Failed. Try Refreshing.") - } - return result["entries"].asJsonArray.map { obj -> - SChapter.create().apply { - name = obj["display_name"].asString - url = "/page/${obj["title_id"].asString}/${obj["id"].asString}/${obj["pages"].asString}/" - date_upload = 1000L * obj["mtime"].asLong - } - }.sortedByDescending { it.date_upload } - } - - // Stub - override fun pageListRequest(chapter: SChapter): Request = - throw UnsupportedOperationException("Not used") - - // Overridden fetch so that we use our overloaded method instead - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - val splitUrl = chapter.url.split("/").toMutableList() - val numPages = splitUrl.removeAt(splitUrl.size - 2).toInt() - val baseUrlChapter = splitUrl.joinToString("/") - val pages = mutableListOf<Page>() - for (i in 0..numPages) { - pages.add( - Page( - index = i, - imageUrl = "$baseUrl/api$baseUrlChapter$i" - ) - ) - } - return Observable.just(pages) - } - - // Stub - override fun pageListParse(response: Response): List<Page> = - throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(response: Response): String = "" - override fun getFilterList(): FilterList = FilterList() - - override val name = "Mango" - override val lang = "en" - override val supportsLatest = false - - override val baseUrl by lazy { getPrefBaseUrl() } - private val username by lazy { getPrefUsername() } - private val password by lazy { getPrefPassword() } - private val gson by lazy { Gson() } - private var apiCookies: String = "" - - override fun headersBuilder(): Headers.Builder = - Headers.Builder() - .add("User-Agent", "Tachiyomi Mango v${BuildConfig.VERSION_NAME}") - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override val client: OkHttpClient = - network.client.newBuilder() - .addInterceptor { authIntercept(it) } - .build() - - private fun authIntercept(chain: Interceptor.Chain): Response { - - // Check that we have our username and password to login with - val request = chain.request() - if (username.isEmpty() || password.isEmpty()) { - throw IOException("Missing username or password") - } - - // Do the login if we have not gotten the cookies yet - if (apiCookies.isEmpty() || !apiCookies.contains("mango-sessid-9000", true)) { - doLogin(chain) - } - - // Append the new cookie from the api - val authRequest = request.newBuilder() - .addHeader("Cookie", apiCookies) - .build() - - return chain.proceed(authRequest) - } - - private fun doLogin(chain: Interceptor.Chain) { - // Try to login - val formHeaders: Headers = headersBuilder() - .add("ContentType", "application/x-www-form-urlencoded") - .build() - val formBody: RequestBody = FormBody.Builder() - .addEncoded("username", username) - .addEncoded("password", password) - .build() - val loginRequest = POST("$baseUrl/login", formHeaders, formBody) - val response = chain.proceed(loginRequest) - if (response.code() != 200 || response.header("Set-Cookie") == null) { - throw Exception("Login Failed. Check Address and Credentials") - } - // Save the cookies from the response - apiCookies = response.header("Set-Cookie")!! - response.close() - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - screen.addPreference(screen.editTextPreference(ADDRESS_TITLE, ADDRESS_DEFAULT, baseUrl)) - screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true)) - } - - private fun androidx.preference.PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference { - return androidx.preference.EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - - if (isPassword) { - setOnBindEditTextListener { - it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - } - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - apiCookies = "" - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - screen.addPreference(screen.supportEditTextPreference(ADDRESS_TITLE, ADDRESS_DEFAULT, baseUrl)) - screen.addPreference(screen.supportEditTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.supportEditTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password)) - } - - private fun PreferenceScreen.supportEditTextPreference(title: String, default: String, value: String): EditTextPreference { - return EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - apiCookies = "" - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - // We strip the last slash since we will append it above - private fun getPrefBaseUrl(): String { - var path = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!! - if (path.isNotEmpty() && path.last() == '/') { - path = path.substring(0, path.length - 1) - } - return path - } - private fun getPrefUsername(): String = preferences.getString(USERNAME_TITLE, USERNAME_DEFAULT)!! - private fun getPrefPassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!! - - companion object { - private const val ADDRESS_TITLE = "Address" - private const val ADDRESS_DEFAULT = "" - private const val USERNAME_TITLE = "Username" - private const val USERNAME_DEFAULT = "" - private const val PASSWORD_TITLE = "Password" - private const val PASSWORD_DEFAULT = "" - } -} diff --git a/src/all/mmrcms/AndroidManifest.xml b/src/all/mmrcms/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/mmrcms/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/mmrcms/build.gradle b/src/all/mmrcms/build.gradle deleted file mode 100644 index d9fcd97f8..000000000 --- a/src/all/mmrcms/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'My Manga Reader CMS (Many sources)' - pkgNameSuffix = 'all.mmrcms' - extClass = '.MyMangaReaderCMSSources' - extVersionCode = 55 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/mmrcms/res/mipmap-hdpi/ic_launcher.png b/src/all/mmrcms/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4a0c51caa..000000000 Binary files a/src/all/mmrcms/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mmrcms/res/mipmap-mdpi/ic_launcher.png b/src/all/mmrcms/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c06361228..000000000 Binary files a/src/all/mmrcms/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mmrcms/res/mipmap-xhdpi/ic_launcher.png b/src/all/mmrcms/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ffa23e4f3..000000000 Binary files a/src/all/mmrcms/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mmrcms/res/mipmap-xxhdpi/ic_launcher.png b/src/all/mmrcms/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1f9cdfc6c..000000000 Binary files a/src/all/mmrcms/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mmrcms/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/mmrcms/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 670d4995a..000000000 Binary files a/src/all/mmrcms/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/mmrcms/res/web_hi_res_512.png b/src/all/mmrcms/res/web_hi_res_512.png deleted file mode 100644 index f2d6d43c6..000000000 Binary files a/src/all/mmrcms/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/GeneratedSources.kt b/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/GeneratedSources.kt deleted file mode 100644 index f48080e3c..000000000 --- a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/GeneratedSources.kt +++ /dev/null @@ -1,37 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mmrcms - -// GENERATED FILE, DO NOT MODIFY! -// Generated Fri, 26 Feb 2021 12:37:22 -0500 - -private const val MMRSOURCE_1 = """{"language":"ar","name":"مانجا اون لاين","base_url":"https://onma.me","supports_latest":false,"isNsfw":false,"item_url":"\u003c!doctype html\u003e\n\u003chtml lang\u003d\"en-US\"\u003e\n \u003chead\u003e \n \u003cmeta charset\u003d\"UTF-8\"\u003e \n \u003cmeta http-equiv\u003d\"Content-Type\" content\u003d\"text/html; charset\u003dUTF-8\"\u003e \n \u003cmeta http-equiv\u003d\"X-UA-Compatible\" content\u003d\"IE\u003dEdge,chrome\u003d1\"\u003e \n \u003cmeta name\u003d\"robots\" content\u003d\"noindex, nofollow\"\u003e \n \u003cmeta name\u003d\"viewport\" content\u003d\"width\u003ddevice-width,initial-scale\u003d1\"\u003e \n \u003ctitle\u003eJust a moment...\u003c/title\u003e \n \u003cstyle type\u003d\"text/css\"\u003e\n html, body {width: 100%; height: 100%; margin: 0; padding: 0;}\n body {background-color: #ffffff; color: #000000; font-family:-apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, \"Helvetica Neue\",Arial, sans-serif; font-size: 16px; line-height: 1.7em;-webkit-font-smoothing: antialiased;}\n h1 { text-align: center; font-weight:700; margin: 16px 0; font-size: 32px; color:#000000; line-height: 1.25;}\n p {font-size: 20px; font-weight: 400; margin: 8px 0;}\n p, .attribution, {text-align: center;}\n #spinner {margin: 0 auto 30px auto; display: block;}\n .attribution {margin-top: 32px;}\n @keyframes fader { 0% {opacity: 0.2;} 50% {opacity: 1.0;} 100% {opacity: 0.2;} }\n @-webkit-keyframes fader { 0% {opacity: 0.2;} 50% {opacity: 1.0;} 100% {opacity: 0.2;} }\n #cf-bubbles \u003e .bubbles { animation: fader 1.6s infinite;}\n #cf-bubbles \u003e .bubbles:nth-child(2) { animation-delay: .2s;}\n #cf-bubbles \u003e .bubbles:nth-child(3) { animation-delay: .4s;}\n .bubbles { background-color: #f58220; width:20px; height: 20px; margin:2px; border-radius:100%; display:inline-block; }\n a { color: #2c7cb0; text-decoration: none; -moz-transition: color 0.15s ease; -o-transition: color 0.15s ease; -webkit-transition: color 0.15s ease; transition: color 0.15s ease; }\n a:hover{color: #f4a15d}\n .attribution{font-size: 16px; line-height: 1.5;}\n .ray_id{display: block; margin-top: 8px;}\n #cf-wrapper #challenge-form { padding-top:25px; padding-bottom:25px; }\n #cf-hcaptcha-container { text-align:center;}\n #cf-hcaptcha-container iframe { display: inline-block;}\n \u003c/style\u003e \n \u003cmeta http-equiv\u003d\"refresh\" content\u003d\"12\"\u003e \n \u003cscript type\u003d\"text/javascript\"\u003e\n //\u003c![CDATA[\n (function(){\n \n window._cf_chl_opt\u003d{\n cvId: \"2\",\n cType: \"non-interactive\",\n cNounce: \"13997\",\n cRay: \"627b6f1fc94f0cb5\",\n cHash: \"35ddd2c36861109\",\n cFPWv: \"g\",\n cTTimeMs: \"4000\",\n cRq: {\n ru: \"aHR0cHM6Ly9vbm1hLm1lLw\u003d\u003d\",\n ra: \"b2todHRwLzMuMTAuMA\u003d\u003d\",\n rm: \"R0VU\",\n d: \"perjLNesnwec2J0ed2/ySdERf73jucfZwEQVLgVcXCGHw6P8O0jLroAf/zm8b7CW0V2fkpmkuAX4i7lfeGA2/qlZWIZ2vl9louVQmNQigRyZPcsZEkh4akKpE3OdrHGDWXtmyStN+72Mnupcu58bOAmovZgc5uUasxD+DI2+QWBnzpvDLiEbf67enhIfZ4oBE4edpmuMChSXZ64U/pKg4YIEelxN3q+xNRAEym0/oYEpM8ZmemZmk9gKSolAclvI/DqOzUTXwC+OSkurwP+AJLAyJVdIFNRKnWFNl4ejWWltvVLGoak36pZEVLZCb19WjoQsJxH4pmhKDOadVuEYEFXCi3qLfzDmGP+gYfRDEvCghIpX7XIkKQ/t/Di42dO08LDHv6wz5v6aMznDLbZywGzaVURLsfxJbYUq7V+H3pdvJ80CxkBnN6tQEqZmyHtdagaj3yNI+mSFe8rc/Q+P3YRTujOJqhOOu0/mDZJ8Ry3nEBUxDkN8sjoHZ8Njg4eMYiStC8tw+Q0ln+VuWMzJJcS6YH7fNV4sRiAOcbjH3QVFduktPfaboEl+FHYbZB1kQI5g41mAolX1r65Q5caV1A\u003d\u003d\",\n t: \"MTYxNDM2MTA0Ny4wMTEwMDA\u003d\",\n m: \"eLfqUZUe/r8pjE8cGOE1QCiB3boeRVj2i/NtmEntI4Y\u003d\",\n i1: \"lzjtnFo2kjaX0kiLi4lxUA\u003d\u003d\",\n i2: \"gQvCXATP8aQ7FLndAC/M4Q\u003d\u003d\",\n uh: \"QNqr1PtsmAsBuHIaoM6zeJRgUdRT1sK83/SuOuA+LQM\u003d\",\n hh: \"igG83LHMfnEqFW43t5rmZJNdy6qUZ3mbsE1OaNp3q7o\u003d\",\n }\n }\n window._cf_chl_enter \u003d function(){window._cf_chl_opt.p\u003d1};\n \n })();\n //]]\u003e\n \u003c/script\u003e \n \u003c/head\u003e \n \u003cbody\u003e \n \u003ctable width\u003d\"100%\" height\u003d\"100%\" cellpadding\u003d\"20\"\u003e \n \u003ctbody\u003e\n \u003ctr\u003e \n \u003ctd align\u003d\"center\" valign\u003d\"middle\"\u003e \n \u003cdiv class\u003d\"cf-browser-verification cf-im-under-attack\"\u003e \n \u003cnoscript\u003e \n \u003ch1 data-translate\u003d\"turn_on_js\" style\u003d\"color:#bd2426;\"\u003ePlease turn JavaScript on and reload the page.\u003c/h1\u003e \n \u003c/noscript\u003e \n \u003cdiv id\u003d\"cf-content\" style\u003d\"display:none\"\u003e \n \u003cdiv id\u003d\"cf-bubbles\"\u003e \n \u003cdiv class\u003d\"bubbles\"\u003e\u003c/div\u003e \n \u003cdiv class\u003d\"bubbles\"\u003e\u003c/div\u003e \n \u003cdiv class\u003d\"bubbles\"\u003e\u003c/div\u003e \n \u003c/div\u003e \n \u003ch1\u003e\u003cspan data-translate\u003d\"checking_browser\"\u003eChecking your browser before accessing\u003c/span\u003e onma.me.\u003c/h1\u003e \n \u003cdiv id\u003d\"no-cookie-warning\" class\u003d\"cookie-warning\" data-translate\u003d\"turn_on_cookies\" style\u003d\"display:none\"\u003e \n \u003cp data-translate\u003d\"turn_on_cookies\" style\u003d\"color:#bd2426;\"\u003ePlease enable Cookies and reload the page.\u003c/p\u003e \n \u003c/div\u003e \n \u003cp data-translate\u003d\"process_is_automatic\"\u003eThis process is automatic. Your browser will redirect to your requested content shortly.\u003c/p\u003e \n \u003cp data-translate\u003d\"allow_5_secs\" id\u003d\"cf-spinner-allow-5-secs\"\u003ePlease allow up to 5 seconds…\u003c/p\u003e \n \u003cp data-translate\u003d\"redirecting\" id\u003d\"cf-spinner-redirecting\" style\u003d\"display:none\"\u003eRedirecting…\u003c/p\u003e \n \u003c/div\u003e \n \u003ca href\u003d\"https://robinsonsdrlg.com/direct.php?tag\u003d7\"\u003e\n \u003c!-- table --\u003e\u003c/a\u003e \n \u003cform class\u003d\"challenge-form\" id\u003d\"challenge-form\" action\u003d\"/?__cf_chl_jschl_tk__\u003d5632e90e17b735f2c60967782a1f177ada357135-1614361047-0-ARPv9TopkAeMJMPfWBiIqdvb-uCqLMoeuuQyUltx6UrNX4zWULStKgyNQNUGDMH1-kQuXNGZ_cR0dHtq9nO2MaJTRQsGLEeaOSAB6RvjE2eQmAxMqsHZHD7tSE3TWaEgzVFYyM4gGQL-IUkEyWrplnVHHcPnQ8AfSiQSo9EOv3XoKRMsa20P6EeMOBNc2S4dkLZglyaKpvSPormJ3WNggosqmHnJzvDJ0dI_zOBqeNJmRpybbZwX7VrrDEdjevj-q5Wd7MtJ4lbzhvUEzLI6bJBbBjmrvfvUnRT7W0hMoJwMXaG_hnmGxar3yo49ojlwp4K0ZXYALMX-i97hDuP-I2M\" method\u003d\"POST\" enctype\u003d\"application/x-www-form-urlencoded\"\u003e \n \u003cinput type\u003d\"hidden\" name\u003d\"r\" value\u003d\"9a1ec5ad9f1dd181ffe461c78386a14b693b3f44-1614361047-0-AU4jEZeN9ma/p3ARxE78JE/WQ5mZfcB6LzyNUxA8oal79Z2zA9rnH64q+//74kM0URJoCeRNVN3F1tZ1DYNlYMxTe1G3pY+zJAUgkEk02xg4sA1dZTdM+hUdrKi4mIIl4plWFQVN7GuRoBWbVWhFTgvQ/F2kgFYBs5w5W8j5WCukeMtexMIJNkAalO+ETfIJpl2H7BfFZG1CyUslcjAe2u26lcpBnQVKa3eI9qTJE27+/5C/HMfOJSzJoqQYvXxiMQvYNWUOJQc+yOfC0AqFG7CoOnNgwE5YP4PFCbMojAv4rjsC90h07Eh/qULuThzbK/ZYARaqSDx17yE0A0JAeiHd6ZYkK1+NtRVipsUOA54wtVBJOzBefb75mUB4ui1kTuySPWZD6KxsT5YO4vHmQEUJfulsRWHD2MqvNeK2PChjbpF3julRJtaGpNHGkYsFM26bFRWbOsIgj3li2KcYrtcDM7CmHmdbB4RS+9vVKkMf/aIrwW5gzR03j3Y2Yj0gmc0bxhTEqr/8rS1nJn8RkwSsAqIpFGExh1CbSEX8cDNLGVG4QJVxK6wmTG4hKnYULMH7PPrFq/i9pvjEseAw5rifKZPsdrIhFrOtJeQ1KupI36zq6J4DH+sikSofyVZ1dkz1VnFO/O3eltFwHDl0r4oiEC/+3h+3L+DGPbtbmw9oVyz3IMrRJgRnTInt0+VwRQIlxuQS7Fd0B2/ydPpOyaGoovZtPUoTsKw4ut/sLyWlAY6oCh0GfJAMUCgcqabDfIpVlgV44u5KspRLGZ2jU8R7XMZvGcgLpy9WYq1TjolEoIFZOPF58avRi6E/2TAaBaiDTLOecDnEQBi0aPvHXpkFoEbL2n50AIydIVPaDHGy64UlB8Dr2tjPBcFI+QIROOveN9gmXeT+oTbSQ7WYfSyjQzcJD9jV/WSXWVzFjE1859zjzUsQXhKbRvZh/0ewLwrZBD9SkARpjjhCxqHhzfQK3jtkKA7Q76yRIaZ24gboeFQJxW6v+ntbEGCTMKbP7t5lEbgZwxFe168Or8009+PymsRAlL3nmls4Y27p5fA40Q08884gL6HN95O/3mt0rF0BZuIjCa8r5E3zbUL6muSRvGWi5epqfeOCPerDaJm4Yth9BWexQxEu6Hf43lDL2cjcciY7Drn/lvQeX0Ff0hHu7alL7ioeamZTOvxQpOVRqG9nIHur5HynpKbnFyvQxK1Asrph4V9wUQCGSrMq10EXi5wY5CA+7VOYEJKOZCt0xsAmpd5lyAxSltbS2IVnPUubgWuN5KmxuadyvGEXxTM4rfGlBp9kOcXY+QMfh7gcmEeq+Dr68n+RVi0ir1jrOLtmvWVZau8nizPBU0r0+gQvgQGC0Al2FTtybgH48ged\"\u003e \n \u003cinput type\u003d\"hidden\" value\u003d\"4fe0d64d3699fcfda18b5510ced2ce96\" id\u003d\"jschl-vc\" name\u003d\"jschl_vc\"\u003e \n \u003c!-- \u003cinput type\u003d\"hidden\" value\u003d\"\" id\u003d\"jschl-vc\" name\u003d\"jschl_vc\"/\u003e --\u003e \n \u003cinput type\u003d\"hidden\" name\u003d\"pass\" value\u003d\"1614361051.011-ABabh7yVsU\"\u003e \n \u003cinput type\u003d\"hidden\" id\u003d\"jschl-answer\" name\u003d\"jschl_answer\"\u003e \n \u003c/form\u003e \n \u003cscript type\u003d\"text/javascript\"\u003e\n //\u003c![CDATA[\n (function(){\n var a \u003d document.getElementById(\u0027cf-content\u0027);\n a.style.display \u003d \u0027block\u0027;\n var isIE \u003d /(MSIE|Trident\\/|Edge\\/)/i.test(window.navigator.userAgent);\n var trkjs \u003d isIE ? new Image() : document.createElement(\u0027img\u0027);\n trkjs.setAttribute(\"src\", \"/cdn-cgi/images/trace/jschal/js/transparent.gif?ray\u003d627b6f1fc94f0cb5\");\n trkjs.id \u003d \"trk_jschal_js\";\n trkjs.setAttribute(\"alt\", \"\");\n document.body.appendChild(trkjs);\n var cpo\u003ddocument.createElement(\u0027script\u0027);\n cpo.type\u003d\u0027text/javascript\u0027;\n cpo.src\u003d\"/cdn-cgi/challenge-platform/h/g/orchestrate/jsch/v1\";\n document.getElementsByTagName(\u0027head\u0027)[0].appendChild(cpo);\n }());\n //]]\u003e\n \u003c/script\u003e \n \u003cdiv id\u003d\"trk_jschal_nojs\" style\u003d\"background-image:url(\u0027/cdn-cgi/images/trace/jschal/nojs/transparent.gif?ray\u003d627b6f1fc94f0cb5\u0027)\"\u003e \n \u003c/div\u003e \n \u003c/div\u003e \n \u003cdiv class\u003d\"attribution\"\u003e\n DDoS protection by \n \u003ca rel\u003d\"noopener noreferrer\" href\u003d\"https://www.cloudflare.com/5xx-error-landing/\" target\u003d\"_blank\"\u003eCloudflare\u003c/a\u003e \n \u003cbr\u003e \n \u003cspan class\u003d\"ray_id\"\u003eRay ID: \u003ccode\u003e627b6f1fc94f0cb5\u003c/code\u003e\u003c/span\u003e \n \u003c/div\u003e \u003c/td\u003e \n \u003c/tr\u003e \n \u003c/tbody\u003e\n \u003c/table\u003e \n \u003c/body\u003e\n\u003c/html\u003e/","categories":[],"tags":"null"}""" -private const val MMRSOURCE_2 = """{"language":"en","name":"Read Comics Online","base_url":"https://readcomicsonline.ru","supports_latest":true,"isNsfw":false,"item_url":"https://readcomicsonline.ru/comic/","categories":[{"id":"1","name":"One Shots \u0026 TPBs"},{"id":"2","name":"DC Comics"},{"id":"3","name":"Marvel Comics"},{"id":"4","name":"Boom Studios"},{"id":"5","name":"Dynamite"},{"id":"6","name":"Rebellion"},{"id":"7","name":"Dark Horse"},{"id":"8","name":"IDW"},{"id":"9","name":"Archie"},{"id":"10","name":"Graphic India"},{"id":"11","name":"Darby Pop"},{"id":"12","name":"Oni Press"},{"id":"13","name":"Icon Comics"},{"id":"14","name":"United Plankton"},{"id":"15","name":"Udon"},{"id":"16","name":"Image Comics"},{"id":"17","name":"Valiant"},{"id":"18","name":"Vertigo"},{"id":"19","name":"Devils Due"},{"id":"20","name":"Aftershock Comics"},{"id":"21","name":"Antartic Press"},{"id":"22","name":"Action Lab"},{"id":"23","name":"American Mythology"},{"id":"24","name":"Zenescope"},{"id":"25","name":"Top Cow"},{"id":"26","name":"Hermes Press"},{"id":"27","name":"451"},{"id":"28","name":"Black Mask"},{"id":"29","name":"Chapterhouse Comics"},{"id":"30","name":"Red 5"},{"id":"31","name":"Heavy Metal"},{"id":"32","name":"Bongo"},{"id":"33","name":"Top Shelf"},{"id":"34","name":"Bubble"},{"id":"35","name":"Boundless"},{"id":"36","name":"Avatar Press"},{"id":"37","name":"Space Goat Productions"},{"id":"38","name":"BroadSword Comics"},{"id":"39","name":"AAM-Markosia"},{"id":"40","name":"Fantagraphics"},{"id":"41","name":"Aspen"},{"id":"42","name":"American Gothic Press"},{"id":"43","name":"Vault"},{"id":"44","name":"215 Ink"},{"id":"45","name":"Abstract Studio"},{"id":"46","name":"Albatross"},{"id":"47","name":"ARH Comix"},{"id":"48","name":"Legendary Comics"},{"id":"49","name":"Monkeybrain"},{"id":"50","name":"Joe Books"},{"id":"51","name":"MAD"},{"id":"52","name":"Comics Experience"},{"id":"53","name":"Alterna Comics"},{"id":"54","name":"Lion Forge"},{"id":"55","name":"Benitez"},{"id":"56","name":"Storm King"},{"id":"57","name":"Sucker"},{"id":"58","name":"Amryl Entertainment"},{"id":"59","name":"Ahoy Comics"},{"id":"60","name":"Mad Cave"},{"id":"61","name":"Coffin Comics"},{"id":"62","name":"Magnetic Press"},{"id":"63","name":"Ablaze"},{"id":"64","name":"Europe Comics"},{"id":"65","name":"Humanoids"},{"id":"66","name":"TKO"},{"id":"67","name":"Soleil"},{"id":"68","name":"SAF Comics"},{"id":"69","name":"Scholastic"},{"id":"70","name":"Upshot"},{"id":"71","name":"Stranger Comics"},{"id":"72","name":"Inverse"},{"id":"73","name":"Virus"}],"tags":"null"}""" -private const val MMRSOURCE_3 = """{"language":"en","name":"Fallen Angels","base_url":"https://manga.fascans.com","supports_latest":true,"isNsfw":false,"item_url":"https://manga.fascans.com/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"4-Koma"},{"id":"34","name":"Cooking"}],"tags":"null"}""" -private const val MMRSOURCE_4 = """{"language":"en","name":"Zahard","base_url":"https://zahard.top","supports_latest":true,"isNsfw":false,"item_url":"https://zahard.top/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":[{"id":"tag","name":"("},{"id":"sdgsdg","name":"sdgsdg"},{"id":"action","name":"Action"},{"id":"fantasy","name":"Fantasy"},{"id":"manhwa","name":"Manhwa"},{"id":"martial-arts","name":"Martial Arts"},{"id":"shounen","name":"Shounen"},{"id":"webtoon","name":"Webtoon"},{"id":"webtoon","name":"Webtoon"},{"id":"action","name":"Action"},{"id":"fantasy","name":"Fantasy"}]}""" -private const val MMRSOURCE_5 = """{"language":"en","name":"Manhwas Men","base_url":"https://manhwas.men","supports_latest":true,"isNsfw":false,"item_url":"https://manhwas.men/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":[{"id":"tag","name":"前女友变女佣"},{"id":"four-sisters","name":"Four sisters"},{"id":"in-laws","name":"in-laws"},{"id":"raws","name":"raws"},{"id":"adult","name":"Adult"},{"id":"raw","name":"RAW"},{"id":"drama","name":"Drama"},{"id":"romance","name":"Romance"},{"id":"manhwa","name":"Manhwa"},{"id":"mature","name":"Mature"},{"id":"sub-english","name":"Sub English"}]}""" -private const val MMRSOURCE_6 = """{"language":"fr","name":"Scan FR","base_url":"https://www.scan-fr.cc","supports_latest":true,"isNsfw":false,"item_url":"https://www.scan-fr.cc/manga/","categories":[{"id":"1","name":"Comedy"},{"id":"2","name":"Doujinshi"},{"id":"3","name":"Drama"},{"id":"4","name":"Ecchi"},{"id":"5","name":"Fantasy"},{"id":"6","name":"Gender Bender"},{"id":"7","name":"Josei"},{"id":"8","name":"Mature"},{"id":"9","name":"Mecha"},{"id":"10","name":"Mystery"},{"id":"11","name":"One Shot"},{"id":"12","name":"Psychological"},{"id":"13","name":"Romance"},{"id":"14","name":"School Life"},{"id":"15","name":"Sci-fi"},{"id":"16","name":"Seinen"},{"id":"17","name":"Shoujo"},{"id":"18","name":"Shoujo Ai"},{"id":"19","name":"Shounen"},{"id":"20","name":"Shounen Ai"},{"id":"21","name":"Slice of Life"},{"id":"22","name":"Sports"},{"id":"23","name":"Supernatural"},{"id":"24","name":"Tragedy"},{"id":"25","name":"Yaoi"},{"id":"26","name":"Yuri"},{"id":"27","name":"Comics"},{"id":"28","name":"Autre"},{"id":"29","name":"BD Occidentale"},{"id":"30","name":"Manhwa"},{"id":"31","name":"Action"},{"id":"32","name":"Aventure"}],"tags":"null"}""" -private const val MMRSOURCE_7 = """{"language":"fr","name":"Scan VF","base_url":"https://www.scan-vf.net","supports_latest":true,"isNsfw":false,"item_url":"https://www.scan-vf.net/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_8 = """{"language":"fr","name":"Scan OP","base_url":"https://scan-op.cc","supports_latest":true,"isNsfw":false,"item_url":"https://scan-op.cc/manga/","categories":[{"id":"1","name":"Comedy"},{"id":"2","name":"Doujinshi"},{"id":"3","name":"Drama"},{"id":"4","name":"Ecchi"},{"id":"5","name":"Fantasy"},{"id":"6","name":"Gender Bender"},{"id":"7","name":"Josei"},{"id":"8","name":"Mature"},{"id":"9","name":"Mecha"},{"id":"10","name":"Mystery"},{"id":"11","name":"One Shot"},{"id":"12","name":"Psychological"},{"id":"13","name":"Romance"},{"id":"14","name":"School Life"},{"id":"15","name":"Sci-fi"},{"id":"16","name":"Seinen"},{"id":"17","name":"Shoujo"},{"id":"18","name":"Shoujo Ai"},{"id":"19","name":"Shounen"},{"id":"20","name":"Shounen Ai"},{"id":"21","name":"Slice of Life"},{"id":"22","name":"Sports"},{"id":"23","name":"Supernatural"},{"id":"24","name":"Tragedy"},{"id":"25","name":"Yaoi"},{"id":"26","name":"Yuri"},{"id":"27","name":"Comics"},{"id":"28","name":"Autre"}],"tags":[{"id":"nouveau","name":"nouveau"}]}""" -private const val MMRSOURCE_9 = """{"language":"id","name":"Komikid","base_url":"https://www.komikid.com","supports_latest":true,"isNsfw":false,"item_url":"https://www.komikid.com/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Fantasy"},{"id":"7","name":"Gender Bender"},{"id":"8","name":"Historical"},{"id":"9","name":"Horror"},{"id":"10","name":"Josei"},{"id":"11","name":"Martial Arts"},{"id":"12","name":"Mature"},{"id":"13","name":"Mecha"},{"id":"14","name":"Mystery"},{"id":"15","name":"One Shot"},{"id":"16","name":"Psychological"},{"id":"17","name":"Romance"},{"id":"18","name":"School Life"},{"id":"19","name":"Sci-fi"},{"id":"20","name":"Seinen"},{"id":"21","name":"Shoujo"},{"id":"22","name":"Shoujo Ai"},{"id":"23","name":"Shounen"},{"id":"24","name":"Shounen Ai"},{"id":"25","name":"Slice of Life"},{"id":"26","name":"Sports"},{"id":"27","name":"Supernatural"},{"id":"28","name":"Tragedy"},{"id":"29","name":"Yaoi"},{"id":"30","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_10 = """{"language":"pt-BR","name":"Mangás Yuri","base_url":"https://mangasyuri.net","supports_latest":true,"isNsfw":false,"item_url":"https://mangasyuri.net/manga/","categories":[{"id":"1","name":"Ação"},{"id":"2","name":"Aventura"},{"id":"3","name":"Comédia"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasia"},{"id":"8","name":"Gênero Trocado"},{"id":"9","name":"Harém"},{"id":"10","name":"Histórico"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Artes Marciais"},{"id":"14","name":"Maduro"},{"id":"15","name":"Robô"},{"id":"16","name":"Mistério"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psicológico"},{"id":"19","name":"Romance"},{"id":"20","name":"Vida Escolar"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Cotidiano"},{"id":"26","name":"Esportes"},{"id":"27","name":"Sobrenatural"},{"id":"28","name":"Tragédia"},{"id":"29","name":"Yuri"},{"id":"30","name":"Adulto"},{"id":"31","name":"Shounen"}],"tags":"null"}""" -private const val MMRSOURCE_11 = """{"language":"pl","name":"Nikushima","base_url":"http://azbivo.webd.pro","supports_latest":true,"isNsfw":false,"item_url":"http://azbivo.webd.pro/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shounen"},{"id":"25","name":"Slice of Life"},{"id":"26","name":"Sports"},{"id":"27","name":"Supernatural"},{"id":"28","name":"Tragedy"},{"id":"29","name":"Isekai"}],"tags":"null"}""" -private const val MMRSOURCE_12 = """{"language":"tr","name":"MangaHanta","base_url":"http://mangahanta.com","supports_latest":true,"isNsfw":false,"item_url":"http://mangahanta.com/manga/","categories":[{"id":"1","name":"Aksiyon"},{"id":"2","name":"Macera"},{"id":"3","name":"Komedi"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantezi"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Tarihi"},{"id":"11","name":"Korku"},{"id":"12","name":"Josei"},{"id":"13","name":"Dövüş Sanatları"},{"id":"14","name":"Yetişkin"},{"id":"15","name":"Mecha"},{"id":"16","name":"Gizem"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psikolojik"},{"id":"19","name":"Romantizm"},{"id":"20","name":"Okul Hayatı"},{"id":"21","name":"Bilim-Kurgu"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Hayattan Bir Parça"},{"id":"28","name":"Spor"},{"id":"29","name":"Doğaüstü"},{"id":"30","name":"Trajedi"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Vampir"},{"id":"34","name":"Webtoon"}],"tags":[{"id":"tag","name":"-ヒトガタナ-"},{"id":"amber","name":"Amber"},{"id":"amber-manga","name":"Amber manga"},{"id":"amber-oku","name":"Amber oku"},{"id":"amber-turkce-oku","name":"Amber Türkçe Oku"},{"id":"amber-yuno","name":"Amber Yuno"},{"id":"back-stage","name":"Back Stage"},{"id":"ballroom-e-youkoso","name":"Ballroom e Youkoso"},{"id":"beauty-game","name":"Beauty Game"},{"id":"beauty-game-oku","name":"Beauty Game Oku"},{"id":"boku-wa-mari-no-naka","name":"Boku Wa Mari No Naka"},{"id":"god-eater-kyuuseishu-no-kikan","name":"God Eater - Kyuuseishu no Kikan"},{"id":"god-eater-the-spiral-fate","name":"God Eater - The Spiral Fate"},{"id":"happiness","name":"Happiness"},{"id":"happiness-manga-oku","name":"happiness manga oku"},{"id":"happiness-turkce-oku","name":"happiness türkçe oku"},{"id":"hitogatana","name":"Hitogatana"},{"id":"im-in-mari-im-inside-mari","name":"ぼくは麻理のなか I\u0027m in Mari I\u0027m Inside Mari"},{"id":"itsuwaribito-utsuho","name":"Itsuwaribito Utsuho"},{"id":"kaguya-sama-wa-kokurasetai","name":"Kaguya-sama wa Kokurasetai"},{"id":"les-memoires-de-vanitas","name":"Les Mémoires de Vanitas"},{"id":"mahouka-koukou-no-rettousei-tsuioku-hen","name":"Mahouka Koukou no Rettousei - Tsuioku Hen"},{"id":"manga-oku","name":"manga oku"},{"id":"maou-na-ore-to-fushihime-no-yubiwa","name":"Maou na Ore to Fushihime no Yubiwa"},{"id":"may-i-shake-your-hand","name":"May I shake your hand"},{"id":"may-i-shake-your-hand-oku","name":"may I shake your hand oku"},{"id":"may-i-shake-your-hand-turkce-oku","name":"May I Shake Your Hand türkçe oku"},{"id":"memoir-of-vanitas","name":"Memoir of Vanitas"},{"id":"mutluluk","name":"Mutluluk"},{"id":"nanatsu-no-taizai","name":"Nanatsu No Taizai"},{"id":"nanatsu-no-taizai-turkce-oku","name":"Nanatsu no taizai Türkçe oku"},{"id":"oshimi-shuzo","name":"OSHIMI Shuzo"},{"id":"sousei-manga-oku","name":"sousei manga oku"},{"id":"sousei-no-onmyouji","name":"Sousei no Onmyouji"},{"id":"sousei-no-onmyouji-manga-oku","name":"Sousei no onmyouji manga oku"},{"id":"sousei-no-onmyouji-turkce-oku","name":"sousei no onmyouji türkçe oku"},{"id":"the-case-study-of-vanitas","name":"The Case Study of Vanitas"},{"id":"the-seven-deadly-sins","name":"The Seven Deadly Sins"},{"id":"vanitas-no-carte","name":"Vanitas no Carte"},{"id":"vanitas-no-shuki","name":"Vanitas no Shuki"},{"id":"yedi-olumcul-gunah","name":"Yedi Ölümcül Günah"}]}""" -private const val MMRSOURCE_13 = """{"language":"vi","name":"Fallen Angels Scans","base_url":"https://truyen.fascans.com","supports_latest":true,"isNsfw":false,"item_url":"https://truyen.fascans.com/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_14 = """{"language":"es","name":"LeoManga","base_url":"https://leomanga.me","supports_latest":false,"isNsfw":false,"item_url":"https://leomanga.me/manga/","categories":[{"id":"1","name":"Accion"},{"id":"2","name":"Aventura"},{"id":"3","name":"Comedia"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasia"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historico"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Artes Marciales"},{"id":"14","name":"Madura"},{"id":"15","name":"Mecha"},{"id":"16","name":"Misterio"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psicológico"},{"id":"19","name":"Romance"},{"id":"20","name":"Vida Cotidiana"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Supernatural"},{"id":"29","name":"Tragedia"},{"id":"30","name":"Yaoi"},{"id":"31","name":"Yuri"},{"id":"32","name":"Deporte"},{"id":"33","name":"Thriller"},{"id":"34","name":"Vida Escolar"},{"id":"35","name":"Boys Love"},{"id":"36","name":"Girls Love"},{"id":"37","name":"Gore"},{"id":"38","name":"Hentai"},{"id":"39","name":"Magia"},{"id":"40","name":"Manwha"},{"id":"41","name":"Policial"},{"id":"42","name":"Realidad Virtual"},{"id":"43","name":"Super Poderes"},{"id":"44","name":"Suspense"},{"id":"45","name":"Supervivencia"},{"id":"46","name":"Parodia"},{"id":"47","name":"Demonios"},{"id":"48","name":"Escolar"}],"tags":[{"id":"freaking-romance","name":"Freaking Romance"},{"id":"love-lucky","name":"Love Lucky"},{"id":"lust-awakening","name":"Lust Awakening"},{"id":"despertar-de-la-lujuria","name":"Despertar de la lujuria"},{"id":"inazumaelevenaresnotenbin","name":"inazumaelevenaresnotenbin"},{"id":"heir-of-the-penguins","name":"Heir of the Penguins"},{"id":"amor","name":"amor"},{"id":"drama","name":"drama"},{"id":"mysteries","name":"mysteries"},{"id":"anal","name":"anal"},{"id":"bukkake","name":"bukkake"},{"id":"doble-penetracion","name":"doble penetracion"},{"id":"orgia","name":"orgia"},{"id":"blow-job","name":"blow job"},{"id":"big-breasts","name":"big breasts"},{"id":"incesto","name":"incesto"},{"id":"milf","name":"milf"},{"id":"prenadas","name":"preñadas"},{"id":"slave-sex","name":"slave sex"},{"id":"lolicon","name":"lolicon"},{"id":"nurse","name":"nurse"},{"id":"reality","name":"Reality"},{"id":"glitch","name":"Glitch"},{"id":"glitcher","name":"Glitcher"},{"id":"horror","name":"Horror"},{"id":"suspenso","name":"Suspenso"},{"id":"realidad","name":"Realidad"},{"id":"slider","name":"Slider"},{"id":"novela","name":"Novela"},{"id":"sobrenatural","name":"Sobrenatural"},{"id":"tragedia","name":"Tragedia"},{"id":"error","name":"Error"},{"id":"psicologico","name":"Psicologico"},{"id":"sufrimiento","name":"Sufrimiento"},{"id":"visual","name":"Visual"},{"id":"narrativo","name":"Narrativo"},{"id":"shotacon","name":"shotacon"},{"id":"paizuri","name":"paizuri"},{"id":"kemonomimi","name":"kemonomimi"},{"id":"mundo-paralelo","name":"mundo paralelo"},{"id":"coleccion-hentai","name":"coleccion hentai"},{"id":"adultos","name":"adultos"}]}""" -private const val MMRSOURCE_15 = """{"language":"es","name":"submanga","base_url":"https://submanga.io","supports_latest":false,"isNsfw":false,"item_url":"https://submanga.io/manga/","categories":[{"id":"1","name":"Accion"},{"id":"2","name":"Aventura"},{"id":"3","name":"Comedia"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasia"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historico"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Artes Marciales"},{"id":"14","name":"Madura"},{"id":"15","name":"Mecha"},{"id":"16","name":"Misterio"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psicológico"},{"id":"19","name":"Romance"},{"id":"20","name":"Vida Cotidiana"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Supernatural"},{"id":"29","name":"Tragedia"},{"id":"30","name":"Yaoi"},{"id":"31","name":"Yuri"},{"id":"32","name":"Deporte"},{"id":"33","name":"Thriller"},{"id":"34","name":"Vida Escolar"},{"id":"35","name":"Boys Love"},{"id":"36","name":"Girls Love"},{"id":"37","name":"Gore"},{"id":"38","name":"Hentai"},{"id":"39","name":"Magia"},{"id":"40","name":"Manwha"},{"id":"41","name":"Policial"},{"id":"42","name":"Realidad Virtual"},{"id":"43","name":"Super Poderes"},{"id":"44","name":"Suspense"},{"id":"45","name":"Supervivencia"},{"id":"46","name":"Parodia"},{"id":"47","name":"Demonios"},{"id":"48","name":"Escolar"}],"tags":"null"}""" -private const val MMRSOURCE_16 = """{"language":"es","name":"Mangas.pw","base_url":"https://mangas.in","supports_latest":true,"isNsfw":false,"item_url":"https://mangas.in/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Hentai"},{"id":"34","name":"Smut"}],"tags":"null"}""" -private const val MMRSOURCE_17 = """{"language":"bg","name":"Utsukushii","base_url":"https://manga.utsukushii-bg.com","supports_latest":true,"isNsfw":false,"item_url":"https://manga.utsukushii-bg.com/manga/","categories":[{"id":"1","name":"Екшън"},{"id":"2","name":"Приключенски"},{"id":"3","name":"Комедия"},{"id":"4","name":"Драма"},{"id":"5","name":"Фентъзи"},{"id":"6","name":"Исторически"},{"id":"7","name":"Ужаси"},{"id":"8","name":"Джосей"},{"id":"9","name":"Бойни изкуства"},{"id":"10","name":"Меха"},{"id":"11","name":"Мистерия"},{"id":"12","name":"Самостоятелна/Пилотна глава"},{"id":"13","name":"Психологически"},{"id":"14","name":"Романтика"},{"id":"15","name":"Училищни"},{"id":"16","name":"Научна фантастика"},{"id":"17","name":"Сейнен"},{"id":"18","name":"Шоджо"},{"id":"19","name":"Реализъм"},{"id":"20","name":"Спорт"},{"id":"21","name":"Свръхестествено"},{"id":"22","name":"Трагедия"},{"id":"23","name":"Йокаи"},{"id":"24","name":"Паралелна вселена"},{"id":"25","name":"Супер сили"},{"id":"26","name":"Пародия"},{"id":"27","name":"Шонен"}],"tags":"null"}""" -private const val MMRSOURCE_18 = """{"language":"pl","name":"Phoenix-Scans","base_url":"https://phoenix-scans.pl","supports_latest":true,"isNsfw":false,"item_url":"https://phoenix-scans.pl/manga/","categories":[{"id":"1","name":"Shounen"},{"id":"2","name":"Tragedia"},{"id":"3","name":"Szkolne życie"},{"id":"4","name":"Romans"},{"id":"5","name":"Zagadka"},{"id":"6","name":"Horror"},{"id":"7","name":"Dojrzałe"},{"id":"8","name":"Psychologiczne"},{"id":"9","name":"Przygodowe"},{"id":"10","name":"Akcja"},{"id":"11","name":"Komedia"},{"id":"12","name":"Zboczone"},{"id":"13","name":"Fantasy"},{"id":"14","name":"Harem"},{"id":"15","name":"Historyczne"},{"id":"16","name":"Manhua"},{"id":"17","name":"Manhwa"},{"id":"18","name":"Sztuki walki"},{"id":"19","name":"One shot"},{"id":"20","name":"Sci fi"},{"id":"21","name":"Seinen"},{"id":"22","name":"Shounen ai"},{"id":"23","name":"Spokojne życie"},{"id":"24","name":"Sport"},{"id":"25","name":"Nadprzyrodzone"},{"id":"26","name":"Webtoons"},{"id":"27","name":"Dramat"},{"id":"28","name":"Hentai"},{"id":"29","name":"Mecha"},{"id":"30","name":"Gender Bender"},{"id":"31","name":"Gry"},{"id":"32","name":"Yaoi"}],"tags":[{"id":"aktywne","name":"aktywne"},{"id":"zakonczone","name":"zakończone"},{"id":"porzucone","name":"porzucone"}]}""" -private const val MMRSOURCE_19 = """{"language":"tr","name":"Puzzmos","base_url":"https://puzzmos.com","supports_latest":true,"isNsfw":false,"item_url":"https://puzzmos.com/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":[{"id":"2011","name":"2011"}]}""" -private const val MMRSOURCE_20 = """{"language":"fr","name":"Scan-1","base_url":"https://wwv.scan-1.com","supports_latest":true,"isNsfw":false,"item_url":"https://wwv.scan-1.com/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_21 = """{"language":"fr","name":"Lelscan-VF","base_url":"https://www.lelscan-vf.com","supports_latest":true,"isNsfw":false,"item_url":"https://lelscan-vf.co/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_22 = """{"language":"id","name":"Komik Manga","base_url":"https://adm.komikmanga.com","supports_latest":true,"isNsfw":false,"item_url":"https://adm.komikmanga.com/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Adult"},{"id":"34","name":"Isekai"}],"tags":"null"}""" -private const val MMRSOURCE_23 = """{"language":"ko","name":"Mangazuki Raws","base_url":"https://raws.mangazuki.co","supports_latest":false,"isNsfw":false,"item_url":"\u003c!doctype html\u003e\n\u003chtml lang\u003d\"en-US\"\u003e\n \u003chead\u003e \n \u003cmeta charset\u003d\"UTF-8\"\u003e \n \u003cmeta http-equiv\u003d\"Content-Type\" content\u003d\"text/html; charset\u003dUTF-8\"\u003e \n \u003cmeta http-equiv\u003d\"X-UA-Compatible\" content\u003d\"IE\u003dEdge,chrome\u003d1\"\u003e \n \u003cmeta name\u003d\"robots\" content\u003d\"noindex, nofollow\"\u003e \n \u003cmeta name\u003d\"viewport\" content\u003d\"width\u003ddevice-width,initial-scale\u003d1\"\u003e \n \u003ctitle\u003eJust a moment...\u003c/title\u003e \n \u003cstyle type\u003d\"text/css\"\u003e\n html, body {width: 100%; height: 100%; margin: 0; padding: 0;}\n body {background-color: #ffffff; color: #000000; font-family:-apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, \"Helvetica Neue\",Arial, sans-serif; font-size: 16px; line-height: 1.7em;-webkit-font-smoothing: antialiased;}\n h1 { text-align: center; font-weight:700; margin: 16px 0; font-size: 32px; color:#000000; line-height: 1.25;}\n p {font-size: 20px; font-weight: 400; margin: 8px 0;}\n p, .attribution, {text-align: center;}\n #spinner {margin: 0 auto 30px auto; display: block;}\n .attribution {margin-top: 32px;}\n @keyframes fader { 0% {opacity: 0.2;} 50% {opacity: 1.0;} 100% {opacity: 0.2;} }\n @-webkit-keyframes fader { 0% {opacity: 0.2;} 50% {opacity: 1.0;} 100% {opacity: 0.2;} }\n #cf-bubbles \u003e .bubbles { animation: fader 1.6s infinite;}\n #cf-bubbles \u003e .bubbles:nth-child(2) { animation-delay: .2s;}\n #cf-bubbles \u003e .bubbles:nth-child(3) { animation-delay: .4s;}\n .bubbles { background-color: #f58220; width:20px; height: 20px; margin:2px; border-radius:100%; display:inline-block; }\n a { color: #2c7cb0; text-decoration: none; -moz-transition: color 0.15s ease; -o-transition: color 0.15s ease; -webkit-transition: color 0.15s ease; transition: color 0.15s ease; }\n a:hover{color: #f4a15d}\n .attribution{font-size: 16px; line-height: 1.5;}\n .ray_id{display: block; margin-top: 8px;}\n #cf-wrapper #challenge-form { padding-top:25px; padding-bottom:25px; }\n #cf-hcaptcha-container { text-align:center;}\n #cf-hcaptcha-container iframe { display: inline-block;}\n \u003c/style\u003e \n \u003cmeta http-equiv\u003d\"refresh\" content\u003d\"12\"\u003e \n \u003cscript type\u003d\"text/javascript\"\u003e\n //\u003c![CDATA[\n (function(){\n \n window._cf_chl_opt\u003d{\n cvId: \"2\",\n cType: \"non-interactive\",\n cNounce: \"40325\",\n cRay: \"627b711a599ff039\",\n cHash: \"5147677fb1f5d40\",\n cFPWv: \"g\",\n cTTimeMs: \"4000\",\n cRq: {\n ru: \"aHR0cHM6Ly9yYXdzLm1hbmdhenVraS5jby8\u003d\",\n ra: \"b2todHRwLzMuMTAuMA\u003d\u003d\",\n rm: \"R0VU\",\n d: \"zOGkQGkYxYN36DBxdP1ySsz6tiLlkQkBxrGzqnIu8m7tz0oU3UVPcErPDphWYj5WNqtHEHwzhlsJKUSLzNPyF1yInxlQBEYRqsbZbWYnfRVmXQTZPnG6KBJ4fFYhX4YR8HXuSDwgJ6kngCW+ekC7vZsh2dnmUmvO6JN4PuTOyC97VAERuALWj/GlkDBQ1FQXR/wEWAqf3V6cCdlZcKXkD5UbRZzhxlw7EsTMnF01amXUMtLV6ggBEcriXWDkFTbFUZpQSUZjm0pwykFJOaXFZwvAaIA7PjQK2uMyTzv6QwZfL5OKpcXOguhdtUigVrQyNHEG1s1gHte0zPG1xGma6LWOrHYatLP5JW4mVJykht+HwwJ04RLICu1f0bJ6SM/yTBWHpnfZrs6hV05dlOmemvE9MQBytWhOi9lGKaVmYxmbgEY0alUGardqWWCxISXYPDhe4+Y0Rxpb6kX+lIiTtqiVSQ5PxiRy8lgQYV2Pp+qWJbe1iHJzMaqFpgJvI0ihjdKXl4KsmxsYZi8QhlDYJcTZyknVI0LHHdAGZUJUnjKGL23ec2f+nLxGn1ZECeib\",\n t: \"MTYxNDM2MTEyOC4wNjYwMDA\u003d\",\n m: \"pX0OPLFnDvuS66xf7tMswlvFBJ7CvM2jGx9C5ihYGOE\u003d\",\n i1: \"g+cJjv/W9/ggN52ebrJZSg\u003d\u003d\",\n i2: \"luhLCMllcfCNJTIko98vrQ\u003d\u003d\",\n uh: \"QNqr1PtsmAsBuHIaoM6zeJRgUdRT1sK83/SuOuA+LQM\u003d\",\n hh: \"I0kdqj2F0l7JNXvXS7ighNXMGXUM2prtK7PBi3zI0Kw\u003d\",\n }\n }\n window._cf_chl_enter \u003d function(){window._cf_chl_opt.p\u003d1};\n \n })();\n //]]\u003e\n \u003c/script\u003e \n \u003c/head\u003e \n \u003cbody\u003e \n \u003ctable width\u003d\"100%\" height\u003d\"100%\" cellpadding\u003d\"20\"\u003e \n \u003ctbody\u003e\n \u003ctr\u003e \n \u003ctd align\u003d\"center\" valign\u003d\"middle\"\u003e \n \u003cdiv class\u003d\"cf-browser-verification cf-im-under-attack\"\u003e \n \u003cnoscript\u003e \n \u003ch1 data-translate\u003d\"turn_on_js\" style\u003d\"color:#bd2426;\"\u003ePlease turn JavaScript on and reload the page.\u003c/h1\u003e \n \u003c/noscript\u003e \n \u003cdiv id\u003d\"cf-content\" style\u003d\"display:none\"\u003e \n \u003cdiv id\u003d\"cf-bubbles\"\u003e \n \u003cdiv class\u003d\"bubbles\"\u003e\u003c/div\u003e \n \u003cdiv class\u003d\"bubbles\"\u003e\u003c/div\u003e \n \u003cdiv class\u003d\"bubbles\"\u003e\u003c/div\u003e \n \u003c/div\u003e \n \u003ch1\u003e\u003cspan data-translate\u003d\"checking_browser\"\u003eChecking your browser before accessing\u003c/span\u003e mangazuki.co.\u003c/h1\u003e \n \u003cdiv id\u003d\"no-cookie-warning\" class\u003d\"cookie-warning\" data-translate\u003d\"turn_on_cookies\" style\u003d\"display:none\"\u003e \n \u003cp data-translate\u003d\"turn_on_cookies\" style\u003d\"color:#bd2426;\"\u003ePlease enable Cookies and reload the page.\u003c/p\u003e \n \u003c/div\u003e \n \u003cp data-translate\u003d\"process_is_automatic\"\u003eThis process is automatic. Your browser will redirect to your requested content shortly.\u003c/p\u003e \n \u003cp data-translate\u003d\"allow_5_secs\" id\u003d\"cf-spinner-allow-5-secs\"\u003ePlease allow up to 5 seconds…\u003c/p\u003e \n \u003cp data-translate\u003d\"redirecting\" id\u003d\"cf-spinner-redirecting\" style\u003d\"display:none\"\u003eRedirecting…\u003c/p\u003e \n \u003c/div\u003e \n \u003cform class\u003d\"challenge-form\" id\u003d\"challenge-form\" action\u003d\"/?__cf_chl_jschl_tk__\u003dc8ab60c47f906f94060319e5f52de899835eec8e-1614361128-0-AfLLJ88dk7y3cdA18ZmVK0qTd-Bjlb02LGoF7HoTOiYr1awxAMGzNap9BOcbQpDdyPIDcoKeMdqBULq_vLrG9wfL6LuiLweId9-hmMiFoA9BwSHR2sQk35BV6ZKAUGfnf4eWZ-aM78rAExX-29lgmjyGaCXSeKR_-VomlyUrQwt40_7MfrkyDxpzQw8abDIoy3ujNZJbhSTt0fBkvJapIMh26HzivQae9MkvrVuiL3U7RwY3261s-HzBjUfXXIcd9X1e1TiZqmc6-2HTHGCLvXPo--ywNyExoRrZmycRWVLM2xUXJtz1WSG_YWAHY28N1w\" method\u003d\"POST\" enctype\u003d\"application/x-www-form-urlencoded\"\u003e \n \u003cinput type\u003d\"hidden\" name\u003d\"r\" value\u003d\"946bc6f89d52ba0964e58e13a060262cc557b8bd-1614361128-0-AdebX22CmfOqZtfz6VfyprBwYzRQ+3Q4uck0LMh6cBBugBO9umDRhN/aSd2+4TEPN3+QP5/2zRLKckLh17N+YjHg0CV19AWqtaHEYhhrzQD9sye8brVMa2HLuRaq3TO3bhWWj+hY7bQu09zCIIxbKpW/JYNaMUA05QGyfNWOV4W/zt1KOPDzxj+iRD0vWQNnK2GQSFYlh5f2/WLVclTEQIqliSGTotnFuIi1MU9zaYfuX1Ol2WPuAwXlJoIfjRC/oNI3M7GVtRXDsKWkSea+3g3wQn2pB3oXX49saJ0s2J6uejnI741ifuZjp5LuWp4ZFst0bCUZPJO8jI/0Stkg/Xd9mEba43JqS+GKNq9npOkuiagZc+lURzLz2ECDtU0x+JWvOgZ2xSapT4ufdKrgVe0q1Bp8xm1QIvGQLFzGjDJtuTH7OcgoQ6gYV8/ty/6wUgrXfPzK47W0iIDrWjOmiN7Y/Gt1lBtj+8oQgzR6ABNZlLOf2IQ7Eauns+MX+4ks3vIK7WRM8KmgkGUcgB2FIwJSJJ573OjWqZkQQkPhEV2yjhIjT8tOzGX9C/nwIHF4i6vpvwSVRCtLjQx+OBdY6M0f+4fcB4TnVW7rR9R8mZ4+bGcCxJ4bNroadBAvkLcbaWJYod8pFdEa8iOHJETK4aAsg1zMixTU89Fj1zeE8JatJ8Je/m1D6fiLiO1KBkeLB3ov0uBUPVSH9R+DzxYOSbbu+LMt97QndIXBoarS2K72zemqjKbHYkTFtWuKvEbnELbOM8993H7SvtUHUKgiTaF+mWVTd1Rv4k8hywCu4cGOupiFUuif14PmEWFrFec0rVlKRDZWf60c/RJ7s/dSd0ZvZO+S2yom9C19EtZuTfnAiX8vYG84O9MsfOM/l+JtvY7yx0lgl7vj7/Genx6l4tOFxulO/Pvy0ZEnlWqNW5EJC491tqAL51C9btiGC5PHOCa6+tKcq2s5hcujAZzqm366hbNcyR8aVQSjY9cdmKImAilaNNiOdLP3Ci2Y13w5tIsO6ifRa+0LSyLPwwlZdEhQ16bWw8ObWrKel5aoJF2Jb2Dk71vaoVIBTCNuOeXdUtJsk1CEk6btguF+2bTj8TClicYRUGxxQM7GGGnIAAm85zmnrAQYsm701FN6SR2XnBpUjDjE5fRGR4EgBQ7LS4QT6tRmcN77FkKQUkiePR2Wq59rhPa6jXSthXPjPAB9FISMyJjuVazstV1taFUVzAp/A/CK2i0Ti6yaacu7sSxnbHykyw/kg87FYNTYw9gpSz0ICNJNanHX1+/vFGvq/cVh9syZVZjTQfPNoT61uQk677awNU5zTmAP/M9KKtMCxNTEezVSh3KrL9MCK5FDiWn8H/FFSRFTj6WyWZFoz0/r3VSQjcRdbw7soTy8wahb3Beko0KLPi802q76s74WUUM\u003d\"\u003e \n \u003cinput type\u003d\"hidden\" value\u003d\"e1ae30b322b2f3884861a95378ea1e08\" id\u003d\"jschl-vc\" name\u003d\"jschl_vc\"\u003e \n \u003c!-- \u003cinput type\u003d\"hidden\" value\u003d\"\" id\u003d\"jschl-vc\" name\u003d\"jschl_vc\"/\u003e --\u003e \n \u003cinput type\u003d\"hidden\" name\u003d\"pass\" value\u003d\"1614361132.066-Lv6AWAuxFD\"\u003e \n \u003cinput type\u003d\"hidden\" id\u003d\"jschl-answer\" name\u003d\"jschl_answer\"\u003e \n \u003c/form\u003e \n \u003cscript type\u003d\"text/javascript\"\u003e\n //\u003c![CDATA[\n (function(){\n var a \u003d document.getElementById(\u0027cf-content\u0027);\n a.style.display \u003d \u0027block\u0027;\n var isIE \u003d /(MSIE|Trident\\/|Edge\\/)/i.test(window.navigator.userAgent);\n var trkjs \u003d isIE ? new Image() : document.createElement(\u0027img\u0027);\n trkjs.setAttribute(\"src\", \"/cdn-cgi/images/trace/jschal/js/transparent.gif?ray\u003d627b711a599ff039\");\n trkjs.id \u003d \"trk_jschal_js\";\n trkjs.setAttribute(\"alt\", \"\");\n document.body.appendChild(trkjs);\n var cpo\u003ddocument.createElement(\u0027script\u0027);\n cpo.type\u003d\u0027text/javascript\u0027;\n cpo.src\u003d\"/cdn-cgi/challenge-platform/h/g/orchestrate/jsch/v1\";\n document.getElementsByTagName(\u0027head\u0027)[0].appendChild(cpo);\n }());\n //]]\u003e\n \u003c/script\u003e \n \u003cdiv id\u003d\"trk_jschal_nojs\" style\u003d\"background-image:url(\u0027/cdn-cgi/images/trace/jschal/nojs/transparent.gif?ray\u003d627b711a599ff039\u0027)\"\u003e \n \u003c/div\u003e \n \u003c/div\u003e \n \u003cdiv class\u003d\"attribution\"\u003e\n DDoS protection by \n \u003ca rel\u003d\"noopener noreferrer\" href\u003d\"https://www.cloudflare.com/5xx-error-landing/\" target\u003d\"_blank\"\u003eCloudflare\u003c/a\u003e \n \u003cbr\u003e \n \u003cspan class\u003d\"ray_id\"\u003eRay ID: \u003ccode\u003e627b711a599ff039\u003c/code\u003e\u003c/span\u003e \n \u003c/div\u003e \u003c/td\u003e \n \u003c/tr\u003e \n \u003c/tbody\u003e\n \u003c/table\u003e \n \u003c/body\u003e\n\u003c/html\u003e/","categories":[],"tags":"null"}""" -private const val MMRSOURCE_24 = """{"language":"pt-BR","name":"Remangas","base_url":"https://remangas.top","supports_latest":true,"isNsfw":false,"item_url":"https://remangas.top/manga/","categories":[{"id":"1","name":"Ação"},{"id":"2","name":"Aventura"},{"id":"3","name":"Comédia"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasia"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Histórico"},{"id":"11","name":"Terror"},{"id":"12","name":"Josei"},{"id":"13","name":"Artes Marciais"},{"id":"14","name":"Adulto"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mistério"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psicológico"},{"id":"19","name":"Romance"},{"id":"20","name":"Vida escolar"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Esporte"},{"id":"29","name":"Sobrenatural"},{"id":"30","name":"Tragédia"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"},{"id":"33","name":"Isekai"},{"id":"34","name":"Guerra"},{"id":"35","name":"Sobrevivência"},{"id":"36","name":"Romance?"}],"tags":[{"id":"seinen","name":"seinen"},{"id":"ecchi","name":"ecchi"},{"id":"harem","name":"harem"},{"id":"isekai","name":"isekai"},{"id":"guerra","name":"guerra"},{"id":"shounen","name":"shounen"},{"id":"18","name":"+18"},{"id":"adulto","name":"Adulto"},{"id":"fantasia","name":"Fantasia"},{"id":"romance","name":"Romance"},{"id":"vida-escolar","name":"Vida Escolar"},{"id":"acao","name":"Ação"},{"id":"misterio","name":"mistério"},{"id":"terror","name":"Terror"},{"id":"detetive","name":"Detetive"},{"id":"misterios","name":"Mistérios"},{"id":"incesto","name":"Incesto"},{"id":"comedia-romantica","name":"Comédia Romantica"},{"id":"alquimia","name":"Alquimia"},{"id":"manhua","name":"Manhua"},{"id":"colorido","name":"Colorido"},{"id":"antologia","name":"Antologia"},{"id":"dragoes","name":"Dragões"},{"id":"briga-de-rua","name":"Briga de Rua"},{"id":"anti-heroi","name":"Anti Herói"},{"id":"zoera","name":"Zoera"},{"id":"protagonista-overpower","name":"Protagonista Overpower"},{"id":"psicologico","name":"Psicológico"},{"id":"protagonista-badass","name":"Protagonista Badass"},{"id":"battleroyale","name":"Battleroyale"},{"id":"apocalispe-zumbi","name":"Apocalispe Zumbi"},{"id":"mc-nao-virjao","name":"Mc Não Virjão"},{"id":"escola-de-magia","name":"Escola de Magia"},{"id":"tensei","name":"Tensei"},{"id":"shota-badass","name":"Shota Badass"},{"id":"isekai-vai-e-volta","name":"Isekai Vai e Volta"},{"id":"gore","name":"gore"},{"id":"garota-monstro","name":"Garota Monstro"},{"id":"maid","name":"Maid"},{"id":"gal","name":"Gal"},{"id":"mordomo","name":"Mordomo"}]}""" -private const val MMRSOURCE_25 = """{"language":"pt-BR","name":"AnimaRegia","base_url":"https://animaregia.net","supports_latest":true,"isNsfw":false,"item_url":"http://animaregia.net/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_26 = """{"language":"tr","name":"MangaVadisi","base_url":"http://manga-v2.mangavadisi.org","supports_latest":true,"isNsfw":false,"item_url":"http://manga-v2.mangavadisi.org/manga/","categories":[{"id":"1","name":"Aksiyon"},{"id":"2","name":"Macera"},{"id":"3","name":"Komedi"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantastik"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Tarihi"},{"id":"11","name":"Korku"},{"id":"12","name":"Josei"},{"id":"13","name":"Dövüş Sanatları"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Gizem"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psikolojik"},{"id":"19","name":"Romantizm"},{"id":"20","name":"Okul Hayatı"},{"id":"21","name":"Bilim Kurgu"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Günlük Yaşam"},{"id":"28","name":"Spor"},{"id":"29","name":"Doğaüstü"},{"id":"30","name":"Trajedi"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_27 = """{"language":"id","name":"MangaID","base_url":"https://mangaid.click","supports_latest":true,"isNsfw":false,"item_url":"https://mangaid.click/manga/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"Psychological"},{"id":"18","name":"Romance"},{"id":"19","name":"School Life"},{"id":"20","name":"Sci-fi"},{"id":"21","name":"Seinen"},{"id":"22","name":"Shoujo"},{"id":"23","name":"Shoujo Ai"},{"id":"24","name":"Shounen"},{"id":"25","name":"Shounen Ai"},{"id":"26","name":"Slice of Life"},{"id":"27","name":"Sports"},{"id":"28","name":"Supernatural"},{"id":"29","name":"Tragedy"},{"id":"30","name":"Yaoi"},{"id":"31","name":"Yuri"},{"id":"32","name":"School"},{"id":"33","name":"Isekai"},{"id":"34","name":"Military"}],"tags":"null"}""" -private const val MMRSOURCE_28 = """{"language":"fr","name":"Jpmangas","base_url":"https://jpmangas.co","supports_latest":true,"isNsfw":false,"item_url":"https://jpmangas.co/lecture-en-ligne/","categories":[{"id":"1","name":"Action"},{"id":"2","name":"Adventure"},{"id":"3","name":"Comedy"},{"id":"4","name":"Doujinshi"},{"id":"5","name":"Drama"},{"id":"6","name":"Ecchi"},{"id":"7","name":"Fantasy"},{"id":"8","name":"Gender Bender"},{"id":"9","name":"Harem"},{"id":"10","name":"Historical"},{"id":"11","name":"Horror"},{"id":"12","name":"Josei"},{"id":"13","name":"Martial Arts"},{"id":"14","name":"Mature"},{"id":"15","name":"Mecha"},{"id":"16","name":"Mystery"},{"id":"17","name":"One Shot"},{"id":"18","name":"Psychological"},{"id":"19","name":"Romance"},{"id":"20","name":"School Life"},{"id":"21","name":"Sci-fi"},{"id":"22","name":"Seinen"},{"id":"23","name":"Shoujo"},{"id":"24","name":"Shoujo Ai"},{"id":"25","name":"Shounen"},{"id":"26","name":"Shounen Ai"},{"id":"27","name":"Slice of Life"},{"id":"28","name":"Sports"},{"id":"29","name":"Supernatural"},{"id":"30","name":"Tragedy"},{"id":"31","name":"Yaoi"},{"id":"32","name":"Yuri"}],"tags":"null"}""" -private const val MMRSOURCE_29 = """{"language":"fr","name":"Op-VF","base_url":"https://www.op-vf.com","supports_latest":true,"isNsfw":false,"item_url":"https://www.op-vf.com/manga/","categories":[],"tags":"null"}""" -private const val MMRSOURCE_30 = """{"language":"fr","name":"FR Scan","base_url":"https://www.frscan.me","supports_latest":false,"isNsfw":false,"item_url":"\u003chtml\u003e\n \u003chead\u003e\u003c/head\u003e\n \u003cbody\u003e\n Product activation error\n \u003c/body\u003e\n\u003c/html\u003e/","categories":[],"tags":"null"}""" -private const val MMRSOURCE_31 = """{"language":"other","name":"HentaiShark","base_url":"https://www.hentaishark.com","supports_latest":true,"isNsfw":true,"item_url":"https://www.hentaishark.com/manga/","categories":[{"id":"1","name":"Doujinshi"},{"id":"2","name":"Manga"},{"id":"3","name":"Western"},{"id":"4","name":"non-h"},{"id":"5","name":"imageset"},{"id":"6","name":"artistcg"},{"id":"7","name":"misc"}],"tags":"null"}""" -val SOURCES: List<String> get() = listOf(MMRSOURCE_1, MMRSOURCE_2, MMRSOURCE_3, MMRSOURCE_4, MMRSOURCE_5, MMRSOURCE_6, MMRSOURCE_7, MMRSOURCE_8, MMRSOURCE_9, MMRSOURCE_10, MMRSOURCE_11, MMRSOURCE_12, MMRSOURCE_13, MMRSOURCE_14, MMRSOURCE_15, MMRSOURCE_16, MMRSOURCE_17, MMRSOURCE_18, MMRSOURCE_19, MMRSOURCE_20, MMRSOURCE_21, MMRSOURCE_22, MMRSOURCE_23, MMRSOURCE_24, MMRSOURCE_25, MMRSOURCE_26, MMRSOURCE_27, MMRSOURCE_28, MMRSOURCE_29, MMRSOURCE_30, MMRSOURCE_31) diff --git a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/Generator.kt b/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/Generator.kt deleted file mode 100644 index 2af17d67b..000000000 --- a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/Generator.kt +++ /dev/null @@ -1,343 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mmrcms - -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.os.Build -import com.google.gson.Gson -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import java.io.File -import java.io.PrintWriter -import java.security.cert.CertificateException -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.util.concurrent.TimeUnit -import javax.net.ssl.SSLContext -import javax.net.ssl.TrustManager -import javax.net.ssl.X509TrustManager -import kotlin.jvm.Throws - -/** - * This class generates the sources for MMRCMS. - * Credit to nulldev for writing the original shell script - * - * CMS: https://getcyberworks.com/product/manga-reader-cms/ - */ - -class Generator { - - private var preRunTotal: String - - init { - System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,TLSv1.3") - preRunTotal = Regex("""MMRSOURCE_(\d+)""").findAll(File(relativePath).readText(Charsets.UTF_8)).last().groupValues[1] - } - - data class SourceData(val lang: String, val name: String, val baseUrl: String, val isNsfw: Boolean = false) - - @TargetApi(Build.VERSION_CODES.O) - fun generate() { - val buffer = StringBuffer() - val dateTime = ZonedDateTime.now() - val formattedDate = dateTime.format(DateTimeFormatter.RFC_1123_DATE_TIME) - buffer.append("package eu.kanade.tachiyomi.extension.all.mmrcms") - buffer.append("\n\n// GENERATED FILE, DO NOT MODIFY!\n// Generated $formattedDate\n\n") - var number = 1 - sources.forEach { - try { - val map = mutableMapOf<String, Any>() - map["language"] = it.lang - map["name"] = it.name - map["base_url"] = it.baseUrl - map["supports_latest"] = supportsLatest(it.baseUrl) - map["isNsfw"] = it.isNsfw - - val advancedSearchDocument = getDocument("${it.baseUrl}/advanced-search", false) - - var parseCategories = mutableListOf<Map<String, String>>() - if (advancedSearchDocument != null) { - parseCategories = parseCategories(advancedSearchDocument) - } - - val homePageDocument = getDocument(it.baseUrl) - - val itemUrl = getItemUrl(homePageDocument, it.baseUrl) - - var prefix = itemUrl.substringAfterLast("/").substringBeforeLast("/") - - // Sometimes itemUrl is the root of the website, and thus the prefix found is the website address. - // In this case, we set the default prefix as "manga". - if (prefix.startsWith("www") || prefix.startsWith("wwv")) { - prefix = "manga" - } - - val mangaListDocument = getDocument("${it.baseUrl}/$prefix-list")!! - - if (parseCategories.isEmpty()) { - parseCategories = parseCategories(mangaListDocument) - } - map["item_url"] = "$itemUrl/" - map["categories"] = parseCategories - val tags = parseTags(mangaListDocument) - map["tags"] = "null" - if (tags.size in 1..49) { - map["tags"] = tags - } - - if (!itemUrl.startsWith(it.baseUrl)) println("**Note: ${it.name} URL does not match! Check for changes: \n ${it.baseUrl} vs $itemUrl") - - val toJson = Gson().toJson(map) - - buffer.append("private const val MMRSOURCE_$number = \"\"\"$toJson\"\"\"\n") - number++ - } catch (e: Exception) { - println("error generating source ${it.name} ${e.printStackTrace()}") - } - } - - buffer.append("val SOURCES: List<String> get() = listOf(") - for (i in 1 until number) { - buffer.append("MMRSOURCE_$i") - when (i) { - number - 1 -> { - buffer.append(")\n") - } - else -> { - buffer.append(", ") - } - } - } - println("Pre-run sources: $preRunTotal") - println("Post-run sources: ${number - 1}") - val writer = PrintWriter(relativePath) - writer.write(buffer.toString()) - writer.close() - } - - private fun getDocument(url: String, printStackTrace: Boolean = true): Document? { - val serverCheck = arrayOf("cloudflare-nginx", "cloudflare") - - try { - val request = Request.Builder().url(url) - getOkHttpClient().newCall(request.build()).execute().let { response -> - // Bypass Cloudflare ("Please wait 5 seconds" page) - if (response.code() == 503 && response.header("Server") in serverCheck) { - var cookie = "${response.header("Set-Cookie")!!.substringBefore(";")}; " - Jsoup.parse(response.body()!!.string()).let { document -> - val path = document.select("[id=\"challenge-form\"]").attr("action") - val chk = document.select("[name=\"s\"]").attr("value") - getOkHttpClient().newCall(Request.Builder().url("$url/$path?s=$chk").build()).execute().let { solved -> - cookie += solved.header("Set-Cookie")!!.substringBefore(";") - request.addHeader("Cookie", cookie).build().let { - return Jsoup.parse(getOkHttpClient().newCall(it).execute().body()?.string()) - } - } - } - } - if (response.code() == 200) { - return Jsoup.parse(response.body()?.string()) - } - } - } catch (e: Exception) { - if (printStackTrace) { - e.printStackTrace() - } - } - return null - } - - private fun parseTags(mangaListDocument: Document): MutableList<Map<String, String>> { - val elements = mangaListDocument.select("div.tag-links a") - - if (elements.isEmpty()) { - return mutableListOf() - } - val array = mutableListOf<Map<String, String>>() - elements.forEach { - val map = mutableMapOf<String, String>() - map["id"] = it.attr("href").substringAfterLast("/") - map["name"] = it.text() - array.add(map) - } - return array - } - - private fun getItemUrl(document: Document?, url: String): String { - document ?: throw Exception("Couldn't get document for: $url") - return document.toString().substringAfter("showURL = \"").substringAfter("showURL=\"").substringBefore("/SELECTION\";") - - // Some websites like mangasyuri use javascript minifiers, and thus "showURL = " becomes "showURL="https://mangasyuri.net/manga/SELECTION"" - // (without spaces). Hence the double substringAfter. - } - - private fun supportsLatest(third: String): Boolean { - val document = getDocument("$third/latest-release?page=1", false) ?: return false - return document.select("div.mangalist div.manga-item a, div.grid-manga tr").isNotEmpty() - } - - private fun parseCategories(document: Document): MutableList<Map<String, String>> { - val array = mutableListOf<Map<String, String>>() - val elements = document.select("select[name^=categories] option, a.category") - if (elements.size == 0) { - return mutableListOf() - } - var id = 1 - elements.forEach { - val map = mutableMapOf<String, String>() - map["id"] = id.toString() - map["name"] = it.text() - array.add(map) - id++ - } - return array - } - - @Throws(Exception::class) - private fun getOkHttpClient(): OkHttpClient { - val trustAllCerts = arrayOf<TrustManager>( - object : X509TrustManager { - @SuppressLint("TrustAllX509TrustManager") - @Throws(CertificateException::class) - override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) { - } - - @SuppressLint("TrustAllX509TrustManager") - @Throws(CertificateException::class) - override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) { - } - - override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> { - return arrayOf() - } - } - ) - - // Install the all-trusting trust manager - - val sc = SSLContext.getInstance("SSL") - sc.init(null, trustAllCerts, java.security.SecureRandom()) - val sslSocketFactory = sc.socketFactory - - // Create all-trusting host name verifier - // Install the all-trusting host verifier - - return OkHttpClient.Builder() - .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) - .hostnameVerifier { _, _ -> true } - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build() - } - - companion object { - val sources = listOf( - SourceData("ar", "مانجا اون لاين", "https://onma.me"), - SourceData("en", "Read Comics Online", "https://readcomicsonline.ru"), - SourceData("en", "Fallen Angels", "https://manga.fascans.com"), - SourceData("en", "Zahard", "https://zahard.top"), - SourceData("en", "Manhwas Men", "https://manhwas.men"), - SourceData("fr", "Scan FR", "https://www.scan-fr.cc"), - SourceData("fr", "Scan VF", "https://www.scan-vf.net"), - SourceData("fr", "Scan OP", "https://scan-op.cc"), - SourceData("id", "Komikid", "https://www.komikid.com"), - SourceData("pt-BR", "Mangás Yuri", "https://mangasyuri.net"), - SourceData("pl", "Nikushima", "http://azbivo.webd.pro"), - SourceData("tr", "MangaHanta", "http://mangahanta.com"), - SourceData("vi", "Fallen Angels Scans", "https://truyen.fascans.com"), - SourceData("es", "LeoManga", "https://leomanga.me"), - SourceData("es", "submanga", "https://submanga.io"), - SourceData("es", "Mangadoor", "https://mangadoor.com"), - SourceData("es", "Mangas.pw", "https://mangas.in"), - SourceData("bg", "Utsukushii", "https://manga.utsukushii-bg.com"), - SourceData("pl", "Phoenix-Scans", "https://phoenix-scans.pl"), - SourceData("tr", "Puzzmos", "https://puzzmos.com"), - SourceData("fr", "Scan-1", "https://wwv.scan-1.com"), - SourceData("fr", "Lelscan-VF", "https://lelscan-vf.co"), - SourceData("id", "Komik Manga", "https://adm.komikmanga.com"), - SourceData("ko", "Mangazuki Raws", "https://raws.mangazuki.co"), - SourceData("en", "Mangazuki", "https://mangazuki.co/"), - SourceData("pt-BR", "Remangas", "https://remangas.top"), - SourceData("pt-BR", "AnimaRegia", "https://animaregia.net"), - SourceData("tr", "MangaVadisi", "http://manga-v2.mangavadisi.org"), - SourceData("id", "MangaID", "https://mangaid.click"), - SourceData("fr", "Jpmangas", "https://jpmangas.co"), - SourceData("fr", "Op-VF", "https://www.op-vf.com"), - SourceData("fr", "FR Scan", "https://www.frscan.me"), - // NOTE: THIS SOURCE CONTAINS A CUSTOM LANGUAGE SYSTEM (which will be ignored)! - SourceData("other", "HentaiShark", "https://www.hentaishark.com", true) - ) - // Changed CMS - // SourceData("es", "Tumangaonline.co", "http://tumangaonline.com"), - // SourceData("id", "MangaYu", "https://mangayu.com"), - // SourceData("en", "MangaTreat Scans", "http://www.mangatreat.com"), - // SourceData("en", "Chibi Manga Reader", "https://www.cmreader.info"), - // SourceData("tr", "Epikmanga", "https://www.epikmanga.com"), - // SourceData("en", "Hatigarm Scans", "https://hatigarmscans.net"), - // Went offline - // SourceData("ru", "Japit Comics", "https://j-comics.ru"), - // SourceData("es", "Universo Yuri", "https://universoyuri.com"), - // SourceData("pl", "Dracaena", "https://dracaena.webd.pl/czytnik"), - // SourceData("pt-BR", "Comic Space", "https://www.comicspace.com.br"), - // SourceData("pl", "ToraScans", "http://torascans.pl"), - // SourceData("en", "White Cloud Pavilion", "https://www.whitecloudpavilion.com/manga/free"), - // SourceData("en", "Biamam Scans", "https://biamam.com"), - // SourceData("en", "Mangawww Reader", "https://mangawww.club"), - // SourceData("ru", "Anigai clan", "http://anigai.ru"), - // SourceData("en", "ZXComic", "http://zxcomic.com"), - // SourceData("es", "SOS Scanlation", "https://sosscanlation.com"), - // SourceData("es", "MangaCasa", "https://mangacasa.com")) - // SourceData("ja", "RAW MANGA READER", "https://rawmanga.site"), - // SourceData("ar", "Manga FYI", "http://mangafyi.com/manga/arabic"), - // SourceData("en", "MangaRoot", "http://mangaroot.com"), - // SourceData("en", "MangaForLife", "http://manga4ever.com"), - // SourceData("en", "Manga Spoil", "http://mangaspoil.com"), - // SourceData("en", "MangaBlue", "http://mangablue.com"), - // SourceData("en", "Manga Forest", "https://mangaforest.com"), - // SourceData("en", "DManga", "http://dmanga.website"), - // SourceData("en", "DB Manga", "http://dbmanga.com"), - // SourceData("en", "Mangacox", "http://mangacox.com"), - // SourceData("en", "GO Manhwa", "http://gomanhwa.xyz"), - // SourceData("en", "KoManga", "https://komanga.net"), - // SourceData("en", "Manganimecan", "http://manganimecan.com"), - // SourceData("en", "Hentai2Manga", "http://hentai2manga.com"), - // SourceData("en", "4 Manga", "http://4-manga.com"), - // SourceData("en", "XYXX.INFO", "http://xyxx.info"), - // SourceData("en", "Isekai Manga Reader", "https://isekaimanga.club"), - // SourceData("fa", "TrinityReader", "http://trinityreader.pw"), - // SourceData("fr", "Manga-LEL", "https://www.manga-lel.com"), - // SourceData("fr", "Manga Etonnia", "https://www.etonnia.com"), - // SourceData("fr", "ScanFR.com"), "http://scanfr.com"), - // SourceData("fr", "Manga FYI", "http://mangafyi.com/manga/french"), - // SourceData("fr", "scans-manga", "http://scans-manga.com"), - // SourceData("fr", "Henka no Kaze", "http://henkanokazelel.esy.es/upload"), - // SourceData("fr", "Tous Vos Scans", "http://www.tous-vos-scans.com"), - // SourceData("id", "Manga Desu", "http://mangadesu.net"), - // SourceData("id", "Komik Mangafire.ID", "http://go.mangafire.id"), - // SourceData("id", "MangaOnline", "https://mangaonline.web.id"), - // SourceData("id", "MangaNesia", "https://manganesia.com"), - // SourceData("id", "MangaID", "https://mangaid.me" - // SourceData("id", "Manga Seru", "http://www.mangaseru.top" - // SourceData("id", "Manga FYI", "http://mangafyi.com/manga/indonesian" - // SourceData("id", "Bacamangaku", "http://www.bacamangaku.com"), - // SourceData("id", "Indo Manga Reader", "http://indomangareader.com"), - // SourceData("it", "Kingdom Italia Reader", "http://kireader.altervista.org"), - // SourceData("ja", "IchigoBook", "http://ichigobook.com"), - // SourceData("ja", "Mangaraw Online", "http://mangaraw.online" - // SourceData("ja", "Mangazuki RAWS", "https://raws.mangazuki.co"), - // SourceData("ja", "MangaRAW", "https://www.mgraw.com"), - // SourceData("ja", "マンガ/漫画 マガジン/雑誌 raw", "http://netabare-manga-raw.com"), - // SourceData("ru", "NAKAMA", "http://nakama.ru"), - // SourceData("tr", "MangAoi", "http://mangaoi.com"), - // SourceData("tr", "ManhuaTR", "http://www.manhua-tr.com"), - - val relativePath = System.getProperty("user.dir") + "/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/GeneratedSources.kt" - - @JvmStatic - fun main(args: Array<String>) { - Generator().generate() - } - } -} diff --git a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/MyMangaReaderCMSSource.kt b/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/MyMangaReaderCMSSource.kt deleted file mode 100644 index 644af8f64..000000000 --- a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/MyMangaReaderCMSSource.kt +++ /dev/null @@ -1,510 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mmrcms - -import android.annotation.SuppressLint -import android.net.Uri -import android.util.Base64 -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element -import rx.Observable -import java.net.URLDecoder -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -open class MyMangaReaderCMSSource( - final override val lang: String, - final override val name: String, - final override val baseUrl: String, - final override val supportsLatest: Boolean, - private val itemUrl: String, - private val categoryMappings: List<Pair<String, String>>, - private val tagMappings: List<Pair<String, String>>? -) : HttpSource() { - private val jsonParser = JsonParser() - private val itemUrlPath = Uri.parse(itemUrl).pathSegments.firstOrNull() - private val parsedBaseUrl = Uri.parse(baseUrl) - - /** - * Hardcode IDs for sources for which we altered name or lang - */ - override val id: Long = when (name) { - "Comic Space" -> 1847392744200215680 - "Mangás Yuri" -> 6456162511058446409 - "AnimaRegia" -> 4378659695320121364 - else -> super.id - } - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build() - - override fun popularMangaRequest(page: Int): Request { - return when (name) { - "Utsukushii" -> GET("$baseUrl/manga-list", headers) - else -> GET("$baseUrl/filterList?page=$page&sortBy=views&asc=false", headers) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url: Uri.Builder - when { - name == "Mangas.pw" -> { - url = Uri.parse("$baseUrl/search")!!.buildUpon() - url.appendQueryParameter("q", query) - } - query.isNotBlank() -> { - url = Uri.parse("$baseUrl/search")!!.buildUpon() - url.appendQueryParameter("query", query) - } - else -> { - url = Uri.parse("$baseUrl/filterList?page=$page")!!.buildUpon() - filters.filterIsInstance<UriFilter>() - .forEach { it.addToUri(url) } - } - } - return GET(url.toString(), headers) - } - - /** - * If the usual search engine isn't available, search through the list of titles with this - */ - private fun selfSearch(query: String): Observable<MangasPage> { - return client.newCall(GET("$baseUrl/changeMangaList?type=text", headers)) - .asObservableSuccess() - .map { response -> - val mangas = response.asJsoup().select("ul.manga-list a") - .filter { it.text().contains(query, ignoreCase = true) } - .map { - SManga.create().apply { - title = it.text() - setUrlWithoutDomain(it.attr("abs:href")) - thumbnail_url = coverGuess(null, it.attr("abs:href")) - } - } - MangasPage(mangas, false) - } - } - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/latest-release?page=$page", headers) - - override fun popularMangaParse(response: Response) = internalMangaParse(response) - override fun searchMangaParse(response: Response): MangasPage { - return if (listOf("query", "q").any { it in response.request().url().queryParameterNames() }) { - // If a search query was specified, use search instead! - val jsonArray = jsonParser.parse(response.body()!!.string()).let { - if (name == "Mangas.pw") it.array else it["suggestions"].array - } - MangasPage( - jsonArray - .map { - SManga.create().apply { - val segment = it["data"].string - url = getUrlWithoutBaseUrl(itemUrl + segment) - title = it["value"].string - - // Guess thumbnails - // thumbnail_url = "$baseUrl/uploads/manga/$segment/cover/cover_250x350.jpg" - } - }, - false - ) - } else { - internalMangaParse(response) - } - } - - private val latestTitles = mutableSetOf<String>() - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - - if (document.location().contains("page=1")) latestTitles.clear() - - val mangas = document.select(latestUpdatesSelector()) - .let { elements -> - when { - // Mangas.pw - elements.select("a.fa-info-circle + a").firstOrNull()?.hasText() == true -> elements.map { latestUpdatesFromElement(it, "a.fa-info-circle + a") } - // List layout (most sources) - elements.select("a").firstOrNull()?.hasText() == true -> elements.map { latestUpdatesFromElement(it, "a") } - // Grid layout (e.g. MangaID) - else -> document.select(gridLatestUpdatesSelector()).map { gridLatestUpdatesFromElement(it) } - } - } - .filterNotNull() - - return MangasPage(mangas, document.select(latestUpdatesNextPageSelector()) != null) - } - private fun latestUpdatesSelector() = "div.mangalist div.manga-item" - private fun latestUpdatesNextPageSelector() = "a[rel=next]" - private fun latestUpdatesFromElement(element: Element, urlSelector: String): SManga? { - return element.select(urlSelector).first().let { titleElement -> - if (titleElement.text() in latestTitles) { - null - } else { - latestTitles.add(titleElement.text()) - SManga.create().apply { - url = titleElement.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain - title = titleElement.text().trim() - thumbnail_url = "$baseUrl/uploads/manga/${url.substringAfterLast('/')}/cover/cover_250x350.jpg" - } - } - } - } - private fun gridLatestUpdatesSelector() = "div.mangalist div.manga-item, div.grid-manga tr" - private fun gridLatestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - element.select("a.chart-title").let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - thumbnail_url = element.select("img").attr("abs:src") - } - - private fun internalMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val internalMangaSelector = when (name) { - "Utsukushii" -> "div.content div.col-sm-6" - else -> "div[class^=col-sm], div.col-xs-6" - } - return MangasPage( - document.select(internalMangaSelector).map { - SManga.create().apply { - val urlElement = it.getElementsByClass("chart-title") - if (urlElement.size == 0) { - url = getUrlWithoutBaseUrl(it.select("a").attr("href")) - title = it.select("div.caption").text() - it.select("div.caption div").text().let { if (it.isNotEmpty()) title = title.substringBefore(it) } // To clean submanga's titles without breaking hentaishark's - } else { - url = getUrlWithoutBaseUrl(urlElement.attr("href")) - title = urlElement.text().trim() - } - - it.select("img").let { img -> - thumbnail_url = when { - it.hasAttr("data-background-image") -> it.attr("data-background-image") // Utsukushii - img.hasAttr("data-src") -> coverGuess(img.attr("abs:data-src"), url) - else -> coverGuess(img.attr("abs:src"), url) - } - } - } - }, - document.select(".pagination a[rel=next]").isNotEmpty() - ) - } - - // Guess thumbnails on broken websites - private fun coverGuess(url: String?, mangaUrl: String): String? { - return if (url?.endsWith("no-image.png") == true) { - "$baseUrl/uploads/manga/${mangaUrl.substringAfterLast('/')}/cover/cover_250x350.jpg" - } else { - url - } - } - - private fun getUrlWithoutBaseUrl(newUrl: String): String { - val parsedNewUrl = Uri.parse(newUrl) - val newPathSegments = parsedNewUrl.pathSegments.toMutableList() - - for (i in parsedBaseUrl.pathSegments) { - if (i.trim().equals(newPathSegments.first(), true)) { - newPathSegments.removeAt(0) - } else break - } - - val builtUrl = parsedNewUrl.buildUpon().path("/") - newPathSegments.forEach { builtUrl.appendPath(it) } - - var out = builtUrl.build().encodedPath!! - if (parsedNewUrl.encodedQuery != null) - out += "?" + parsedNewUrl.encodedQuery - if (parsedNewUrl.encodedFragment != null) - out += "#" + parsedNewUrl.encodedFragment - - return out - } - - @SuppressLint("DefaultLocale") - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val document = response.asJsoup() - document.select("h2.listmanga-header, h2.widget-title").firstOrNull()?.text()?.trim()?.let { title = it } - thumbnail_url = coverGuess(document.select(".row [class^=img-responsive]").firstOrNull()?.attr("abs:src"), document.location()) - description = document.select(".row .well p").text().trim() - - val detailAuthor = setOf("author(s)", "autor(es)", "auteur(s)", "著作", "yazar(lar)", "mangaka(lar)", "pengarang/penulis", "pengarang", "penulis", "autor", "المؤلف", "перевод", "autor/autorzy") - val detailArtist = setOf("artist(s)", "artiste(s)", "sanatçi(lar)", "artista(s)", "artist(s)/ilustrator", "الرسام", "seniman", "rysownik/rysownicy") - val detailGenre = setOf("categories", "categorías", "catégories", "ジャンル", "kategoriler", "categorias", "kategorie", "التصنيفات", "жанр", "kategori", "tagi") - val detailStatus = setOf("status", "statut", "estado", "状態", "durum", "الحالة", "статус") - val detailStatusComplete = setOf("complete", "مكتملة", "complet", "completo", "zakończone") - val detailStatusOngoing = setOf("ongoing", "مستمرة", "en cours", "em lançamento", "prace w toku") - val detailDescription = setOf("description", "resumen") - - for (element in document.select(".row .dl-horizontal dt")) { - when (element.text().trim().toLowerCase()) { - in detailAuthor -> author = element.nextElementSibling().text() - in detailArtist -> artist = element.nextElementSibling().text() - in detailGenre -> genre = element.nextElementSibling().select("a").joinToString { - it.text().trim() - } - in detailStatus -> status = when (element.nextElementSibling().text().trim().toLowerCase()) { - in detailStatusComplete -> SManga.COMPLETED - in detailStatusOngoing -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } - } - // When details are in a .panel instead of .row (ES sources) - for (element in document.select("div.panel span.list-group-item")) { - when (element.select("b").text().toLowerCase().substringBefore(":")) { - in detailAuthor -> author = element.select("b + a").text() - in detailArtist -> artist = element.select("b + a").text() - in detailGenre -> genre = element.getElementsByTag("a").joinToString { - it.text().trim() - } - in detailStatus -> status = when (element.select("b + span.label").text().toLowerCase()) { - in detailStatusComplete -> SManga.COMPLETED - in detailStatusOngoing -> SManga.ONGOING - else -> SManga.UNKNOWN - } - in detailDescription -> description = element.ownText() - } - } - } - - /** - * Parses the response from the site and returns a list of chapters. - * - * Overriden to allow for null chapters - * - * @param response the response from the site. - */ - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - return document.select(chapterListSelector()).mapNotNull { nullableChapterFromElement(it) } - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. - */ - private fun chapterListSelector() = "ul[class^=chapters] > li:not(.btn), table.table tr" - // Some websites add characters after "chapters" thus the need of checking classes that starts with "chapters" - - /** - * titleWrapper can have multiple "a" elements, filter to the first that contains letters (i.e. not "" or # as is possible) - */ - private val urlRegex = Regex("""[a-zA-z]""") - - /** - * Returns a chapter from the given element. - * - * @param element an element obtained from [chapterListSelector]. - */ - private fun nullableChapterFromElement(element: Element): SChapter? { - val chapter = SChapter.create() - - try { - val titleWrapper = if (name == "Mangas.pw") element.select("i a").first() else element.select("[class^=chapter-title-rtl]").first() - // Some websites add characters after "..-rtl" thus the need of checking classes that starts with that - val url = titleWrapper.getElementsByTag("a") - .first { it.attr("href").contains(urlRegex) } - .attr("href") - - // Ensure chapter actually links to a manga - // Some websites use the chapters box to link to post announcements - // The check is skipped if mangas are stored in the root of the website (ex '/one-piece' without a segment like '/manga/one-piece') - if (itemUrlPath != null && !Uri.parse(url).pathSegments.firstOrNull().equals(itemUrlPath, true)) { - return null - } - - chapter.url = getUrlWithoutBaseUrl(url) - chapter.name = titleWrapper.text() - - // Parse date - val dateText = element.getElementsByClass("date-chapter-title-rtl").text().trim() - chapter.date_upload = parseDate(dateText) - - return chapter - } catch (e: NullPointerException) { - // For chapter list in a table - if (element.select("td").hasText()) { - element.select("td a").let { - chapter.setUrlWithoutDomain(it.attr("href")) - chapter.name = it.text() - } - val tableDateText = element.select("td + td").text() - chapter.date_upload = parseDate(tableDateText) - - return chapter - } - } - - return null - } - - private fun parseDate(dateText: String): Long { - return try { - DATE_FORMAT.parse(dateText)?.time ?: 0 - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(response: Response) = response.asJsoup().select("#all > .img-responsive") - .mapIndexed { i, e -> - var url = (if (e.hasAttr("data-src")) e.attr("abs:data-src") else e.attr("abs:src")).trim() - - // Mangas.pw encodes some of their urls, decode them - if (name.contains("Mangas.pw") && !url.contains(".")) { - url = Base64.decode(url.substringAfter("//"), Base64.DEFAULT).toString(Charsets.UTF_8).substringBefore("=") - url = URLDecoder.decode(url, "UTF-8") - } - - Page(i, "", url) - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Unused method called!") - - private fun getInitialFilterList() = listOf<Filter<*>>( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - AuthorFilter(), - UriSelectFilter( - "Category", - "cat", - arrayOf( - "" to "Any", - *categoryMappings.toTypedArray() - ) - ), - UriSelectFilter( - "Begins with", - "alpha", - arrayOf( - "" to "Any", - *"#ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray().map { - Pair(it.toString(), it.toString()) - }.toTypedArray() - ) - ), - SortFilter() - ) - - /** - * Returns the list of filters for the source. - */ - override fun getFilterList(): FilterList { - return when { - name == "Mangas.pw" -> FilterList() - tagMappings != null -> { - FilterList( - getInitialFilterList() + UriSelectFilter( - "Tag", - "tag", - arrayOf( - "" to "Any", - *tagMappings.toTypedArray() - ) - ) - ) - } - else -> FilterList(getInitialFilterList()) - } - } - - /** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ - // vals: <name, display> - open class UriSelectFilter( - displayName: String, - private val uriParam: String, - private val vals: Array<Pair<String, String>>, - private val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) - uri.appendQueryParameter(uriParam, vals[state].first) - } - } - - class AuthorFilter : Filter.Text("Author"), UriFilter { - override fun addToUri(uri: Uri.Builder) { - uri.appendQueryParameter("author", state) - } - } - - class SortFilter : - Filter.Sort( - "Sort", - sortables.map { it.second }.toTypedArray(), - Selection(0, true) - ), - UriFilter { - override fun addToUri(uri: Uri.Builder) { - uri.appendQueryParameter("sortBy", sortables[state!!.index].first) - uri.appendQueryParameter("asc", state!!.ascending.toString()) - } - - companion object { - private val sortables = arrayOf( - "name" to "Name", - "views" to "Popularity", - "last_release" to "Last update" - ) - } - } - - /** - * Represents a filter that is able to modify a URI. - */ - interface UriFilter { - fun addToUri(uri: Uri.Builder) - } - - companion object { - private val DATE_FORMAT = SimpleDateFormat("d MMM. yyyy", Locale.US) - } -} - -@Nsfw -class MyMangaReaderCMSSourceNsfw( - lang: String, - name: String, - baseUrl: String, - supportsLatest: Boolean, - itemUrl: String, - categoryMappings: List<Pair<String, String>>, - tagMappings: List<Pair<String, String>>? -) : MyMangaReaderCMSSource( - lang, - name, - baseUrl, - supportsLatest, - itemUrl, - categoryMappings, - tagMappings -) diff --git a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/MyMangaReaderCMSSources.kt b/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/MyMangaReaderCMSSources.kt deleted file mode 100644 index 92305c0cb..000000000 --- a/src/all/mmrcms/src/eu/kanade/tachiyomi/extension/all/mmrcms/MyMangaReaderCMSSources.kt +++ /dev/null @@ -1,101 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.mmrcms - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.bool -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.source.SourceFactory - -class MyMangaReaderCMSSources : SourceFactory { - /** - * Create a new copy of the sources - * @return The created sources - */ - override fun createSources() = parseSources(SOURCES) - - /** - * Parse a List of JSON sources into a list of `MyMangaReaderCMSSource`s - * - * Example JSON : - * ``` - * { - * "language": "en", - * "name": "Example manga reader", - * "base_url": "https://example.com", - * "supports_latest": true, - * "item_url": "https://example.com/manga/", - * "categories": [ - * {"id": "stuff", "name": "Stuff"}, - * {"id": "test", "name": "Test"} - * ], - * "tags": [ - * {"id": "action", "name": "Action"}, - * {"id": "adventure", "name": "Adventure"} - * ] - * } - * - * - * Sources that do not supports tags may use `null` instead of a list of json objects - * - * @param sourceString The List of JSON strings 1 entry = one source - * @return The list of parsed sources - */ - private fun parseSources(sourceString: List<String>): List<MyMangaReaderCMSSource> { - val parser = JsonParser() - return sourceString.map { - val jsonObject = parser.parse(it) as JsonObject - - val language = jsonObject["language"].string - val name = jsonObject["name"].string - val baseUrl = jsonObject["base_url"].string - val supportsLatest = jsonObject["supports_latest"].bool - val itemUrl = jsonObject["item_url"].string - val categories = mapToPairs(jsonObject["categories"].array) - var tags = emptyList<Pair<String, String>>() - if (jsonObject["tags"].isJsonArray) { - tags = mapToPairs(jsonObject["tags"].asJsonArray) - } - val isNsfw = jsonObject["isNsfw"].bool - - if (isNsfw) { - MyMangaReaderCMSSourceNsfw( - language, - name, - baseUrl, - supportsLatest, - itemUrl, - categories, - tags - ) - } else { - MyMangaReaderCMSSource( - language, - name, - baseUrl, - supportsLatest, - itemUrl, - categories, - tags - ) - } - } - } - - /** - * Map an array of JSON objects to pairs. Each JSON object must have - * the following properties: - * - * id: first item in pair - * name: second item in pair - * - * @param array The array to process - * @return The new list of pairs - */ - private fun mapToPairs(array: JsonArray): List<Pair<String, String>> = array.map { - it as JsonObject - - it["id"].string to it["name"].string - } -} diff --git a/src/all/myreadingmanga/AndroidManifest.xml b/src/all/myreadingmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/myreadingmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/myreadingmanga/build.gradle b/src/all/myreadingmanga/build.gradle deleted file mode 100644 index c13f5f942..000000000 --- a/src/all/myreadingmanga/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MyReadingManga' - pkgNameSuffix = 'all.myreadingmanga' - extClass = '.MyReadingMangaFactory' - extVersionCode = 41 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/myreadingmanga/res/mipmap-hdpi/ic_launcher.png b/src/all/myreadingmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7727ceabc..000000000 Binary files a/src/all/myreadingmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/myreadingmanga/res/mipmap-mdpi/ic_launcher.png b/src/all/myreadingmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4350ed268..000000000 Binary files a/src/all/myreadingmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/myreadingmanga/res/mipmap-xhdpi/ic_launcher.png b/src/all/myreadingmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e55596ab4..000000000 Binary files a/src/all/myreadingmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/myreadingmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/all/myreadingmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e3089a76f..000000000 Binary files a/src/all/myreadingmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/myreadingmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/myreadingmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7bffb3556..000000000 Binary files a/src/all/myreadingmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/myreadingmanga/res/web_hi_res_512.png b/src/all/myreadingmanga/res/web_hi_res_512.png deleted file mode 100644 index c1ab5b326..000000000 Binary files a/src/all/myreadingmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt deleted file mode 100644 index 2fc471020..000000000 --- a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingManga.kt +++ /dev/null @@ -1,314 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.myreadingmanga - -import android.annotation.SuppressLint -import android.net.Uri -import android.webkit.URLUtil -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -open class MyReadingManga(override val lang: String, private val siteLang: String, private val latestLang: String) : ParsedHttpSource() { - - // Basic Info - override val name = "MyReadingManga" - final override val baseUrl = "https://myreadingmanga.info" - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build()!! - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", baseUrl) - - // Popular - Random - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/search/?wpsolr_sort=sort_by_random&wpsolr_page=$page&wpsolr_fq[0]=lang_str:$siteLang", headers) // Random Manga as returned by search - } - - override fun popularMangaParse(response: Response): MangasPage { - if (!filtersCached) { - cachedPagesUrls.onEach { filterAssist(it.value) } - filtersCached = true - } - return searchMangaParse(response) - } - override fun popularMangaNextPageSelector() = throw Exception("Not used") - override fun popularMangaSelector() = throw Exception("Not used") - override fun popularMangaFromElement(element: Element) = throw Exception("Not used") - - // Latest - @SuppressLint("DefaultLocale") - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/lang/${latestLang.toLowerCase()}" + if (page > 1) "/page/$page/" else "", headers) // Home Page - Latest Manga - } - - override fun latestUpdatesNextPageSelector() = "li.pagination-next" - override fun latestUpdatesSelector() = "article" - override fun latestUpdatesFromElement(element: Element) = buildManga(element.select("a[rel]").first(), element.select("a.entry-image-link img").first()) - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filterList = if (filters.isEmpty()) getFilterList() else filters - // whether enforce language is true will change the index of the loop below - val indexModifier = filterList.filterIsInstance<EnforceLanguageFilter>().first().indexModifier() - - val uri = Uri.parse("$baseUrl/search/").buildUpon() - .appendQueryParameter("wpsolr_q", query) - filterList.forEachIndexed { i, filter -> - if (filter is UriFilter) { - filter.addToUri(uri, "wpsolr_fq[${i - indexModifier}]") - } - } - uri.appendQueryParameter("wpsolr_page", page.toString()) - - return GET(uri.toString(), headers) - } - - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") - override fun searchMangaSelector() = "div.results-by-facets div[id*=res]" - private var mangaParsedSoFar = 0 - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - if (document.location().contains("page=1")) mangaParsedSoFar = 0 - val mangas = document.select(searchMangaSelector()).map { searchMangaFromElement(it) } - .also { mangaParsedSoFar += it.count() } - val totalResults = Regex("""(\d+)""").find(document.select("div.res_info").text())?.groupValues?.get(1)?.toIntOrNull() ?: 0 - return MangasPage(mangas, mangaParsedSoFar < totalResults) - } - override fun searchMangaFromElement(element: Element) = buildManga(element.select("a").first(), element.select("img")?.first()) - - // Build Manga From Element - private fun buildManga(titleElement: Element, thumbnailElement: Element?): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(titleElement.attr("href")) - manga.title = cleanTitle(titleElement.text()) - if (thumbnailElement != null) manga.thumbnail_url = getThumbnail(getImage(thumbnailElement)) - return manga - } - - private val extensionRegex = Regex("""\.(jpg|png|jpeg)""") - - private fun getImage(element: Element): String { - return when { - element.attr("data-src").contains(extensionRegex) -> element.attr("abs:data-src") - element.attr("src").contains(extensionRegex) -> element.attr("abs:src") - else -> element.attr("abs:data-lazy-src") - } - } - - // removes resizing - private fun getThumbnail(thumbnailUrl: String?): String? { - thumbnailUrl ?: return null - val url = thumbnailUrl.substringBeforeLast("-") + "." + thumbnailUrl.substringAfterLast(".") - return if (URLUtil.isValidUrl(url)) url else null - } - - // cleans up the name removing author and language from the title - private val titleRegex = Regex("""\[[^]]*]""") - private fun cleanTitle(title: String) = title.replace(titleRegex, "").substringBeforeLast("(").trim() - - private fun cleanAuthor(author: String) = author.substringAfter("[").substringBefore("]").trim() - - // Manga Details - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - val needCover = manga.thumbnail_url?.let { !client.newCall(GET(it, headers)).execute().isSuccessful } ?: true - - return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response.asJsoup(), needCover).apply { initialized = true } - } - } - - private fun mangaDetailsParse(document: Document, needCover: Boolean = true): SManga { - return SManga.create().apply { - author = cleanAuthor(document.select("h1").text()) - artist = author - genre = document.select(".entry-header p a[href*=genre]").joinToString { it.text() } - val basicDescription = document.select("h1").text() - // too troublesome to achieve 100% accuracy assigning scanlator group during chapterListParse - val scanlatedBy = document.select(".entry-terms:has(a[href*=group])").firstOrNull() - ?.select("a[href*=group]")?.joinToString(prefix = "Scanlated by: ") { it.text() } - val extendedDescription = document.select(".entry-content p:not(p:containsOwn(|)):not(.chapter-class + p)")?.joinToString("\n") { it.text() } - description = listOfNotNull(basicDescription, scanlatedBy, extendedDescription).joinToString("\n") - status = when (document.select("a[href*=status]")?.first()?.text()) { - "Ongoing" -> SManga.ONGOING - "Completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - if (needCover) { - thumbnail_url = getThumbnail( - getImage( - client.newCall(GET("$baseUrl/search/?search=${document.location()}", headers)) - .execute().asJsoup().select("div.wdm_results div.p_content img").first() - ) - ) - } - } - } - - override fun mangaDetailsParse(document: Document) = throw Exception("Not used") - - // Start Chapter Get - override fun chapterListSelector() = ".entry-pagination a" - - @SuppressLint("DefaultLocale") - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - - val date = parseDate(document.select(".entry-time").text()) - val mangaUrl = document.baseUri() - val chfirstname = document.select(".chapter-class a[href*=$mangaUrl]")?.first()?.text()?.ifEmpty { "Ch. 1" }?.capitalize() - ?: "Ch. 1" - // create first chapter since its on main manga page - chapters.add(createChapter("1", document.baseUri(), date, chfirstname)) - // see if there are multiple chapters or not - document.select(chapterListSelector())?.let { it -> - it.forEach { - if (!it.text().contains("Next »", true)) { - val pageNumber = it.text() - val chname = document.select(".chapter-class a[href$=/$pageNumber/]")?.text()?.ifEmpty { "Ch. $pageNumber" }?.capitalize() - ?: "Ch. $pageNumber" - chapters.add(createChapter(it.text(), document.baseUri(), date, chname)) - } - } - } - chapters.reverse() - return chapters - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(date)?.time ?: 0 - } - - private fun createChapter(pageNumber: String, mangaUrl: String, date: Long, chname: String): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain("$mangaUrl/$pageNumber") - chapter.name = chname - chapter.date_upload = date - return chapter - } - - override fun chapterFromElement(element: Element) = throw Exception("Not used") - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return (document.select("div > img") + document.select("div.separator img[data-src]")) - .map { getImage(it) } - .distinct() - .mapIndexed { i, url -> Page(i, "", url) } - } - - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - // Filter Parsing, grabs pages as document and filters out Genres, Popular Tags, and Categories, Parings, and Scan Groups - private var filtersCached = false - - // Grabs page containing filters and puts it into cache - private fun filterAssist(url: String): String { - val response = client.newCall(GET(url, headers)).execute() - return response.body()!!.string() - } - - // Returns page from cache to reduce calls to website - private fun getCache(url: String): Document? { - val response = client.newCall(GET(url, headers, CacheControl.FORCE_CACHE)).execute() - return if (response.isSuccessful) { - filtersCached = true - response.asJsoup() - } else { - filtersCached = false - null - } - } - - // Parses page for filter - private fun returnFilter(document: Document?, css: String): Array<String> { - return document?.select(css)?.map { it.text() }?.toTypedArray() - ?: arrayOf("Press 'Reset' to try again") - } - - // URLs for the pages we need to cache - private val cachedPagesUrls = hashMapOf( - Pair("genres", baseUrl), - Pair("tags", baseUrl), - Pair("categories", "$baseUrl/cats/"), - Pair("pairings", "$baseUrl/pairing/"), - Pair("groups", "$baseUrl/group/") - ) - - // Generates the filter lists for app - override fun getFilterList(): FilterList { - return FilterList( - EnforceLanguageFilter(siteLang), - GenreFilter(returnFilter(getCache(cachedPagesUrls["genres"]!!), ".tagcloud a[href*=/genre/]")), - TagFilter(returnFilter(getCache(cachedPagesUrls["tags"]!!), ".tagcloud a[href*=/tag/]")), - CatFilter(returnFilter(getCache(cachedPagesUrls["categories"]!!), ".links a")), - PairingFilter(returnFilter(getCache(cachedPagesUrls["pairings"]!!), ".links a")), - ScanGroupFilter(returnFilter(getCache(cachedPagesUrls["groups"]!!), ".links a")) - ) - } - - private class EnforceLanguageFilter(val siteLang: String) : Filter.CheckBox("Enforce language", true), UriFilter { - fun indexModifier() = if (state) 0 else 1 - override fun addToUri(uri: Uri.Builder, uriParam: String) { - if (state) uri.appendQueryParameter(uriParam, "lang_str:$siteLang") - } - } - - private class GenreFilter(GENRES: Array<String>) : UriSelectFilter("Genre", "genre_str", arrayOf("Any", *GENRES)) - private class TagFilter(POPTAG: Array<String>) : UriSelectFilter("Popular Tags", "tags", arrayOf("Any", *POPTAG)) - private class CatFilter(CATID: Array<String>) : UriSelectFilter("Categories", "categories", arrayOf("Any", *CATID)) - private class PairingFilter(PAIR: Array<String>) : UriSelectFilter("Pairing", "pairing_str", arrayOf("Any", *PAIR)) - private class ScanGroupFilter(GROUP: Array<String>) : UriSelectFilter("Scanlation Group", "group_str", arrayOf("Any", *GROUP)) - - /** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ - private open class UriSelectFilter( - displayName: String, - val uriValuePrefix: String, - val vals: Array<String>, - val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it }.toTypedArray(), defaultValue), UriFilter { - override fun addToUri(uri: Uri.Builder, uriParam: String) { - if (state != 0 || !firstIsUnspecified) - uri.appendQueryParameter(uriParam, "$uriValuePrefix:${vals[state]}") - } - } - - /** - * Represents a filter that is able to modify a URI. - */ - private interface UriFilter { - fun addToUri(uri: Uri.Builder, uriParam: String) - } -} diff --git a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingMangaFactory.kt b/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingMangaFactory.kt deleted file mode 100644 index 6bbcdb1b1..000000000 --- a/src/all/myreadingmanga/src/eu/kanade/tachiyomi/extension/all/myreadingmanga/MyReadingMangaFactory.kt +++ /dev/null @@ -1,47 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.myreadingmanga - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class MyReadingMangaFactory : SourceFactory { - override fun createSources(): List<Source> = languageList.map { MyReadingManga(it.tachiLang, it.siteLang, it.latestLang) } -} - -private data class Source(val tachiLang: String, val siteLang: String, val latestLang: String = siteLang) - -// These should all be valid. Add a language code and uncomment to enable -private val languageList = listOf( - Source("ar", "Arabic"), -// Source("", "Bahasa"), - Source("id", "Indonesia"), -// Source("", "Bulgarian"), - Source("zh", "Chinese"), -// Source("", "Croatian"), -// Source("", "Czech"), - Source("en", "English"), -// Source("", "Filipino"), -// Source("", "Finnish"), -// Source("", "Flemish", "flemish-dutch"), -// Source("", "Dutch"), - Source("fr", "French"), - Source("de", "German"), -// Source("", "Greek"), -// Source("", "Hebrew"), -// Source("", "Hindi"), -// Source("", "Hungarian"), - Source("it", "Italian"), - Source("ja", "Japanese", "jp"), - Source("ko", "Korean"), -// Source("", "Polish"), - Source("pt-BR", "Portuguese"), -// Source("", "Romanian"), - Source("ru", "Russian"), -// Source("", "Slovak"), - Source("es", "Spanish"), -// Source("", "Swedish"), -// Source("", "Thai"), - Source("tr", "Turkish"), - Source("vi", "Vietnamese") -) diff --git a/src/all/nhentai/AndroidManifest.xml b/src/all/nhentai/AndroidManifest.xml deleted file mode 100644 index 294d8050f..000000000 --- a/src/all/nhentai/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".all.nhentai.NHUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="nhentai.net" - android:pathPattern="/g/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/all/nhentai/build.gradle b/src/all/nhentai/build.gradle deleted file mode 100644 index 87ee88fb2..000000000 --- a/src/all/nhentai/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NHentai' - pkgNameSuffix = 'all.nhentai' - extClass = '.NHFactory' - extVersionCode = 28 - libVersion = '1.2' - containsNsfw = true -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/nhentai/res/mipmap-hdpi/ic_launcher.png b/src/all/nhentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3762b52b3..000000000 Binary files a/src/all/nhentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/nhentai/res/mipmap-mdpi/ic_launcher.png b/src/all/nhentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 59d965e69..000000000 Binary files a/src/all/nhentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/nhentai/res/mipmap-xhdpi/ic_launcher.png b/src/all/nhentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 749a00d6a..000000000 Binary files a/src/all/nhentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/nhentai/res/mipmap-xxhdpi/ic_launcher.png b/src/all/nhentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fd6c94e4f..000000000 Binary files a/src/all/nhentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/nhentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/nhentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 988c67af6..000000000 Binary files a/src/all/nhentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/nhentai/res/web_hi_res_512.png b/src/all/nhentai/res/web_hi_res_512.png deleted file mode 100644 index e8cbc337a..000000000 Binary files a/src/all/nhentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHFactory.kt b/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHFactory.kt deleted file mode 100644 index 722c4f673..000000000 --- a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.nhentai - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class NHFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - NHentai("en", "english"), - NHentai("ja", "japanese"), - NHentai("zh", "chinese"), - NHentai("other", "") - ) -} diff --git a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUrlActivity.kt b/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUrlActivity.kt deleted file mode 100644 index 60c3131c3..000000000 --- a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUrlActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.nhentai - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://nhentai.net/g/xxxxxx intents and redirects them to - * the main Tachiyomi process. - */ -class NHUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${NHentai.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("NHUrlActivity", e.toString()) - } - } else { - Log.e("NHUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUtils.kt b/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUtils.kt deleted file mode 100644 index 020fb151b..000000000 --- a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHUtils.kt +++ /dev/null @@ -1,64 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.nhentai - -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat - -object NHUtils { - fun getArtists(document: Document): String { - val artists = document.select("#tags > div:nth-child(4) > span > a .name") - return artists.joinToString(", ") { it.cleanTag() } - } - - fun getGroups(document: Document): String? { - val groups = document.select("#tags > div:nth-child(5) > span > a .name") - return if (groups.isNotEmpty()) { - groups.joinToString(", ") { it.cleanTag() } - } else { - null - } - } - - fun getTagDescription(document: Document): String { - val stringBuilder = StringBuilder() - - val categories = document.select("#tags > div:nth-child(7) > span > a .name") - if (categories.isNotEmpty()) { - stringBuilder.append("Categories: ") - stringBuilder.append(categories.joinToString(", ") { it.cleanTag() }) - stringBuilder.append("\n\n") - } - - val parodies = document.select("#tags > div:nth-child(1) > span > a .name") - if (parodies.isNotEmpty()) { - stringBuilder.append("Parodies: ") - stringBuilder.append(parodies.joinToString(", ") { it.cleanTag() }) - stringBuilder.append("\n\n") - } - - val characters = document.select("#tags > div:nth-child(2) > span > a .name") - if (characters.isNotEmpty()) { - stringBuilder.append("Characters: ") - stringBuilder.append(characters.joinToString(", ") { it.cleanTag() }) - } - - return stringBuilder.toString() - } - - fun getTags(document: Document): String { - val tags = document.select("#tags > div:nth-child(3) > span > a .name") - return tags.map { it.cleanTag() }.sorted().joinToString(", ") - } - - fun getNumPages(document: Document): String { - return document.select("#tags > div:nth-child(8) > span > a .name").first().cleanTag() - } - - fun getTime(document: Document): Long { - val timeString = document.toString().substringAfter("datetime=\"").substringBefore("\">").replace("T", " ") - - return SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSZ").parse(timeString)?.time ?: 0L - } - - private fun Element.cleanTag(): String = text().replace(Regex("\\(.*\\)"), "").trim() -} diff --git a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHentai.kt b/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHentai.kt deleted file mode 100644 index 6c025e51d..000000000 --- a/src/all/nhentai/src/eu/kanade/tachiyomi/extension/all/nhentai/NHentai.kt +++ /dev/null @@ -1,327 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.nhentai - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getArtists -import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getGroups -import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getNumPages -import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTagDescription -import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTags -import eu.kanade.tachiyomi.extension.all.nhentai.NHUtils.getTime -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -open class NHentai( - override val lang: String, - private val nhLang: String -) : ConfigurableSource, ParsedHttpSource() { - - final override val baseUrl = "https://nhentai.net" - - override val name = "NHentai" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(4) - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor) - .build() - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private var displayFullTitle: Boolean = when (preferences.getString(TITLE_PREF, "full")) { - "full" -> true - else -> false - } - - private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""") - private fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim() - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val serverPref = androidx.preference.ListPreference(screen.context).apply { - key = TITLE_PREF - title = TITLE_PREF - entries = arrayOf("Full Title", "Short Title") - entryValues = arrayOf("full", "short") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - displayFullTitle = when (newValue) { - "full" -> true - else -> false - } - true - } - } - - if (!preferences.contains(TITLE_PREF)) - preferences.edit().putString(TITLE_PREF, "full").apply() - - screen.addPreference(serverPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val serverPref = ListPreference(screen.context).apply { - key = TITLE_PREF - title = TITLE_PREF - entries = arrayOf("Full Title", "Short Title") - entryValues = arrayOf("full", "short") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - displayFullTitle = when (newValue) { - "full" -> true - else -> false - } - true - } - } - - if (!preferences.contains(TITLE_PREF)) - preferences.edit().putString(TITLE_PREF, "full").apply() - - screen.addPreference(serverPref) - } - - override fun latestUpdatesRequest(page: Int) = GET(if (nhLang.isBlank()) "$baseUrl/?page=$page" else "$baseUrl/language/$nhLang/?page=$page", headers) - - override fun latestUpdatesSelector() = "#content .gallery" - - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - title = element.select("a > div").text().replace("\"", "").let { - if (displayFullTitle) it.trim() else it.shortenTitle() - } - thumbnail_url = element.select(".cover img").first().let { img -> - if (img.hasAttr("data-src")) img.attr("abs:data-src") else img.attr("abs:src") - } - } - - override fun latestUpdatesNextPageSelector() = "#content > section.pagination > a.next" - - override fun popularMangaRequest(page: Int) = GET(if (nhLang.isBlank()) "$baseUrl/?page=$page" else "$baseUrl/language/$nhLang/popular?page=$page", headers) - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return when { - query.startsWith(PREFIX_ID_SEARCH) -> { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(searchMangaByIdRequest(id)) - .asObservableSuccess() - .map { response -> searchMangaByIdParse(response, id) } - } - query.isQueryIdNumbers() -> { - client.newCall(searchMangaByIdRequest(query)) - .asObservableSuccess() - .map { response -> searchMangaByIdParse(response, query) } - } - else -> super.fetchSearchManga(page, query, filters) - } - } - - // The website redirects for any number <= 400000 - private fun String.isQueryIdNumbers(): Boolean { - val int = this.toIntOrNull() ?: return false - return int <= 400000 - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filterList = if (filters.isEmpty()) getFilterList() else filters - val nhLangSearch = if (nhLang.isBlank()) "" else "+$nhLang " - val advQuery = combineQuery(filterList) - val favoriteFilter = filterList.findInstance<FavoriteFilter>() - val isOkayToSort = filterList.findInstance<UploadedFilter>()?.state?.isBlank() ?: true - - if (favoriteFilter?.state == true) { - val url = HttpUrl.parse("$baseUrl/favorites")!!.newBuilder() - .addQueryParameter("q", "$query $advQuery") - .addQueryParameter("page", page.toString()) - - return GET(url.toString(), headers) - } else { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("q", "$query $nhLangSearch$advQuery") - .addQueryParameter("page", page.toString()) - - if (isOkayToSort) { - filterList.findInstance<SortFilter>()?.let { f -> - url.addQueryParameter("sort", f.toUriPart()) - } - } - - return GET(url.toString(), headers) - } - } - - private fun combineQuery(filters: FilterList): String { - val stringBuilder = StringBuilder() - val advSearch = filters.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter -> - val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank) - splitState.map { - AdvSearchEntry(filter.name, it.removePrefix("-"), it.startsWith("-")) - } - } - - advSearch.forEach { entry -> - if (entry.exclude) stringBuilder.append("-") - stringBuilder.append("${entry.name}:") - stringBuilder.append(entry.text) - stringBuilder.append(" ") - } - - return stringBuilder.toString() - } - - data class AdvSearchEntry(val name: String, val text: String, val exclude: Boolean) - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/g/$id", headers) - - private fun searchMangaByIdParse(response: Response, id: String): MangasPage { - val details = mangaDetailsParse(response) - details.url = "/g/$id/" - return MangasPage(listOf(details), false) - } - - override fun searchMangaParse(response: Response): MangasPage { - if (response.request().url().toString().contains("/login/")) { - val document = response.asJsoup() - if (document.select(".fa-sign-in").isNotEmpty()) { - throw Exception("Log in via WebView to view favorites") - } - } - - return super.searchMangaParse(response) - } - - override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun searchMangaSelector() = latestUpdatesSelector() - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val fullTitle = document.select("#info > h1").text().replace("\"", "").trim() - - return SManga.create().apply { - title = if (displayFullTitle) fullTitle else fullTitle.shortenTitle() - thumbnail_url = document.select("#cover > a > img").attr("data-src") - status = SManga.COMPLETED - artist = getArtists(document) - author = artist - // Some people want these additional details in description - description = "Full English and Japanese titles:\n" - .plus("$fullTitle\n") - .plus("${document.select("div#info h2").text()}\n\n") - .plus("Pages: ${getNumPages(document)}\n") - .plus("Favorited by: ${document.select("div#info i.fa-heart + span span").text().removeSurrounding("(", ")")}\n") - .plus(getTagDescription(document)) - genre = getTags(document) - } - } - - override fun chapterListRequest(manga: SManga): Request = GET("$baseUrl${manga.url}", headers) - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - return listOf( - SChapter.create().apply { - name = "Chapter" - scanlator = getGroups(document) - date_upload = getTime(document) - setUrlWithoutDomain(response.request().url().encodedPath()) - } - ) - } - - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.thumbs a > img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:data-src").replace("t.nh", "i.nh").replace("t.", ".")) - } - } - - override fun getFilterList(): FilterList = FilterList( - Filter.Header("Separate tags with commas (,)"), - Filter.Header("Prepend with dash (-) to exclude"), - TagFilter(), - CategoryFilter(), - GroupFilter(), - ArtistFilter(), - ParodyFilter(), - CharactersFilter(), - Filter.Header("Uploaded valid units are h, d, w, m, y."), - Filter.Header("example: (>20d)"), - UploadedFilter(), - - Filter.Separator(), - SortFilter(), - Filter.Header("Sort is ignored if favorites only"), - FavoriteFilter() - ) - - class TagFilter : AdvSearchEntryFilter("Tags") - class CategoryFilter : AdvSearchEntryFilter("Categories") - class GroupFilter : AdvSearchEntryFilter("Groups") - class ArtistFilter : AdvSearchEntryFilter("Artists") - class ParodyFilter : AdvSearchEntryFilter("Parodies") - class CharactersFilter : AdvSearchEntryFilter("Characters") - class UploadedFilter : AdvSearchEntryFilter("Uploaded") - open class AdvSearchEntryFilter(name: String) : Filter.Text(name) - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - private class FavoriteFilter : Filter.CheckBox("Show favorites only", false) - - private class SortFilter : UriPartFilter( - "Sort By", - arrayOf( - Pair("Popular: All Time", "popular"), - Pair("Popular: Week", "popular-week"), - Pair("Popular: Today", "popular-today"), - Pair("Recent", "date") - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T - - companion object { - const val PREFIX_ID_SEARCH = "id:" - private const val TITLE_PREF = "Display manga title as:" - } -} diff --git a/src/all/ninehentai/AndroidManifest.xml b/src/all/ninehentai/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/ninehentai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/ninehentai/build.gradle b/src/all/ninehentai/build.gradle deleted file mode 100644 index 6aa6a525a..000000000 --- a/src/all/ninehentai/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NineHentai' - pkgNameSuffix = 'all.ninehentai' - extClass = '.NineHentai' - extVersionCode = 9 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1fa8c130b..000000000 Binary files a/src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 45a458885..000000000 Binary files a/src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 227b84683..000000000 Binary files a/src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2d854fe03..000000000 Binary files a/src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 93bd7ff8b..000000000 Binary files a/src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninehentai/res/web_hi_res_512.png b/src/all/ninehentai/res/web_hi_res_512.png deleted file mode 100644 index ac94bbdf8..000000000 Binary files a/src/all/ninehentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/Dto.kt b/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/Dto.kt deleted file mode 100644 index e972c6b22..000000000 --- a/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/Dto.kt +++ /dev/null @@ -1,31 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ninehentai - -import eu.kanade.tachiyomi.source.model.Filter - -data class Manga( - val id: Int, - var title: String, - val image_server: String, - val tags: List<Tag>, - val total_page: Int -) - -class Tag( - val id: Int, - name: String, - val description: String = "null", - val type: Int = 1 -) : Filter.TriState(name) - -data class SearchRequest( - val text: String, - val page: Int, - val sort: Int, - val pages: Map<String, IntArray> = mapOf("range" to intArrayOf(0, 2000)), - val tag: Map<String, Items> -) - -data class Items( - val included: MutableList<Tag>, - val excluded: MutableList<Tag> -) diff --git a/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/NHTags.kt b/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/NHTags.kt deleted file mode 100644 index 984214805..000000000 --- a/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/NHTags.kt +++ /dev/null @@ -1,1523 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ninehentai - -object NHTags { - fun getTagsList() = listOf( - Tag(id = 16712, name = "3d"), - Tag(id = 1054, name = "Abortion"), - Tag(id = 5198, name = "Absorption"), - Tag(id = 3576, name = "Adventitious Penis"), - Tag(id = 9348, name = "Adventitious Vagina"), - Tag(id = 25549, name = "Afro"), - Tag(id = 193, name = "Age Progression"), - Tag(id = 702, name = "Age Regression"), - Tag(id = 214, name = "Ahegao"), - Tag(id = 6636, name = "Albino"), - Tag(id = 10, name = "Alien"), - Tag(id = 1101, name = "Alien Girl"), - Tag(id = 215, name = "All The Way Through"), - Tag(id = 23272, name = "Already Uploaded"), - Tag(id = 288, name = "Amputee"), - Tag(id = 30, name = "Anal"), - Tag(id = 6266, name = "Anal Birth"), - Tag(id = 10530, name = "Animal On Animal"), - Tag(id = 11881, name = "Animal On Furry"), - Tag(id = 17056, name = "Animated"), - Tag(id = 4770, name = "Anorexic"), - Tag(id = 15880, name = "Anthology"), - Tag(id = 24975, name = "Ao No Sunadokei"), - Tag(id = 209, name = "Apron"), - Tag(id = 814, name = "Armpit Licking"), - Tag(id = 815, name = "Armpit Sex"), - Tag(id = 15974, name = "Artbook"), - Tag(id = 1705, name = "Asphyxiation"), - Tag(id = 1755, name = "Ass Expansion"), - Tag(id = 2148, name = "Assjob"), - Tag(id = 24352, name = "Atsushima Tetsuya"), - Tag(id = 673, name = "Aunt"), - Tag(id = 329, name = "Autofellatio"), - Tag(id = 5662, name = "Autopaizuri"), - Tag(id = 602, name = "Bald"), - Tag(id = 1689, name = "Ball Sucking"), - Tag(id = 5558, name = "Balljob"), - Tag(id = 6380, name = "Balls Expansion"), - Tag(id = 19880, name = "Ban Daiki"), - Tag(id = 36, name = "Bandages"), - Tag(id = 3424, name = "Bandaid"), - Tag(id = 16564, name = "Baphomet"), - Tag(id = 65, name = "Bbm"), - Tag(id = 860, name = "Bbw"), - Tag(id = 147, name = "Bdsm"), - Tag(id = 1154, name = "Bear"), - Tag(id = 4259, name = "Bear Boy"), - Tag(id = 2161, name = "Bear Girl"), - Tag(id = 744, name = "Beauty Mark"), - Tag(id = 2349, name = "Bee Girl"), - Tag(id = 25675, name = "Belial"), - Tag(id = 230, name = "Bestiality"), - Tag(id = 768, name = "Big Areolae"), - Tag(id = 175, name = "Big Ass"), - Tag(id = 1979, name = "Big Balls"), - Tag(id = 42, name = "Big Breasts"), - Tag(id = 559, name = "Big Clit"), - Tag(id = 5081, name = "Big Lips"), - Tag(id = 861, name = "Big Nipples"), - Tag(id = 127, name = "Big Penis"), - Tag(id = 5387, name = "Big Vagina"), - Tag(id = 130, name = "Bike Shorts"), - Tag(id = 201, name = "Bikini"), - Tag(id = 284, name = "Birth"), - Tag(id = 121, name = "Bisexual"), - Tag(id = 104, name = "Blackmail"), - Tag(id = 1057, name = "Blind"), - Tag(id = 1004, name = "Blindfold"), - Tag(id = 330, name = "Blood"), - Tag(id = 106, name = "Bloomers"), - Tag(id = 52, name = "Blowjob"), - Tag(id = 2274, name = "Blowjob Face"), - Tag(id = 269, name = "Body Modification"), - Tag(id = 888, name = "Body Painting"), - Tag(id = 5502, name = "Body Swap"), - Tag(id = 3385, name = "Body Writing"), - Tag(id = 4359, name = "Bodystocking"), - Tag(id = 35, name = "Bodysuit"), - Tag(id = 107, name = "Bondage"), - Tag(id = 5101, name = "Brain Fuck"), - Tag(id = 22629, name = "Brave Kingdom"), - Tag(id = 194, name = "Breast Expansion"), - Tag(id = 246, name = "Breast Feeding"), - Tag(id = 2801, name = "Breast Reduction"), - Tag(id = 335, name = "Bride"), - Tag(id = 2216, name = "Brother"), - Tag(id = 25392, name = "Buddly Love"), - Tag(id = 131, name = "Bukkake"), - Tag(id = 561, name = "Bull"), - Tag(id = 16623, name = "Bump Squad Wolfsbane"), - Tag(id = 797, name = "Bunny Boy"), - Tag(id = 76, name = "Bunny Girl"), - Tag(id = 17169, name = "Burn Out Syndrome"), - Tag(id = 14027, name = "Burping"), - Tag(id = 585, name = "Business Suit"), - Tag(id = 16194, name = "Buso"), - Tag(id = 20819, name = "Buta Hormone"), - Tag(id = 1534, name = "Butler"), - Tag(id = 20149, name = "Byoujo No Sho"), - Tag(id = 16605, name = "Cacty"), - Tag(id = 289, name = "Cannibalism"), - Tag(id = 16362, name = "Canvas 2"), - Tag(id = 19114, name = "Capsule"), - Tag(id = 16484, name = "Captain Tsubasa Fcmin Hiroshima"), - Tag(id = 16028, name = "Caption"), - Tag(id = 10130, name = "Cashier"), - Tag(id = 599, name = "Cat"), - Tag(id = 562, name = "Catboy"), - Tag(id = 17122, name = "Catch And Release"), - Tag(id = 3115, name = "Catfight"), - Tag(id = 77, name = "Catgirl"), - Tag(id = 1053, name = "Cbt"), - Tag(id = 25097, name = "Cc6512"), - Tag(id = 2468, name = "Centaur"), - Tag(id = 239, name = "Cervix Penetration"), - Tag(id = 25210, name = "Chakichi"), - Tag(id = 1049, name = "Chastity Belt"), - Tag(id = 17123, name = "Chayamachi Mejiro"), - Tag(id = 211, name = "Cheating"), - Tag(id = 132, name = "Cheerleader"), - Tag(id = 19745, name = "Chihiro Nobuki"), - Tag(id = 20502, name = "Chika Madoka"), - Tag(id = 22943, name = "Chikadoh"), - Tag(id = 533, name = "Chikan"), - Tag(id = 774, name = "Chinese Dress"), - Tag(id = 25058, name = "Chinpo"), - Tag(id = 15911, name = "Chitumatsuriya Honpo"), - Tag(id = 16516, name = "Chloe Wichers"), - Tag(id = 19312, name = "Chloe Withers"), - Tag(id = 148, name = "Chloroform"), - Tag(id = 19538, name = "Chobikuma"), - Tag(id = 18671, name = "Choiki"), - Tag(id = 16987, name = "Chou Shiryu"), - Tag(id = 16075, name = "Chouji Maboroshi"), - Tag(id = 17135, name = "Chouko"), - Tag(id = 22986, name = "Chounyuu For You"), - Tag(id = 23808, name = "Chounyuu Kenkyuushitsu"), - Tag(id = 1598, name = "Christmas"), - Tag(id = 21526, name = "Chrono Nanae"), - Tag(id = 16546, name = "Chu-shin Kuranosuke"), - Tag(id = 19374, name = "Chubold"), - Tag(id = 22053, name = "Chuchu"), - Tag(id = 16443, name = "Chunen"), - Tag(id = 17036, name = "Clalaclan Philias"), - Tag(id = 5840, name = "Clamp"), - Tag(id = 16280, name = "Clara.v"), - Tag(id = 16140, name = "Classmates"), - Tag(id = 1178, name = "Clit Growth"), - Tag(id = 4008, name = "Closed Eyes"), - Tag(id = 2691, name = "Clothed Female Nude Male"), - Tag(id = 8975, name = "Clothed Male Nude Female"), - Tag(id = 5229, name = "Clothed Paizuri"), - Tag(id = 16661, name = "Clown"), - Tag(id = 1044, name = "Coach"), - Tag(id = 5826, name = "Cockslapping"), - Tag(id = 231, name = "Collar"), - Tag(id = 16496, name = "Compilation"), - Tag(id = 179, name = "Condom"), - Tag(id = 13690, name = "Conjoined"), - Tag(id = 3064, name = "Coprophagia"), - Tag(id = 25201, name = "Cor Leonis"), - Tag(id = 22318, name = "Coribou"), - Tag(id = 253, name = "Corruption"), - Tag(id = 796, name = "Corset"), - Tag(id = 527, name = "Cosplaying"), - Tag(id = 674, name = "Cousin"), - Tag(id = 16666, name = "Cousinanon"), - Tag(id = 14293, name = "Cow"), - Tag(id = 564, name = "Cowgirl"), - Tag(id = 7008, name = "Cowman"), - Tag(id = 25442, name = "Creamytea"), - Tag(id = 128, name = "Crossdressing"), - Tag(id = 852, name = "Crotch Tattoo"), - Tag(id = 4034, name = "Crown"), - Tag(id = 17534, name = "Cubetype"), - Tag(id = 4760, name = "Cum Bath"), - Tag(id = 535, name = "Cum In Eye"), - Tag(id = 913, name = "Cum Swap"), - Tag(id = 560, name = "Cunnilingus"), - Tag(id = 10106, name = "Cuntboy"), - Tag(id = 17444, name = "Curo-kun"), - Tag(id = 23878, name = "Cyan Coke"), - Tag(id = 17323, name = "Cynthia B. Rogers"), - Tag(id = 16510, name = "Dabi"), - Tag(id = 18152, name = "Dai Moru Ya"), - Tag(id = 16120, name = "Daiousamajihen"), - Tag(id = 16366, name = "Dakouin Saboru"), - Tag(id = 16008, name = "Danger-j"), - Tag(id = 23670, name = "Darai."), - Tag(id = 1306, name = "Dark Nipples"), - Tag(id = 16662, name = "Dark Princess"), - Tag(id = 3621, name = "Dark Sclera"), - Tag(id = 51, name = "Dark Skin"), - Tag(id = 275, name = "Daughter"), - Tag(id = 16017, name = "De Kirei Yume"), - Tag(id = 20305, name = "Deception Iv"), - Tag(id = 216, name = "Deepthroat"), - Tag(id = 24670, name = "Deer Boy"), - Tag(id = 16956, name = "Deer Girl"), - Tag(id = 67, name = "Defloration"), - Tag(id = 17321, name = "Gyuo"), - Tag(id = 15909, name = "Gyupaibyu"), - Tag(id = 25565, name = "Gyzertoast"), - Tag(id = 16759, name = "Hachikyu"), - Tag(id = 20014, name = "Hachimitsu Ouji"), - Tag(id = 19369, name = "Hadaka"), - Tag(id = 16772, name = "Hadamurasaki"), - Tag(id = 16474, name = "Hagiri"), - Tag(id = 15251, name = "Haigure"), - Tag(id = 2647, name = "Hairjob"), - Tag(id = 336, name = "Hairy"), - Tag(id = 337, name = "Hairy Armpits"), - Tag(id = 17070, name = "Haitenai"), - Tag(id = 22944, name = "Halco"), - Tag(id = 16293, name = "Halftooth"), - Tag(id = 20445, name = "Halkrom"), - Tag(id = 24341, name = "Halsione"), - Tag(id = 15898, name = "Hamu Boshi"), - Tag(id = 17064, name = "Hanaoka"), - Tag(id = 16792, name = "Hanatarou"), - Tag(id = 7088, name = "Handicapped"), - Tag(id = 352, name = "Handjob"), - Tag(id = 15919, name = "Hanzaki Ran"), - Tag(id = 17041, name = "Happy Start"), - Tag(id = 16376, name = "Haraheridou"), - Tag(id = 16954, name = "Harapeko Manbou"), - Tag(id = 20423, name = "Harazumi Tami"), - Tag(id = 16189, name = "Haredou"), - Tag(id = 270, name = "Harem"), - Tag(id = 16115, name = "Harenchi Noon"), - Tag(id = 22137, name = "Hareta"), - Tag(id = 15877, name = "Harimaro"), - Tag(id = 3468, name = "Harness"), - Tag(id = 616, name = "Harpy"), - Tag(id = 17537, name = "Hatomile"), - Tag(id = 16483, name = "Hayase Hidekazu"), - Tag(id = 17473, name = "Hayato Shibusawa"), - Tag(id = 16736, name = "Heart Core"), - Tag(id = 17552, name = "Hebun Irebun"), - Tag(id = 24170, name = "Hemu"), - Tag(id = 15905, name = "Henachokopinkizu"), - Tag(id = 16183, name = "Hentai B"), - Tag(id = 16072, name = "Hentai The Radical"), - Tag(id = 21724, name = "Heresy"), - Tag(id = 3527, name = "Heterochromia"), - Tag(id = 19908, name = "Hidamaru"), - Tag(id = 125, name = "Hidden Sex"), - Tag(id = 16164, name = "Hieda No Anana"), - Tag(id = 16165, name = "Hieda No Aya"), - Tag(id = 16005, name = "Hieda Touijin"), - Tag(id = 15907, name = "Higashizato Kirico"), - Tag(id = 18131, name = "Higumax"), - Tag(id = 9356, name = "Hijab"), - Tag(id = 24155, name = "Hikari Kamigishi"), - Tag(id = 16632, name = "Hikari Konohana"), - Tag(id = 15981, name = "Hikaru Hayashi"), - Tag(id = 22492, name = "Hikkin"), - Tag(id = 17457, name = "Hikoushi"), - Tag(id = 17138, name = "Himawari Manjyu"), - Tag(id = 19581, name = "Himitsu No Hanazono"), - Tag(id = 15903, name = "Hina Can"), - Tag(id = 16026, name = "Hiroki Endo"), - Tag(id = 16336, name = "Hisako Ushigami"), - Tag(id = 24708, name = "Hisashi Ryuuto"), - Tag(id = 25200, name = "Hiyori Chan"), - Tag(id = 23351, name = "Hizuki Haruka"), - Tag(id = 16982, name = "Hmp"), - Tag(id = 15978, name = "Hobihobi"), - Tag(id = 15895, name = "Hokuhokutou Ejiputo Gaisha"), - Tag(id = 17246, name = "Homigiwa Ichiyou"), - Tag(id = 15902, name = "Homing Spitz"), - Tag(id = 16256, name = "Honami Majo"), - Tag(id = 21678, name = "Honenuki Chicken."), - Tag(id = 20041, name = "Hong Ban-jang"), - Tag(id = 21465, name = "Hori Makoto"), - Tag(id = 1712, name = "Horns"), - Tag(id = 15899, name = "Horny Animals"), - Tag(id = 17178, name = "Horobi No Michi"), - Tag(id = 234, name = "Horse"), - Tag(id = 1577, name = "Horse Boy"), - Tag(id = 8480, name = "Horse Cock"), - Tag(id = 1578, name = "Horse Girl"), - Tag(id = 1187, name = "Hotpants"), - Tag(id = 15982, name = "How To"), - Tag(id = 202, name = "Huge Breasts"), - Tag(id = 282, name = "Huge Penis"), - Tag(id = 21295, name = "Hui C"), - Tag(id = 1095, name = "Human On Furry"), - Tag(id = 298, name = "Human Pet"), - Tag(id = 753, name = "Humiliation"), - Tag(id = 16216, name = "Husky Guy"), - Tag(id = 16225, name = "Hutamizu Kirin"), - Tag(id = 20991, name = "Hyakuhachi Kyoukai"), - Tag(id = 17315, name = "Hys"), - Tag(id = 24780, name = "Ibarad"), - Tag(id = 16238, name = "Ibaraki Kasen"), - Tag(id = 16711, name = "Ice Lee"), - Tag(id = 16105, name = "Ichinensei Ni Nacchattara"), - Tag(id = 21720, name = "Idumi"), - Tag(id = 15874, name = "If Code"), - Tag(id = 18986, name = "Iiniku Ushijima"), - Tag(id = 17229, name = "Iisuke"), - Tag(id = 16788, name = "Ikebukuro"), - Tag(id = 15998, name = "Ikenie Seminar"), - Tag(id = 16339, name = "Im Dal-young"), - Tag(id = 15973, name = "Immoral Maika"), - Tag(id = 220, name = "Impregnation"), - Tag(id = 18800, name = "Inazuma Blade"), - Tag(id = 203, name = "Incest"), - Tag(id = 15868, name = "Incomplete"), - Tag(id = 8840, name = "Infantilism"), - Tag(id = 221, name = "Inflation"), - Tag(id = 21709, name = "Inijio"), - Tag(id = 17962, name = "Inner Moka"), - Tag(id = 16144, name = "Inoue"), - Tag(id = 236, name = "Insect"), - Tag(id = 4648, name = "Insect Boy"), - Tag(id = 2469, name = "Insect Girl"), - Tag(id = 1252, name = "Inseki"), - Tag(id = 16978, name = "Inugoya Kakko Kari"), - Tag(id = 22358, name = "Inuguro Sansei"), - Tag(id = 24207, name = "Inui Achu"), - Tag(id = 24994, name = "Inunabe"), - Tag(id = 23760, name = "Inuzuka Koutarou"), - Tag(id = 242, name = "Inverted Nipples"), - Tag(id = 1424, name = "Invisible"), - Tag(id = 20910, name = "Ireading"), - Tag(id = 18318, name = "Irukukwu"), - Tag(id = 16885, name = "Isana Tachibana"), - Tag(id = 16498, name = "Isobe Mutsumi"), - Tag(id = 16276, name = "Issei Ryuudou"), - Tag(id = 16701, name = "Itigosizu Eri Natsume"), - Tag(id = 17005, name = "Itomari"), - Tag(id = 15975, name = "Itsuki Sayaka"), - Tag(id = 23386, name = "Iwapero"), - Tag(id = 16142, name = "Izawa"), - Tag(id = 25181, name = "Izu"), - Tag(id = 17556, name = "Izumi-s"), - Tag(id = 21941, name = "Izumida Touichirou"), - Tag(id = 16468, name = "Jace Beleren"), - Tag(id = 19082, name = "Jack O Lantern"), - Tag(id = 16157, name = "Jaga Note"), - Tag(id = 16942, name = "Jake Muller"), - Tag(id = 16321, name = "Jibakurei"), - Tag(id = 25577, name = "Jiggly Toons"), - Tag(id = 17156, name = "Jika"), - Tag(id = 19874, name = "Joe Hasegawa"), - Tag(id = 17324, name = "Johnny-do"), - Tag(id = 16774, name = "Johnnys Jimusho"), - Tag(id = 16950, name = "Jomy Marquis Shin"), - Tag(id = 16263, name = "Jonokuchi Joji"), - Tag(id = 18310, name = "Jony"), - Tag(id = 18990, name = "Jonylaser R"), - Tag(id = 17778, name = "Joso No Oji-sama"), - Tag(id = 2231, name = "Josou Seme"), - Tag(id = 24084, name = "Jsc"), - Tag(id = 16495, name = "Juana"), - Tag(id = 16222, name = "Jun686"), - Tag(id = 23865, name = "Junior"), - Tag(id = 16625, name = "Junjou Kurarimondo"), - Tag(id = 22065, name = "Junkie Daijin"), - Tag(id = 16078, name = "Juuichijou"), - Tag(id = 20745, name = "Juurokurou"), - Tag(id = 23758, name = "K.tomo"), - Tag(id = 15984, name = "K.z.z."), - Tag(id = 15985, name = "K.z.z. Force"), - Tag(id = 15986, name = "K.z.z. Gundan"), - Tag(id = 17660, name = "K2tomonokai"), - Tag(id = 22223, name = "Kabi Killer"), - Tag(id = 17227, name = "Kabutomaru Choko"), - Tag(id = 24023, name = "Kadarinka"), - Tag(id = 23374, name = "Kagayakitei"), - Tag(id = 16267, name = "Kagehara Hanzow"), - Tag(id = 16976, name = "Kagura Heki"), - Tag(id = 19909, name = "Kaijuu"), - Tag(id = 18440, name = "Kaishou Nachi"), - Tag(id = 17147, name = "Kaitouchuu."), - Tag(id = 23508, name = "Kakeyu"), - Tag(id = 16584, name = "Kamadoya"), - Tag(id = 21085, name = "Kamatsukatei"), - Tag(id = 20255, name = "Kamiaya"), - Tag(id = 16315, name = "Kaminobe"), - Tag(id = 16316, name = "Kaminobe Kanon"), - Tag(id = 18528, name = "Kamiomi Tsukuyomi"), - Tag(id = 16606, name = "Kamo Nabako"), - Tag(id = 17155, name = "Kaname Itsuki"), - Tag(id = 16274, name = "Kanatofu"), - Tag(id = 23940, name = "Kancho"), - Tag(id = 15912, name = "Kankuro"), - Tag(id = 18378, name = "Kannagi No Tori"), - Tag(id = 24265, name = "Kano Yattsu Hashi"), - Tag(id = 16826, name = "Kanran Okawa"), - Tag(id = 18818, name = "Kanu Uncho"), - Tag(id = 5660, name = "Kappa"), - Tag(id = 21307, name = "Kare-nidaikon"), - Tag(id = 24171, name = "Karhu"), - Tag(id = 17168, name = "Kasai Yukiha"), - Tag(id = 18360, name = "Kashinopon"), - Tag(id = 16519, name = "Katanashi Apollo"), - Tag(id = 19616, name = "Katou Setsuko"), - Tag(id = 16831, name = "Katsumi Liqueur"), - Tag(id = 21010, name = "Kawada"), - Tag(id = 16456, name = "Kawakita Hiroyuki"), - Tag(id = 16850, name = "Kazemichi"), - Tag(id = 15892, name = "Kazoku Yuugi"), - Tag(id = 24881, name = "Kazumi Araiwa"), - Tag(id = 15956, name = "Kdft"), - Tag(id = 23717, name = "Kebiishi"), - Tag(id = 15972, name = "Keikihei"), - Tag(id = 15878, name = "Keitaro Arima"), - Tag(id = 80, name = "Kemonomimi"), - Tag(id = 16979, name = "Kemonoskey"), - Tag(id = 16557, name = "Ken Fudou"), - Tag(id = 23880, name = "Kenesco"), - Tag(id = 23881, name = "Kenesu"), - Tag(id = 16558, name = "Kenn Fudou"), - Tag(id = 20769, name = "Kenta Yumiya"), - Tag(id = 25043, name = "Kichihachi"), - Tag(id = 21547, name = "Kien-biu"), - Tag(id = 1155, name = "Kigurumi"), - Tag(id = 16633, name = "Kimiaki Shirai"), - Tag(id = 16340, name = "Kimkwang-hyun"), - Tag(id = 17199, name = "Kimmie"), - Tag(id = 689, name = "Kimono"), - Tag(id = 16627, name = "Kin-tore"), - Tag(id = 6173, name = "Kindergarten Uniform"), - Tag(id = 16761, name = "Kinkaku"), - Tag(id = 17116, name = "Kinmechou Pinku"), - Tag(id = 22920, name = "Kinnataro"), - Tag(id = 16834, name = "Kino Asana"), - Tag(id = 22586, name = "Kinomiya Yutaka"), - Tag(id = 17842, name = "Kira Asamiya"), - Tag(id = 16671, name = "Kiri Gami Shima"), - Tag(id = 16672, name = "Kiri Shin Shima"), - Tag(id = 16355, name = "Kirimochi Niwe"), - Tag(id = 724, name = "Kissing"), - Tag(id = 16793, name = "Kitamakura"), - Tag(id = 18682, name = "Kitou Chimata"), - Tag(id = 25255, name = "Kitsunekov"), - Tag(id = 3473, name = "Kneepit Sex"), - Tag(id = 21820, name = "Kobamiso"), - Tag(id = 17109, name = "Koboshi"), - Tag(id = 21384, name = "Kohako"), - Tag(id = 16141, name = "Kohe"), - Tag(id = 18298, name = "Koi Mo Mata Utau"), - Tag(id = 17325, name = "Koikuchikinako"), - Tag(id = 25612, name = "Kokitsuma"), - Tag(id = 15896, name = "Kokuritsu Shounen"), - Tag(id = 16678, name = "Kokuryu"), - Tag(id = 17267, name = "Kokuto Nikke"), - Tag(id = 23694, name = "Konakona"), - Tag(id = 23872, name = "Konbu Kyoudai"), - Tag(id = 23151, name = "Konezu"), - Tag(id = 16851, name = "Konno Kita"), - Tag(id = 16949, name = "Konoka"), - Tag(id = 16306, name = "Koridorasu"), - Tag(id = 19523, name = "Kotarou Katsura"), - Tag(id = 16922, name = "Kotoe"), - Tag(id = 19555, name = "Kou-chan"), - Tag(id = 18799, name = "Koube Iori"), - Tag(id = 17278, name = "Kozo Youhei"), - Tag(id = 21828, name = "Kozountoko"), - Tag(id = 15901, name = "Kudara Naizo"), - Tag(id = 16424, name = "Kumazaki Satoru"), - Tag(id = 17658, name = "Kunisaki"), - Tag(id = 377, name = "Kunoichi"), - Tag(id = 24057, name = "Kuntakku"), - Tag(id = 16870, name = "Kurajin"), - Tag(id = 17086, name = "Kurazushi"), - Tag(id = 24069, name = "Kurenai Okome"), - Tag(id = 19039, name = "Kurokami Kujika"), - Tag(id = 22340, name = "Kuromaru"), - Tag(id = 17215, name = "Kuromoinu No Kemono"), - Tag(id = 16138, name = "Kuroshiro"), - Tag(id = 17002, name = "Kurumigi Kurumi"), - Tag(id = 20608, name = "Kuryu Josai"), - Tag(id = 20395, name = "Kusogaki Teikoku"), - Tag(id = 20572, name = "Kusoge"), - Tag(id = 24249, name = "Kussie"), - Tag(id = 23474, name = "Kusu"), - Tag(id = 16114, name = "Kuu"), - Tag(id = 16695, name = "Kuukaku"), - Tag(id = 16265, name = "Kyojinkou"), - Tag(id = 19338, name = "Kyojitsu Himaku"), - Tag(id = 20583, name = "Kyoroukan"), - Tag(id = 16955, name = "Kyougoku Akira"), - Tag(id = 212, name = "Lab Coat"), - Tag(id = 204, name = "Lactation"), - Tag(id = 24573, name = "Landstalker"), - Tag(id = 22394, name = "Langley"), - Tag(id = 222, name = "Large Insertions"), - Tag(id = 15925, name = "Largo"), - Tag(id = 75, name = "Latex"), - Tag(id = 1407, name = "Layer Cake"), - Tag(id = 25125, name = "Leash"), - Tag(id = 16341, name = "Lee Soo-hyon"), - Tag(id = 16998, name = "Lee Yo Dong"), - Tag(id = 1647, name = "Leg Lock"), - Tag(id = 7978, name = "Legjob"), - Tag(id = 17052, name = "Lenna"), - Tag(id = 16985, name = "Leon"), - Tag(id = 22295, name = "Leonstar"), - Tag(id = 958, name = "Leotard"), - Tag(id = 16665, name = "Lhytiss"), - Tag(id = 19309, name = "Liangshan Bo"), - Tag(id = 16290, name = "Liaswe No.9"), - Tag(id = 16517, name = "Lillian Ljungstrom"), - Tag(id = 17102, name = "Ling"), - Tag(id = 782, name = "Lingerie"), - Tag(id = 8995, name = "Lion"), - Tag(id = 20966, name = "Lioness"), - Tag(id = 15916, name = "Little Retro"), - Tag(id = 710, name = "Living Clothes"), - Tag(id = 3709, name = "Lizard Girl"), - Tag(id = 2491, name = "Lizard Guy"), - Tag(id = 54, name = "Lolicon"), - Tag(id = 16143, name = "Lolita Core"), - Tag(id = 2742, name = "Long Tongue"), - Tag(id = 25252, name = "Lordkingu"), - Tag(id = 17085, name = "Love Junky"), - Tag(id = 16261, name = "Lovely Kinoko"), - Tag(id = 3681, name = "Low Bestiality"), - Tag(id = 94, name = "Low Lolicon"), - Tag(id = 1938, name = "Low Shotacon"), - Tag(id = 16943, name = "M Company"), - Tag(id = 16541, name = "M Works"), - Tag(id = 16221, name = "M2 Company"), - Tag(id = 23339, name = "Maako Asagiri"), - Tag(id = 16931, name = "Macaroni.e"), - Tag(id = 16957, name = "Machan Nankin"), - Tag(id = 271, name = "Machine"), - Tag(id = 23066, name = "Machino Suteinu"), - Tag(id = 23767, name = "Machio"), - Tag(id = 13273, name = "Maggot"), - Tag(id = 640, name = "Magical Girl"), - Tag(id = 16059, name = "Mahou Shounen Majorian"), - Tag(id = 55, name = "Maid"), - Tag(id = 25194, name = "Maita Keikaku"), - Tag(id = 16243, name = "Majorina"), - Tag(id = 17572, name = "Makamaka Dou"), - Tag(id = 25636, name = "Makeup"), - Tag(id = 16004, name = "Makie Sazaki"), - Tag(id = 24882, name = "Makoto Araiwa"), - Tag(id = 16744, name = "Makoto Sako"), - Tag(id = 15886, name = "Makoto Teteno"), - Tag(id = 22583, name = "Malboro"), - Tag(id = 95, name = "Male On Dickgirl"), - Tag(id = 686, name = "Males Only"), - Tag(id = 19159, name = "Mami Itou"), - Tag(id = 15950, name = "Manaka De Ikuno"), - Tag(id = 25025, name = "Manga Jigoku"), - Tag(id = 16486, name = "Mangetsu Ujiya"), - Tag(id = 16208, name = "Mannmaru"), - Tag(id = 23178, name = "Mao Fa Bao"), - Tag(id = 22921, name = "Maou Kyuu"), - Tag(id = 24668, name = "Mari Tamaki"), - Tag(id = 21395, name = "Marie Ange"), - Tag(id = 19487, name = "Marika Hoshino"), - Tag(id = 20235, name = "Marusuke"), - Tag(id = 19645, name = "Maruya Kae"), - Tag(id = 16437, name = "Mashiba Kenta"), - Tag(id = 118, name = "Masked Face"), - Tag(id = 23319, name = "Master Asia"), - Tag(id = 90, name = "Masturbation"), - Tag(id = 16647, name = "Masuda Aura"), - Tag(id = 23163, name = "Mata Kara Stream"), - Tag(id = 16307, name = "Matsukazon"), - Tag(id = 15891, name = "Matsumoto Mimi"), - Tag(id = 16969, name = "Mazala."), - Tag(id = 16463, name = "Me Ten"), - Tag(id = 21432, name = "Mebaeros"), - Tag(id = 1260, name = "Mecha Boy"), - Tag(id = 1615, name = "Mecha Girl"), - Tag(id = 16932, name = "Medetaya"), - Tag(id = 24450, name = "Meikyukoubou"), - Tag(id = 16825, name = "Melibe Mukade"), - Tag(id = 5329, name = "Menstruation"), - Tag(id = 5201, name = "Mermaid"), - Tag(id = 5341, name = "Merman"), - Tag(id = 25382, name = "Merryweather"), - Tag(id = 959, name = "Metal Armor"), - Tag(id = 24906, name = "Metameta"), - Tag(id = 24907, name = "Metametadan"), - Tag(id = 20406, name = "Metata"), - Tag(id = 16617, name = "Miakis"), - Tag(id = 16429, name = "Michiru Katou"), - Tag(id = 16085, name = "Microgravity"), - Tag(id = 3289, name = "Midget"), - Tag(id = 17027, name = "Midoh Tsukasaa"), - Tag(id = 18452, name = "Mikado Muramasa"), - Tag(id = 18687, name = "Mikado Sensei"), - Tag(id = 17065, name = "Mikage Mika"), - Tag(id = 152, name = "Miko"), - Tag(id = 22689, name = "Mikumo"), - Tag(id = 18023, name = "Mikuni Jiou"), - Tag(id = 16363, name = "Mikupantu"), - Tag(id = 205, name = "Milf"), - Tag(id = 17078, name = "Milfeuille Sakuraba"), - Tag(id = 811, name = "Military"), - Tag(id = 17476, name = "Dekamarasu Scirocco"), - Tag(id = 685, name = "Demon"), - Tag(id = 78, name = "Demon Girl"), - Tag(id = 17023, name = "Deriken"), - Tag(id = 17024, name = "Deriya"), - Tag(id = 16876, name = "Descent Into Darkness"), - Tag(id = 24824, name = "Detention"), - Tag(id = 1151, name = "Diaper"), - Tag(id = 23413, name = "Dic-f41"), - Tag(id = 149, name = "Dick Growth"), - Tag(id = 150, name = "Dickgirl On Dickgirl"), - Tag(id = 16693, name = "Dickgirl On Female"), - Tag(id = 266, name = "Dickgirl On Male"), - Tag(id = 3068, name = "Dickgirls Only"), - Tag(id = 2735, name = "Dicknipples"), - Tag(id = 24289, name = "Digianko"), - Tag(id = 111, name = "Dilf"), - Tag(id = 16926, name = "Dingo Egret"), - Tag(id = 16927, name = "Dingo Igrit"), - Tag(id = 7096, name = "Dinosaur"), - Tag(id = 19202, name = "Dismantling"), - Tag(id = 17018, name = "Doburoki"), - Tag(id = 261, name = "Dog"), - Tag(id = 446, name = "Dog Boy"), - Tag(id = 296, name = "Dog Girl"), - Tag(id = 15889, name = "Dogu Bros."), - Tag(id = 16974, name = "Dojidom"), - Tag(id = 17952, name = "Doku Ninjin"), - Tag(id = 5464, name = "Doll Joints"), - Tag(id = 2506, name = "Dolphin"), - Tag(id = 5665, name = "Donkey"), - Tag(id = 18545, name = "Doppel Gangers"), - Tag(id = 22418, name = "Dorina"), - Tag(id = 22635, name = "Dosukoi"), - Tag(id = 1525, name = "Double Anal"), - Tag(id = 1589, name = "Double Blowjob"), - Tag(id = 133, name = "Double Penetration"), - Tag(id = 17249, name = "Double Sensei Life"), - Tag(id = 217, name = "Double Vaginal"), - Tag(id = 1229, name = "Dougi"), - Tag(id = 16490, name = "Doumeki"), - Tag(id = 5865, name = "Dragon"), - Tag(id = 24209, name = "Drasna"), - Tag(id = 23510, name = "Drawg"), - Tag(id = 19857, name = "Dreamers Fantasy"), - Tag(id = 108, name = "Drugs"), - Tag(id = 775, name = "Drunk"), - Tag(id = 16113, name = "Duga"), - Tag(id = 24408, name = "Eagle Marin"), - Tag(id = 4675, name = "Ear Fuck"), - Tag(id = 19315, name = "Ebio"), - Tag(id = 18286, name = "Ebipan"), - Tag(id = 17371, name = "Ebisuya"), - Tag(id = 1157, name = "Eel"), - Tag(id = 1273, name = "Eggs"), - Tag(id = 23385, name = "Eji"), - Tag(id = 1698, name = "Electric Shocks"), - Tag(id = 24182, name = "Elephant"), - Tag(id = 748, name = "Elf"), - Tag(id = 24369, name = "Elf-san Wa Yaserarenai."), - Tag(id = 16643, name = "Elizaveta Hedervary"), - Tag(id = 16929, name = "Emeralda Etuva"), - Tag(id = 417, name = "Emotionless Sex"), - Tag(id = 17236, name = "Ena Seishuin"), - Tag(id = 17237, name = "Ena Seishuuin"), - Tag(id = 16930, name = "Endou Macaroni"), - Tag(id = 297, name = "Enema"), - Tag(id = 19026, name = "Ero Hon"), - Tag(id = 16815, name = "Erotic Mania"), - Tag(id = 20271, name = "Etk"), - Tag(id = 24870, name = "Etori Yuuya"), - Tag(id = 16417, name = "Ex-driver"), - Tag(id = 17088, name = "Executant"), - Tag(id = 17044, name = "Executional"), - Tag(id = 134, name = "Exhibitionism"), - Tag(id = 16482, name = "Exorcist Miko"), - Tag(id = 1815, name = "Eye Penetration"), - Tag(id = 1699, name = "Eyemask"), - Tag(id = 1227, name = "Eyepatch"), - Tag(id = 20744, name = "Eyo"), - Tag(id = 176, name = "Facesitting"), - Tag(id = 232, name = "Fairy"), - Tag(id = 19301, name = "Farron"), - Tag(id = 974, name = "Farting"), - Tag(id = 1890, name = "Father"), - Tag(id = 16027, name = "Faulklin"), - Tag(id = 16966, name = "Fcnurse"), - Tag(id = 16861, name = "Femal"), - Tag(id = 101, name = "Females Only"), - Tag(id = 240, name = "Femdom"), - Tag(id = 455, name = "Feminization"), - Tag(id = 15865, name = "Ffm Threesome"), - Tag(id = 1406, name = "Fft Threesome"), - Tag(id = 16337, name = "Figure"), - Tag(id = 528, name = "Filming"), - Tag(id = 11, name = "Fingering"), - Tag(id = 23994, name = "Firing Pin"), - Tag(id = 376, name = "First Person Perspective"), - Tag(id = 10947, name = "Fish"), - Tag(id = 13079, name = "Fishnets"), - Tag(id = 218, name = "Fisting"), - Tag(id = 24402, name = "Flaccid"), - Tag(id = 21373, name = "Flamenco Diamond"), - Tag(id = 21374, name = "Flamenco Ruby"), - Tag(id = 21375, name = "Flamenco Sapphire"), - Tag(id = 536, name = "Foot Insertion"), - Tag(id = 502, name = "Foot Licking"), - Tag(id = 355, name = "Footjob"), - Tag(id = 24432, name = "Forbidden Content"), - Tag(id = 514, name = "Forniphilia"), - Tag(id = 9242, name = "Fox"), - Tag(id = 716, name = "Fox Boy"), - Tag(id = 1094, name = "Fox Girl"), - Tag(id = 25605, name = "Foxinshadow"), - Tag(id = 25286, name = "Foxxx321"), - Tag(id = 834, name = "Freckles"), - Tag(id = 22144, name = "Fredrika"), - Tag(id = 5458, name = "Frog"), - Tag(id = 7363, name = "Frog Girl"), - Tag(id = 151, name = "Frottage"), - Tag(id = 23984, name = "Fuji Potato"), - Tag(id = 18059, name = "Fujii Sakuya"), - Tag(id = 18155, name = "Fujikatsupiko"), - Tag(id = 17505, name = "Fujimoto Go"), - Tag(id = 16121, name = "Fujitani Sonami"), - Tag(id = 16421, name = "Fukai Youki"), - Tag(id = 16975, name = "Fukaumi Tadashito"), - Tag(id = 17200, name = "Fukuyama San"), - Tag(id = 791, name = "Full Body Tattoo"), - Tag(id = 24998, name = "Full Censored"), - Tag(id = 15866, name = "Full Censorship"), - Tag(id = 15871, name = "Full Color"), - Tag(id = 17284, name = "Fumotonoya"), - Tag(id = 1439, name = "Fundoshi"), - Tag(id = 563, name = "Furry"), - Tag(id = 20956, name = "Fushimori Tonkatsu"), - Tag(id = 79, name = "Futanari"), - Tag(id = 16756, name = "Futsuka"), - Tag(id = 21620, name = "Fuuka Kazaguruma"), - Tag(id = 17696, name = "Fuuzen No Tomoshibi"), - Tag(id = 16583, name = "Fuwa Kaduki"), - Tag(id = 19171, name = "Fwpa"), - Tag(id = 16828, name = "G-maru Edition"), - Tag(id = 18256, name = "G.to.me"), - Tag(id = 241, name = "Gag"), - Tag(id = 24168, name = "Gakuran"), - Tag(id = 23017, name = "Ganesha"), - Tag(id = 219, name = "Gaping"), - Tag(id = 316, name = "Garter Belt"), - Tag(id = 2778, name = "Gasmask"), - Tag(id = 22826, name = "Gasper Vladi"), - Tag(id = 20741, name = "Gass. Mosa"), - Tag(id = 17257, name = "Gayasabu"), - Tag(id = 21679, name = "Gekidan Futari"), - Tag(id = 267, name = "Gender Bender"), - Tag(id = 15873, name = "Gene Shalit"), - Tag(id = 16913, name = "Genjuro Kazanari"), - Tag(id = 16616, name = "Genso Suikoden"), - Tag(id = 17226, name = "Genso Suikoden 5"), - Tag(id = 17176, name = "Gesho Ichiro"), - Tag(id = 16818, name = "Gevanni"), - Tag(id = 923, name = "Ghost"), - Tag(id = 2744, name = "Giant"), - Tag(id = 1706, name = "Giantess"), - Tag(id = 25288, name = "Giantessamazons"), - Tag(id = 5218, name = "Gigantic Breasts"), - Tag(id = 16236, name = "Gijinka"), - Tag(id = 16760, name = "Ginkaku"), - Tag(id = 16620, name = "Girlchoco"), - Tag(id = 22566, name = "Glamorous Sky"), - Tag(id = 53, name = "Glasses"), - Tag(id = 3976, name = "Glory Hole"), - Tag(id = 9628, name = "Goat"), - Tag(id = 4296, name = "Goblin"), - Tag(id = 17238, name = "Godou Kusanagi"), - Tag(id = 17433, name = "Gogogo"), - Tag(id = 23116, name = "Gohan Oomori"), - Tag(id = 537, name = "Gokkun"), - Tag(id = 21383, name = "Goko"), - Tag(id = 10723, name = "Gorilla"), - Tag(id = 1330, name = "Gothic Lolita"), - Tag(id = 23831, name = "Gozenzeuna"), - Tag(id = 16435, name = "Grace Ishikawa"), - Tag(id = 24086, name = "Grad Lancia"), - Tag(id = 23889, name = "Gragas"), - Tag(id = 1904, name = "Granddaughter"), - Tag(id = 5121, name = "Grandfather"), - Tag(id = 16065, name = "Grandia Iii"), - Tag(id = 4724, name = "Grandmother"), - Tag(id = 17319, name = "Granny Smith"), - Tag(id = 17068, name = "Greek Myth"), - Tag(id = 25439, name = "Groovymidnightshow"), - Tag(id = 23631, name = "Grop"), - Tag(id = 695, name = "Group"), - Tag(id = 7097, name = "Growth"), - Tag(id = 20957, name = "Grpr"), - Tag(id = 34, name = "Guro"), - Tag(id = 1658, name = "Gyaru"), - Tag(id = 4454, name = "Gyaru-oh"), - Tag(id = 156, name = "Gymshorts"), - Tag(id = 17318, name = "Milkcure"), - Tag(id = 243, name = "Milking"), - Tag(id = 16602, name = "Millefiori Firianno Biscotti"), - Tag(id = 23274, name = "Millia"), - Tag(id = 18027, name = "Mimana Orimoto"), - Tag(id = 23225, name = "Mina Shirouto"), - Tag(id = 114, name = "Mind Break"), - Tag(id = 116, name = "Mind Control"), - Tag(id = 3724, name = "Minigirl"), - Tag(id = 5707, name = "Miniguy"), - Tag(id = 4842, name = "Minotaur"), - Tag(id = 23177, name = "Miou Ootori"), - Tag(id = 16345, name = "Mirayia"), - Tag(id = 16018, name = "Missing Cover"), - Tag(id = 21546, name = "Misuke"), - Tag(id = 16377, name = "Mitoukana"), - Tag(id = 19911, name = "Mitsuhide"), - Tag(id = 18486, name = "Mitsuko"), - Tag(id = 22161, name = "Mitsunoho"), - Tag(id = 19284, name = "Miu Otsuki"), - Tag(id = 25514, name = "Miyabe Kiwi"), - Tag(id = 23417, name = "Miyabi Ash"), - Tag(id = 19158, name = "Miyama Yasuhiro"), - Tag(id = 17060, name = "Mizupii"), - Tag(id = 15869, name = "Mmf Threesome"), - Tag(id = 16095, name = "Mmt Threesome"), - Tag(id = 16009, name = "Moeharuka Non"), - Tag(id = 23164, name = "Mohorovicic Matako"), - Tag(id = 23511, name = "Mokechi"), - Tag(id = 23765, name = "Moki"), - Tag(id = 25011, name = "Mokko"), - Tag(id = 22519, name = "Mokkuafunfun"), - Tag(id = 23903, name = "Mokottsu"), - Tag(id = 20625, name = "Momihama"), - Tag(id = 16346, name = "Momoishi"), - Tag(id = 24588, name = "Momoka"), - Tag(id = 16757, name = "Momoya"), - Tag(id = 7567, name = "Monkey"), - Tag(id = 6717, name = "Monoeye"), - Tag(id = 237, name = "Monster"), - Tag(id = 285, name = "Monster Girl"), - Tag(id = 18423, name = "Moon Zero"), - Tag(id = 22080, name = "Moonfishcafe"), - Tag(id = 17059, name = "Moonlight Panic"), - Tag(id = 3028, name = "Moral Degeneration"), - Tag(id = 20001, name = "Mori Udura"), - Tag(id = 22527, name = "Morizoh"), - Tag(id = 15867, name = "Mosaic Censorship"), - Tag(id = 206, name = "Mother"), - Tag(id = 14906, name = "Mouse"), - Tag(id = 734, name = "Mouse Boy"), - Tag(id = 1802, name = "Mouse Girl"), - Tag(id = 15910, name = "Mtf Threesome"), - Tag(id = 17083, name = "Mugen Fiammatta"), - Tag(id = 17124, name = "Mugifumi Tetsudauyo"), - Tag(id = 15863, name = "Multi-work Series"), - Tag(id = 3622, name = "Multiple Arms"), - Tag(id = 272, name = "Multiple Breasts"), - Tag(id = 21176, name = "Multiple Nipples"), - Tag(id = 481, name = "Multiple Paizuri"), - Tag(id = 1100, name = "Multiple Penises"), - Tag(id = 17228, name = "Mune-mune"), - Tag(id = 16884, name = "Muratataichi"), - Tag(id = 119, name = "Muscle"), - Tag(id = 20643, name = "Muscle Growth"), - Tag(id = 2197, name = "Mute"), - Tag(id = 16787, name = "Myon"), - Tag(id = 25638, name = "Myster Box"), - Tag(id = 15929, name = "Mystic Cage"), - Tag(id = 16449, name = "Nabeshima Akira"), - Tag(id = 16912, name = "Nabu Umedama"), - Tag(id = 20729, name = "Nagano-n"), - Tag(id = 16789, name = "Nagumo."), - Tag(id = 12, name = "Nakadashi"), - Tag(id = 20242, name = "Nakate Sae"), - Tag(id = 19813, name = "Nama Wasabi Honten"), - Tag(id = 16852, name = "Namida"), - Tag(id = 15958, name = "Nan"), - Tag(id = 15893, name = "Nanaki"), - Tag(id = 20068, name = "Nanaki Nanatarou"), - Tag(id = 16442, name = "Nanako Todoroki"), - Tag(id = 17096, name = "Nanami-kasuga"), - Tag(id = 15959, name = "Nann"), - Tag(id = 16832, name = "Naono Bokera"), - Tag(id = 16704, name = "Naruga"), - Tag(id = 20996, name = "Naruse Sakei"), - Tag(id = 15885, name = "Nase No Go"), - Tag(id = 22604, name = "Natalie"), - Tag(id = 17012, name = "Natsu An"), - Tag(id = 17158, name = "Natsu No Kumo"), - Tag(id = 15887, name = "Natsumi Takao"), - Tag(id = 16264, name = "Natsune Tachibana"), - Tag(id = 5644, name = "Navel Fuck"), - Tag(id = 1188, name = "Nazi"), - Tag(id = 16819, name = "Near"), - Tag(id = 287, name = "Necrophilia"), - Tag(id = 16123, name = "Negative From The Beginning"), - Tag(id = 17113, name = "Neji Musume"), - Tag(id = 16320, name = "Nekomarudou Honpo"), - Tag(id = 23607, name = "Nekota Shiro"), - Tag(id = 24552, name = "Nekoweapons"), - Tag(id = 16514, name = "Nesshisen"), - Tag(id = 105, name = "Netorare"), - Tag(id = 20880, name = "New Horizon"), - Tag(id = 805, name = "Niece"), - Tag(id = 22906, name = "Nijigen"), - Tag(id = 2822, name = "Ninja"), - Tag(id = 23632, name = "Nintai Akira"), - Tag(id = 10476, name = "Nipple Birth"), - Tag(id = 4719, name = "Nipple Expansion"), - Tag(id = 903, name = "Nipple Fuck"), - Tag(id = 16837, name = "Nishioka Kyoudai"), - Tag(id = 15906, name = "Nishizuki Chikara"), - Tag(id = 17174, name = "Nitta"), - Tag(id = 20829, name = "Nitta Kani"), - Tag(id = 18637, name = "Nizimu Me"), - Tag(id = 16572, name = "No Graffiti"), - Tag(id = 16248, name = "Nomoto Saharu"), - Tag(id = 25482, name = "Non-nude"), - Tag(id = 15983, name = "Nonorumia"), - Tag(id = 17805, name = "Noramushi"), - Tag(id = 25481, name = "Norman Maggot"), - Tag(id = 8273, name = "Nose Fuck"), - Tag(id = 1481, name = "Nose Hook"), - Tag(id = 15955, name = "Notsu"), - Tag(id = 16024, name = "Novel"), - Tag(id = 16155, name = "Nudity Only"), - Tag(id = 16364, name = "Nuko"), - Tag(id = 338, name = "Nun"), - Tag(id = 235, name = "Nurse"), - Tag(id = 18238, name = "Nururyun"), - Tag(id = 17035, name = "Nutmeg"), - Tag(id = 19856, name = "Nyankofujin"), - Tag(id = 22878, name = "Nyansei-fukko"), - Tag(id = 19730, name = "O-ide Riko"), - Tag(id = 19809, name = "Ochazukenori"), - Tag(id = 20689, name = "Ochine"), - Tag(id = 1440, name = "Octopus"), - Tag(id = 16279, name = "Ogiura Tomoko"), - Tag(id = 18991, name = "Ohkawa Aoi"), - Tag(id = 21315, name = "Ohno Kogorou"), - Tag(id = 22307, name = "Oidemase"), - Tag(id = 3088, name = "Oil"), - Tag(id = 24399, name = "Ojo Jotaro"), - Tag(id = 15991, name = "Ojou"), - Tag(id = 20508, name = "Okano Hajimetei"), - Tag(id = 16272, name = "Okayu Club"), - Tag(id = 19112, name = "Okome Takeichi"), - Tag(id = 8946, name = "Old Lady"), - Tag(id = 334, name = "Old Man"), - Tag(id = 22633, name = "Omae Umasou Da Na"), - Tag(id = 16766, name = "Omya"), - Tag(id = 1651, name = "Onahole"), - Tag(id = 1234, name = "Oni"), - Tag(id = 22587, name = "Oono Saeko"), - Tag(id = 18200, name = "Ootaki Miura"), - Tag(id = 17188, name = "Ootsuka Shunji"), - Tag(id = 20717, name = "Ootsuki Kyouko"), - Tag(id = 16151, name = "Oppai Brothers"), - Tag(id = 1013, name = "Oppai Loli"), - Tag(id = 20320, name = "Opq"), - Tag(id = 1650, name = "Orc"), - Tag(id = 17516, name = "Ore P 2-gou"), - Tag(id = 23659, name = "Ore To Kakuni To Abura Soba"), - Tag(id = 457, name = "Orgasm Denial"), - Tag(id = 16585, name = "Oruga Susumu"), - Tag(id = 16947, name = "Orunito"), - Tag(id = 15914, name = "Oshioki Yuki-chan"), - Tag(id = 24995, name = "Osovo"), - Tag(id = 25227, name = "Ostrich"), - Tag(id = 16634, name = "Osuneko Shoukai"), - Tag(id = 17862, name = "Otaku Life Japan"), - Tag(id = 20726, name = "Otome Kibun"), - Tag(id = 16941, name = "Otonano Omochiya"), - Tag(id = 16894, name = "Otosaki Tsubaki"), - Tag(id = 17152, name = "Otoskai Tsubaki"), - Tag(id = 17087, name = "Ototoi No Are"), - Tag(id = 24715, name = "Ougi Production"), - Tag(id = 16122, name = "Ousama Jihen"), - Tag(id = 15861, name = "Out Of Order"), - Tag(id = 23387, name = "Outcount"), - Tag(id = 24808, name = "Outdoor"), - Tag(id = 17133, name = "Outdoors"), - Tag(id = 1334, name = "Oyakodon"), - Tag(id = 16241, name = "Pacifier"), - Tag(id = 183, name = "Paizuri"), - Tag(id = 7823, name = "Panda Girl"), - Tag(id = 25178, name = "Pandaj"), - Tag(id = 22688, name = "Pandaya"), - Tag(id = 10970, name = "Panther"), - Tag(id = 135, name = "Pantyhose"), - Tag(id = 136, name = "Pantyjob"), - Tag(id = 17100, name = "Paopa Ship"), - Tag(id = 827, name = "Parasite"), - Tag(id = 17137, name = "Partial Color"), - Tag(id = 16412, name = "Partially Translated"), - Tag(id = 1516, name = "Pasties"), - Tag(id = 23693, name = "Pecora"), - Tag(id = 456, name = "Pegging"), - Tag(id = 17153, name = "Pekopon Shinryaku Hyougikai"), - Tag(id = 7368, name = "Penis Birth"), - Tag(id = 16563, name = "Peri"), - Tag(id = 2136, name = "Petrification"), - Tag(id = 1515, name = "Phimosis"), - Tag(id = 2432, name = "Phone Sex"), - Tag(id = 18992, name = "Pied A Terre"), - Tag(id = 299, name = "Piercing"), - Tag(id = 600, name = "Pig"), - Tag(id = 5713, name = "Pig Girl"), - Tag(id = 4211, name = "Pig Man"), - Tag(id = 1153, name = "Pillory"), - Tag(id = 7261, name = "Pirate"), - Tag(id = 1674, name = "Piss Drinking"), - Tag(id = 22865, name = "Pisti"), - Tag(id = 793, name = "Plant Girl"), - Tag(id = 22520, name = "Poki"), - Tag(id = 1399, name = "Pole Dancing"), - Tag(id = 1732, name = "Policeman"), - Tag(id = 320, name = "Policewoman"), - Tag(id = 22829, name = "Pometa"), - Tag(id = 3599, name = "Ponygirl"), - Tag(id = 760, name = "Ponytail"), - Tag(id = 15894, name = "Poor Grammar"), - Tag(id = 16310, name = "Poporo"), - Tag(id = 16453, name = "Porunamin C"), - Tag(id = 538, name = "Possession"), - Tag(id = 268, name = "Pregnant"), - Tag(id = 503, name = "Prehensile Hair"), - Tag(id = 11469, name = "Priest"), - Tag(id = 20497, name = "Principal Kuno"), - Tag(id = 25407, name = "Project Physalis"), - Tag(id = 1496, name = "Prolapse"), - Tag(id = 294, name = "Prostate Massage"), - Tag(id = 931, name = "Prostitution"), - Tag(id = 16367, name = "Psycho Soldier"), - Tag(id = 1482, name = "Pubic Stubble"), - Tag(id = 2990, name = "Public Use"), - Tag(id = 16795, name = "Purimu"), - Tag(id = 16250, name = "Purupyon"), - Tag(id = 18500, name = "Pythagora Switch"), - Tag(id = 19115, name = "Quattro"), - Tag(id = 20138, name = "R-otome"), - Tag(id = 17110, name = "Rabbit Revelry Inc."), - Tag(id = 12938, name = "Raccoon Boy"), - Tag(id = 3567, name = "Raccoon Girl"), - Tag(id = 6453, name = "Race Queen"), - Tag(id = 21425, name = "Raichi Hoshimiya"), - Tag(id = 21955, name = "Rajyaki"), - Tag(id = 16139, name = "Rakanka Tiger"), - Tag(id = 23718, name = "Rakuen Tsuihou"), - Tag(id = 15920, name = "Rakugaki Syachu"), - Tag(id = 19071, name = "Randamu"), - Tag(id = 2551, name = "Randoseru"), - Tag(id = 43, name = "Rape"), - Tag(id = 17006, name = "Rati"), - Tag(id = 18186, name = "Rebaudio"), - Tag(id = 23800, name = "Recording"), - Tag(id = 15915, name = "Redraw"), - Tag(id = 17032, name = "Reese"), - Tag(id = 19632, name = "Reisen Two"), - Tag(id = 17270, name = "Remilia"), - Tag(id = 20382, name = "Renekton"), - Tag(id = 15890, name = "Replaced"), - Tag(id = 5574, name = "Reptile"), - Tag(id = 16858, name = "Resi"), - Tag(id = 16257, name = "Retreat"), - Tag(id = 24087, name = "Ricardo Gavarni"), - Tag(id = 16835, name = "Rika Koushu"), - Tag(id = 16836, name = "Rika Kousyu"), - Tag(id = 223, name = "Rimjob"), - Tag(id = 16439, name = "Rinbu"), - Tag(id = 18907, name = "Ringo Koubou"), - Tag(id = 17252, name = "Ritomasu"), - Tag(id = 16659, name = "Rittiri"), - Tag(id = 56, name = "Robot"), - Tag(id = 15872, name = "Rock Dou"), - Tag(id = 24880, name = "Rofu Pukaj"), - Tag(id = 19929, name = "Roudoc 2-gou"), - Tag(id = 15960, name = "Route A"), - Tag(id = 16682, name = "Rurio Nagase"), - Tag(id = 17810, name = "Ryo Yazaki"), - Tag(id = 16481, name = "Ryo-ko"), - Tag(id = 21672, name = "Ryokurin"), - Tag(id = 224, name = "Ryona"), - Tag(id = 17179, name = "Ryouji Sawa"), - Tag(id = 23147, name = "Ryouta Murakami"), - Tag(id = 16365, name = "Ryu Morikawa"), - Tag(id = 16457, name = "Ryuho"), - Tag(id = 16063, name = "Ryuji Kawamoto"), - Tag(id = 16021, name = "Ryuushika"), - Tag(id = 16944, name = "Sabatama Yumi"), - Tag(id = 15897, name = "Sabato Mihashigo"), - Tag(id = 16996, name = "Sabawo 380"), - Tag(id = 16518, name = "Sagasile"), - Tag(id = 16402, name = "Sai Akuto"), - Tag(id = 24085, name = "Saifu"), - Tag(id = 16670, name = "Saikare."), - Tag(id = 17177, name = "Saikoro"), - Tag(id = 17017, name = "Saikyo Tomomi"), - Tag(id = 17146, name = "Sailor Aluminum Siren"), - Tag(id = 16973, name = "Saimin"), - Tag(id = 16013, name = "Saimin Pikatto House"), - Tag(id = 16186, name = "Sakazaki"), - Tag(id = 16252, name = "Saki Watanabe"), - Tag(id = 15883, name = "Sakino Asuka"), - Tag(id = 23322, name = "Sakki Okita"), - Tag(id = 23612, name = "Sakkimita"), - Tag(id = 24437, name = "Sakura Kagamihara"), - Tag(id = 23768, name = "Sakurabobu"), - Tag(id = 423, name = "Saliva"), - Tag(id = 15881, name = "Sammohung"), - Tag(id = 15882, name = "Samohanyunpyou"), - Tag(id = 16125, name = "Sample"), - Tag(id = 15928, name = "Sangokushi Puzzle Taisen"), - Tag(id = 17117, name = "Santa Maria"), - Tag(id = 24336, name = "Sarugaso"), - Tag(id = 16506, name = "Sasara Somae"), - Tag(id = 16694, name = "Sata"), - Tag(id = 16586, name = "Satellite U"), - Tag(id = 16375, name = "Satoshi Sou"), - Tag(id = 16824, name = "Satou Kiyotoshi"), - Tag(id = 23366, name = "Satsuki Marin"), - Tag(id = 20566, name = "Sautsu"), - Tag(id = 19124, name = "Sawara Cashy"), - Tag(id = 16428, name = "Sayuri Hirose"), - Tag(id = 15876, name = "Scanmark"), - Tag(id = 1336, name = "Scar"), - Tag(id = 273, name = "Scat"), - Tag(id = 68, name = "School Swimsuit"), - Tag(id = 22, name = "Schoolboy Uniform"), - Tag(id = 24, name = "Schoolgirl Uniform"), - Tag(id = 17103, name = "Schwarzkatze"), - Tag(id = 17104, name = "Schwarzkratze"), - Tag(id = 6068, name = "Scrotal Lingerie"), - Tag(id = 17224, name = "Seikan Nekoguruma"), - Tag(id = 17995, name = "Sekaiju No Anone"), - Tag(id = 788, name = "Selfcest"), - Tag(id = 17004, name = "Semaru Taiho"), - Tag(id = 16440, name = "Sen Renbu"), - Tag(id = 17171, name = "Senbonzakura"), - Tag(id = 18677, name = "Sendai Oni"), - Tag(id = 23873, name = "Sendaiki"), - Tag(id = 22387, name = "Senhime"), - Tag(id = 17322, name = "Senki Zesshou"), - Tag(id = 17809, name = "Setsuko"), - Tag(id = 109, name = "Sex Toys"), - Tag(id = 16644, name = "Sexual Sunday"), - Tag(id = 16438, name = "Shadow Thorn"), - Tag(id = 331, name = "Shared Senses"), - Tag(id = 20051, name = "Shark"), - Tag(id = 3974, name = "Shark Boy"), - Tag(id = 22443, name = "Sharon"), - Tag(id = 5823, name = "Shaved Head"), - Tag(id = 15652, name = "Sheep Boy"), - Tag(id = 784, name = "Sheep Girl"), - Tag(id = 458, name = "Shemale"), - Tag(id = 25341, name = "Sherwood"), - Tag(id = 21466, name = "Shibainu Lab"), - Tag(id = 321, name = "Shibari"), - Tag(id = 17154, name = "Shichimi Hacchin"), - Tag(id = 23344, name = "Shikiouzi"), - Tag(id = 22907, name = "Shima Shuu"), - Tag(id = 21991, name = "Shimada Hisami"), - Tag(id = 137, name = "Shimapan"), - Tag(id = 16860, name = "Shimotsuki Kairi"), - Tag(id = 22950, name = "Shina Mon"), - Tag(id = 21673, name = "Shinna"), - Tag(id = 18092, name = "Shinomiya Utai"), - Tag(id = 16190, name = "Shinooka Fuku Enchou"), - Tag(id = 20450, name = "Shinoshima Usa"), - Tag(id = 18494, name = "Shinsuke Inue"), - Tag(id = 16822, name = "Shinsuke Nishizono"), - Tag(id = 17370, name = "Shintaro Konno"), - Tag(id = 24070, name = "Shio Onigiri"), - Tag(id = 16451, name = "Shiokaze Kaidou"), - Tag(id = 19727, name = "Shiomeshi"), - Tag(id = 18973, name = "Shirokuma Sato"), - Tag(id = 18720, name = "Shirotakurota"), - Tag(id = 16914, name = "Shirow"), - Tag(id = 17014, name = "Shisaki Tayuu"), - Tag(id = 20045, name = "Shizu Shidou"), - Tag(id = 16014, name = "Short Circuit"), - Tag(id = 66, name = "Shotacon"), - Tag(id = 16970, name = "Shounen Kyouso"), - Tag(id = 18864, name = "Shounen Teacher Group"), - Tag(id = 17053, name = "Shoutai Fumei"), - Tag(id = 17054, name = "Shoutai Humei"), - Tag(id = 711, name = "Shrinking"), - Tag(id = 17130, name = "Shugetsu"), - Tag(id = 22637, name = "Shunju"), - Tag(id = 21905, name = "Shut Hell"), - Tag(id = 23240, name = "Shuuyu Koukin"), - Tag(id = 24355, name = "Sibasaki Koh"), - Tag(id = 15943, name = "Silver Sandial"), - Tag(id = 16041, name = "Sinyati"), - Tag(id = 138, name = "Sister"), - Tag(id = 17329, name = "Sjf"), - Tag(id = 16020, name = "Ski"), - Tag(id = 5877, name = "Skinsuit"), - Tag(id = 19682, name = "Skn"), - Tag(id = 300, name = "Slave"), - Tag(id = 153, name = "Sleeping"), - Tag(id = 1449, name = "Slime"), - Tag(id = 13461, name = "Slime Boy"), - Tag(id = 1408, name = "Slime Girl"), - Tag(id = 22104, name = "Slime King"), - Tag(id = 23847, name = "Slow Masturbation Research Society"), - Tag(id = 4597, name = "Slug"), - Tag(id = 32, name = "Small Breasts"), - Tag(id = 25364, name = "Small Penis"), - Tag(id = 429, name = "Smegma"), - Tag(id = 1254, name = "Smell"), - Tag(id = 3524, name = "Smoking"), - Tag(id = 8672, name = "Snail Girl"), - Tag(id = 4702, name = "Snake"), - Tag(id = 23232, name = "Snake Boy"), - Tag(id = 785, name = "Snake Girl"), - Tag(id = 254, name = "Snuff"), - Tag(id = 6084, name = "Sockjob"), - Tag(id = 16849, name = "Softcel Pictures"), - Tag(id = 16846, name = "Soichiro Arima"), - Tag(id = 81, name = "Sole Dickgirl"), - Tag(id = 25, name = "Sole Female"), - Tag(id = 23, name = "Sole Male"), - Tag(id = 24229, name = "Solefemale"), - Tag(id = 608, name = "Solo Action"), - Tag(id = 18433, name = "Solullaby"), - Tag(id = 23404, name = "Son-son"), - Tag(id = 20256, name = "Sorairo Porin"), - Tag(id = 17197, name = "Sorauta"), - Tag(id = 24446, name = "Southern Emperor"), - Tag(id = 1359, name = "Spanking"), - Tag(id = 23067, name = "Spanner And Camellia"), - Tag(id = 889, name = "Speculum"), - Tag(id = 18061, name = "Speedy"), - Tag(id = 16215, name = "Spicaya"), - Tag(id = 11905, name = "Spider"), - Tag(id = 3623, name = "Spider Girl"), - Tag(id = 16823, name = "Spirit Of Ecsta"), - Tag(id = 16917, name = "Spon"), - Tag(id = 16898, name = "Square Rain"), - Tag(id = 5355, name = "Squid Boy"), - Tag(id = 1582, name = "Squid Girl"), - Tag(id = 7230, name = "Squirrel Girl"), - Tag(id = 786, name = "Squirting"), - Tag(id = 7371, name = "Ssbbm"), - Tag(id = 7093, name = "Ssbbw"), - Tag(id = 23302, name = "Stagger"), - Tag(id = 20756, name = "Staggio Ton-pierohbee"), - Tag(id = 16997, name = "Steevejo"), - Tag(id = 22316, name = "Steward"), - Tag(id = 884, name = "Stewardess"), - Tag(id = 57, name = "Stockings"), - Tag(id = 225, name = "Stomach Deformation"), - Tag(id = 17126, name = "Story Act"), - Tag(id = 15864, name = "Story Arc"), - Tag(id = 139, name = "Strap-on"), - Tag(id = 6576, name = "Stretching"), - Tag(id = 3873, name = "Stuck In Wall"), - Tag(id = 17106, name = "Studio Campus"), - Tag(id = 21909, name = "Studio Crimson"), - Tag(id = 17045, name = "Studio Mxh"), - Tag(id = 20431, name = "Studio Onion"), - Tag(id = 23602, name = "Suckaline"), - Tag(id = 17003, name = "Suda Yuriko"), - Tag(id = 16952, name = "Sui Kayama"), - Tag(id = 16811, name = "Sui Ren"), - Tag(id = 23732, name = "Suidoku"), - Tag(id = 16452, name = "Suina Ruu"), - Tag(id = 17184, name = "Suiranao"), - Tag(id = 17069, name = "Suiseimushi"), - Tag(id = 22021, name = "Sukurinton"), - Tag(id = 140, name = "Sumata"), - Tag(id = 141, name = "Sundress"), - Tag(id = 166, name = "Sunglasses"), - Tag(id = 25589, name = "Super Smash Bros"), - Tag(id = 15904, name = "Supertoasted"), - Tag(id = 16899, name = "Susu Suzumi"), - Tag(id = 16016, name = "Sutazubu-saku"), - Tag(id = 25195, name = "Suzuki Senpai"), - Tag(id = 22531, name = "Suzuki Tenpura"), - Tag(id = 16491, name = "Suzuku Kururugi"), - Tag(id = 16663, name = "Suzumi Yuu"), - Tag(id = 17273, name = "Suzunari"), - Tag(id = 23674, name = "Suzune Arizono"), - Tag(id = 696, name = "Sweating"), - Tag(id = 69, name = "Swimsuit"), - Tag(id = 635, name = "Swinging"), - Tag(id = 1955, name = "Syringe"), - Tag(id = 17011, name = "System Speculation"), - Tag(id = 754, name = "Table Masturbation"), - Tag(id = 19556, name = "Tachinami Takajin"), - Tag(id = 16816, name = "Tadai Yuu"), - Tag(id = 16664, name = "Tae Anezaki"), - Tag(id = 24925, name = "Tae Yamada"), - Tag(id = 17908, name = "Taeko Nonomiya"), - Tag(id = 25425, name = "Tail"), - Tag(id = 1156, name = "Tail Plug"), - Tag(id = 16907, name = "Tail-gun"), - Tag(id = 2861, name = "Tailjob"), - Tag(id = 16464, name = "Taimanin"), - Tag(id = 16465, name = "Taimanin Haiboku"), - Tag(id = 17797, name = "Taiyoushin"), - Tag(id = 20077, name = "Takakujyu"), - Tag(id = 16281, name = "Takakura Row"), - Tag(id = 16742, name = "Takamura Chinatsu"), - Tag(id = 24757, name = "Takatou Kei"), - Tag(id = 23832, name = "Takatsu Rin"), - Tag(id = 18111, name = "Takimoto Yukari"), - Tag(id = 16480, name = "Takoyaki Yoshi"), - Tag(id = 922, name = "Tall Girl"), - Tag(id = 120, name = "Tall Man"), - Tag(id = 16626, name = "Tama Shippo"), - Tag(id = 24669, name = "Tamaki Mari"), - Tag(id = 20900, name = "Tami Nishimikado"), - Tag(id = 23735, name = "Tanken Wa Ra 2"), - Tag(id = 15870, name = "Tankoubon"), - Tag(id = 303, name = "Tanlines"), - Tag(id = 17151, name = "Tanukigirl"), - Tag(id = 22214, name = "Tanukine"), - Tag(id = 16794, name = "Tanuma"), - Tag(id = 16698, name = "Tapir"), - Tag(id = 22323, name = "Tasu"), - Tag(id = 17057, name = "Tat"), - Tag(id = 16821, name = "Tateno Makoto"), - Tag(id = 20524, name = "Tatsuhide"), - Tag(id = 23415, name = "Tayuri"), - Tag(id = 112, name = "Teacher"), - Tag(id = 23459, name = "Tecoya"), - Tag(id = 16158, name = "Tee Crown"), - Tag(id = 25562, name = "Teebsly"), - Tag(id = 16487, name = "Tekorun"), - Tag(id = 22899, name = "Tenjou Shio"), - Tag(id = 196, name = "Tentacles"), - Tag(id = 24136, name = "Tep"), - Tag(id = 16180, name = "Teru-bee"), - Tag(id = 18721, name = "Tetsu Hagane"), - Tag(id = 12900, name = "Thick Eyebrows"), - Tag(id = 769, name = "Thigh High Boots"), - Tag(id = 641, name = "Tiara"), - Tag(id = 2728, name = "Tickling"), - Tag(id = 12085, name = "Tiger"), - Tag(id = 960, name = "Tights"), - Tag(id = 23600, name = "Tigrevurmud Vorn"), - Tag(id = 23173, name = "Tilia"), - Tag(id = 15913, name = "Time Stop"), - Tag(id = 17717, name = "Tobaroku"), - Tag(id = 20550, name = "Tocori"), - Tag(id = 495, name = "Toddlercon"), - Tag(id = 16287, name = "Tohno Hongshi"), - Tag(id = 16785, name = "Tokage 3gou"), - Tag(id = 21710, name = "Tokizawa"), - Tag(id = 20551, name = "Tokori"), - Tag(id = 24137, name = "Tokyo Survivor"), - Tag(id = 16928, name = "Tokyo-ya"), - Tag(id = 19807, name = "Tom Jpn"), - Tag(id = 16595, name = "Tomadoiki"), - Tag(id = 13, name = "Tomboy"), - Tag(id = 129, name = "Tomgirl"), - Tag(id = 25091, name = "Tomobukiya"), - Tag(id = 16226, name = "Tomoko Ogura"), - Tag(id = 15917, name = "Tonboya"), - Tag(id = 16537, name = "Tongue Job"), - Tag(id = 23105, name = "Tooku No Mura"), - Tag(id = 1441, name = "Tooth Brushing"), - Tag(id = 15888, name = "Tooyama Masuko"), - Tag(id = 25256, name = "Toriaezu."), - Tag(id = 752, name = "Torture"), - Tag(id = 17328, name = "Toshinari Arito"), - Tag(id = 25478, name = "Toshkarts"), - Tag(id = 23840, name = "Totomono"), - Tag(id = 22879, name = "Touma Nigou"), - Tag(id = 16946, name = "Toyotomi Hideyoshi"), - Tag(id = 113, name = "Tracksuit"), - Tag(id = 7331, name = "Trampling"), - Tag(id = 195, name = "Transformation"), - Tag(id = 233, name = "Tribadism"), - Tag(id = 5540, name = "Triple Anal"), - Tag(id = 226, name = "Triple Penetration"), - Tag(id = 1526, name = "Triple Vaginal"), - Tag(id = 16765, name = "Try Hougen"), - Tag(id = 23845, name = "Tsujiadon"), - Tag(id = 16106, name = "Tsukasa Kotobuki"), - Tag(id = 16786, name = "Tsukigara Rosshi"), - Tag(id = 16560, name = "Tsukigata Rosshi"), - Tag(id = 23897, name = "Tsuru Kame"), - Tag(id = 15999, name = "Tsurumanjaro"), - Tag(id = 15908, name = "Tsuta Hiroko"), - Tag(id = 18801, name = "Tsuzuki Shiori"), - Tag(id = 792, name = "Ttf Threesome"), - Tag(id = 15875, name = "Ttm Threesome"), - Tag(id = 244, name = "Tube"), - Tag(id = 22694, name = "Turnover"), - Tag(id = 10994, name = "Turtle"), - Tag(id = 780, name = "Tutor"), - Tag(id = 17021, name = "Twin Bell"), - Tag(id = 142, name = "Twins"), - Tag(id = 115, name = "Twintails"), - Tag(id = 23174, name = "Tyria"), - Tag(id = 25109, name = "Tzinnxt"), - Tag(id = 16561, name = "Uekio Aloe"), - Tag(id = 15884, name = "Uesato Takeharu"), - Tag(id = 23739, name = "Ujiro"), - Tag(id = 22521, name = "Umaisake"), - Tag(id = 20106, name = "Ume Kurumizawa"), - Tag(id = 18201, name = "Umetsu Yasuomi"), - Tag(id = 16776, name = "Una777"), - Tag(id = 712, name = "Unbirth"), - Tag(id = 15862, name = "Uncensored"), - Tag(id = 7151, name = "Uncle"), - Tag(id = 286, name = "Underwater"), - Tag(id = 21894, name = "Uni Unio"), - Tag(id = 19786, name = "Unihoge"), - Tag(id = 16292, name = "Unomaa"), - Tag(id = 1517, name = "Unusual Pupils"), - Tag(id = 1565, name = "Unusual Teeth"), - Tag(id = 16432, name = "Urabata"), - Tag(id = 16184, name = "Uragiru Kuchibiru"), - Tag(id = 18434, name = "Urako"), - Tag(id = 227, name = "Urethra Insertion"), - Tag(id = 143, name = "Urination"), - Tag(id = 16624, name = "Uru Fusube In"), - Tag(id = 16945, name = "Usagiro"), - Tag(id = 20522, name = "Ustilago Nuda"), - Tag(id = 17161, name = "Usuk"), - Tag(id = 18665, name = "Utsu Kawaya"), - Tag(id = 6045, name = "Vacbed"), - Tag(id = 2800, name = "Vaginal Sticker"), - Tag(id = 58, name = "Vampire"), - Tag(id = 16948, name = "Variant Set"), - Tag(id = 23976, name = "Virginia Nitouhei"), - Tag(id = 213, name = "Virginity"), - Tag(id = 17046, name = "Viriya"), - Tag(id = 17458, name = "Visual She"), - Tag(id = 228, name = "Vomit"), - Tag(id = 238, name = "Vore"), - Tag(id = 124, name = "Voyeurism"), - Tag(id = 16192, name = "Wada Tomohiro"), - Tag(id = 20686, name = "Waio"), - Tag(id = 2084, name = "Waiter"), - Tag(id = 374, name = "Waitress"), - Tag(id = 15879, name = "Walkure"), - Tag(id = 19824, name = "Wanya Aguda"), - Tag(id = 16645, name = "Warikka"), - Tag(id = 16191, name = "Watanabe Enchou"), - Tag(id = 16212, name = "Watari Naomi"), - Tag(id = 16182, name = "Watermarked"), - Tag(id = 16007, name = "Webtoon"), - Tag(id = 16507, name = "Wedding Ring"), - Tag(id = 1756, name = "Weight Gain"), - Tag(id = 16197, name = "Welkin Gunther"), - Tag(id = 4703, name = "Wet Clothes"), - Tag(id = 3719, name = "Whip"), - Tag(id = 264, name = "Widow"), - Tag(id = 17058, name = "Will Powers"), - Tag(id = 16994, name = "Wingjob"), - Tag(id = 404, name = "Wings"), - Tag(id = 926, name = "Witch"), - Tag(id = 1560, name = "Wolf"), - Tag(id = 2066, name = "Wolf Boy"), - Tag(id = 1551, name = "Wolf Girl"), - Tag(id = 2868, name = "Wooden Horse"), - Tag(id = 828, name = "Worm"), - Tag(id = 332, name = "Wormhole"), - Tag(id = 17066, name = "Wrestle Angels Survivor"), - Tag(id = 1990, name = "Wrestling"), - Tag(id = 17042, name = "X-boy"), - Tag(id = 144, name = "X-ray"), - Tag(id = 20575, name = "Xera"), - Tag(id = 16249, name = "Yada Masaka"), - Tag(id = 16883, name = "Yagami"), - Tag(id = 17084, name = "Yagami Baneri"), - Tag(id = 16967, name = "Yahiko"), - Tag(id = 24447, name = "Yamada Botan"), - Tag(id = 18806, name = "Yamada Kana"), - Tag(id = 17470, name = "Yamada Tasaku"), - Tag(id = 16275, name = "Yamamomo Kajitsu"), - Tag(id = 16511, name = "Yamamoto Hideo"), - Tag(id = 18257, name = "Yamane Akira"), - Tag(id = 16006, name = "Yamane Amano"), - Tag(id = 16877, name = "Yami No Matsuri"), - Tag(id = 968, name = "Yandere"), - Tag(id = 283, name = "Yaoi"), - Tag(id = 23158, name = "Yarou Tomo No Bansankai"), - Tag(id = 16015, name = "Yasou Shigeru"), - Tag(id = 16508, name = "Yasu G"), - Tag(id = 22667, name = "Yasuhara Osamu"), - Tag(id = 23460, name = "Yatsugi Teco"), - Tag(id = 17040, name = "Yokohama-ya"), - Tag(id = 16965, name = "Yokotaya"), - Tag(id = 16273, name = "Yoshiki Aya"), - Tag(id = 17459, name = "Yoshizuki Minoru"), - Tag(id = 16817, name = "Youna"), - Tag(id = 18367, name = "Yousei Kanin"), - Tag(id = 22257, name = "Yshtola"), - Tag(id = 20770, name = "Yu Tendo"), - Tag(id = 16862, name = "Yubel"), - Tag(id = 15997, name = "Yui Narumi"), - Tag(id = 17115, name = "Yukai Sachiko"), - Tag(id = 17850, name = "Yukata Kobayakawa"), - Tag(id = 16117, name = "Yuki Hiiragi"), - Tag(id = 21680, name = "Yukiruru"), - Tag(id = 19205, name = "Yume No Omutsu Kissa"), - Tag(id = 16916, name = "Yumego"), - Tag(id = 16454, name = "Yumemigotoki"), - Tag(id = 17542, name = "Yumesaki Ai"), - Tag(id = 24592, name = "Yumeutsutsu Hideki"), - Tag(id = 15900, name = "Yumiko"), - Tag(id = 16420, name = "Yunisuke"), - Tag(id = 14, name = "Yuri"), - Tag(id = 24046, name = "Yuriyura"), - Tag(id = 17798, name = "Yuugaitosho"), - Tag(id = 16130, name = "Yuugi Yami"), - Tag(id = 20140, name = "Yuzurizaki Nero"), - Tag(id = 20767, name = "Zeitaku Zanmai"), - Tag(id = 24430, name = "Ziggurat"), - Tag(id = 21354, name = "Zinkurou"), - Tag(id = 290, name = "Zombie") - ).sortedBy { it.name } -} diff --git a/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/NineHentai.kt b/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/NineHentai.kt deleted file mode 100644 index 050f9ffc2..000000000 --- a/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/NineHentai.kt +++ /dev/null @@ -1,221 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ninehentai - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import rx.Observable -import java.util.Date - -@Nsfw -class NineHentai : HttpSource() { - - override val baseUrl = "https://9hentai.ru" - - override val name = "NineHentai" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - override fun popularMangaRequest(page: Int): Request { - return POST(baseUrl + SEARCH_URL, headers, buildRequestBody(page = page, sort = 1)) - } - - override fun latestUpdatesRequest(page: Int): Request { - return POST(baseUrl + SEARCH_URL, headers, buildRequestBody(page = page)) - } - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - getMangaList(response, page) - } - } - - override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { - return client.newCall(latestUpdatesRequest(page)) - .asObservableSuccess() - .map { response -> - getMangaList(response, page) - } - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - getMangaList(response, page) - } - } - - private fun getMangaList(response: Response, page: Int): MangasPage { - val jsonData = response.body()!!.string() - val jsonObject = JsonParser().parse(jsonData).asJsonObject - val totalPages = jsonObject["total_count"].int - val results = jsonObject["results"].array - return MangasPage(parseSearch(results.toList()), page < totalPages) - } - - private fun parseSearch(jsonArray: List<JsonElement>): List<SManga> { - val mutableList = mutableListOf<SManga>() - jsonArray.forEach { json -> - val manga = SManga.create() - val gsonManga = gson.fromJson<Manga>(json) - manga.url = "/g/${gsonManga.id}" - manga.title = gsonManga.title - manga.thumbnail_url = gsonManga.image_server + gsonManga.id + "/cover.jpg" - manga.genre = gsonManga.tags.filter { it.type == 1 }.joinToString { it.name } - manga.artist = gsonManga.tags.firstOrNull { it.type == 4 }?.name - manga.initialized = true - mutableList.add(manga) - } - return mutableList - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - val chapter = SChapter.create() - val chapterId = manga.url.substringAfter("/g/").toInt() - chapter.url = "/g/$chapterId" - chapter.name = "chapter" - // api doesnt return date so setting to current date for now - chapter.date_upload = Date().time - - return Observable.just(listOf(chapter)) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val includedTags = mutableListOf<Tag>() - val excludedTags = mutableListOf<Tag>() - var sort = 0 - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is GenreList -> { - filter.state.forEach { f -> - if (!f.isIgnored()) { - if (f.isExcluded()) - excludedTags.add(f) - else - includedTags.add(f) - } - } - } - is Sorting -> { - sort = filter.state!!.index - } - } - } - return POST(baseUrl + SEARCH_URL, headers, buildRequestBody(query, page, sort, includedTags, excludedTags)) - } - - override fun mangaDetailsParse(response: Response): SManga { - return SManga.create().apply { - response.asJsoup().select("div.card-body").firstOrNull()?.let { info -> - title = info.select("h1").text() - genre = info.select("div.field-name:contains(Tag:) a.tag").joinToString { it.text() } - artist = info.select("div.field-name:contains(Artist:) a.tag").joinToString { it.text() } - thumbnail_url = info.select("div#cover v-lazy-image").attr("abs:src") - } - } - } - - override fun pageListRequest(chapter: SChapter): Request { - val mangaId = chapter.url.substringAfter("/g/").toInt() - return POST(baseUrl + MANGA_URL, headers, buildIdBody(mangaId)) - } - - override fun pageListParse(response: Response): List<Page> { - val jsonData = response.body()!!.string() - val jsonObject = JsonParser().parse(jsonData).asJsonObject - val jsonArray = jsonObject.getAsJsonObject("results") - var imageUrl: String - var totalPages: Int - var mangaId: String - jsonArray.let { json -> - mangaId = json["id"].string - imageUrl = json["image_server"].string + mangaId - totalPages = json["total_page"].int - } - val pages = mutableListOf<Page>() - - client.newCall( - GET( - "$imageUrl/preview/${totalPages}t.jpg", - headersBuilder().build() - ) - ).execute().code().let { code -> - if (code == 404) totalPages-- - } - - for (i in 1..totalPages) { - pages.add(Page(pages.size, "", "$imageUrl/$i.jpg")) - } - - return pages - } - - private fun buildRequestBody(searchText: String = "", page: Int, sort: Int = 0, includedTags: MutableList<Tag> = arrayListOf(), excludedTags: MutableList<Tag> = arrayListOf()): RequestBody { - val json = gson.toJson(mapOf("search" to SearchRequest(text = searchText, page = page - 1, sort = sort, tag = mapOf("items" to Items(includedTags, excludedTags))))) - return RequestBody.create(MEDIA_TYPE, json) - } - - private fun buildIdBody(id: Int): RequestBody { - return RequestBody.create(MEDIA_TYPE, gson.toJson(mapOf("id" to id))) - } - - private class GenreList(tags: List<Tag>) : Filter.Group<Tag>("Tags", tags) - - private class Sorting : Filter.Sort( - "Sorting", - arrayOf("Newest", "Popular Right now", "Most Fapped", "Most Viewed", "By Title"), - Selection(1, false) - ) - - override fun getFilterList() = FilterList( - Sorting(), - GenreList(NHTags.getTagsList()) - ) - - override fun imageUrlParse(response: Response): String = throw Exception("Not Used") - - override fun chapterListRequest(manga: SManga): Request = throw Exception("Not Used") - - override fun popularMangaParse(response: Response): MangasPage = throw Exception("Not Used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not Used") - - override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not Used") - - override fun chapterListParse(response: Response): List<SChapter> = throw Exception("Not Used") - - companion object { - private val MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8") - private const val SEARCH_URL = "/api/getBook" - private const val MANGA_URL = "/api/getBookByID" - } -} diff --git a/src/all/ninemanga/AndroidManifest.xml b/src/all/ninemanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/ninemanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/ninemanga/build.gradle b/src/all/ninemanga/build.gradle deleted file mode 100644 index b0b22d6cb..000000000 --- a/src/all/ninemanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NineManga' - pkgNameSuffix = "all.ninemanga" - extClass = '.NineMangaFactory' - extVersionCode = 13 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/ninemanga/res/mipmap-hdpi/ic_launcher.png b/src/all/ninemanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3fb0ce05f..000000000 Binary files a/src/all/ninemanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninemanga/res/mipmap-mdpi/ic_launcher.png b/src/all/ninemanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c42ac3268..000000000 Binary files a/src/all/ninemanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninemanga/res/mipmap-xhdpi/ic_launcher.png b/src/all/ninemanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f19db082d..000000000 Binary files a/src/all/ninemanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninemanga/res/mipmap-xxhdpi/ic_launcher.png b/src/all/ninemanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a50654086..000000000 Binary files a/src/all/ninemanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninemanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/ninemanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 538fcdeda..000000000 Binary files a/src/all/ninemanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/ninemanga/res/web_hi_res_512.png b/src/all/ninemanga/res/web_hi_res_512.png deleted file mode 100644 index 98b30f2da..000000000 Binary files a/src/all/ninemanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/ninemanga/src/eu/kanade/tachiyomi/extension/all/ninemanga/NineManga.kt b/src/all/ninemanga/src/eu/kanade/tachiyomi/extension/all/ninemanga/NineManga.kt deleted file mode 100644 index 98ac0203f..000000000 --- a/src/all/ninemanga/src/eu/kanade/tachiyomi/extension/all/ninemanga/NineManga.kt +++ /dev/null @@ -1,265 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ninemanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -open class NineManga(override val name: String, override val baseUrl: String, override val lang: String) : ParsedHttpSource() { - - override val supportsLatest: Boolean = true - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept-Language", "es-ES,es;q=0.9,en;q=0.8,gl;q=0.7") - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) Gecko/20100101 Firefox/75") - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/New-Update/", headers) // "$baseUrl/category/updated_$page.html" - - override fun latestUpdatesSelector() = "dl.bookinfo" - - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - element.select("a.bookname").let { - url = it.attr("href").substringAfter(baseUrl) - title = it.text() - } - thumbnail_url = element.select("img").attr("abs:src") - } - - override fun latestUpdatesNextPageSelector() = "ul.pageList > li:last-child > a.l" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/index_$page.html", headers) - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - document.select("div.bookintro").let { - title = it.select("li > span:not([class])").text().removeSuffix(" Manga") - genre = it.select("li[itemprop=genre] a").joinToString { e -> e.text() } - author = it.select("li a[itemprop=author]").text() - status = parseStatus(it.select("li a.red").first().text()) - description = it.select("p[itemprop=description]").text() - thumbnail_url = it.select("img[itemprop=image]").attr("abs:src") - } - } - - open fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + manga.url + "?waring=1", headers) // Bypasses adult content warning - } - - override fun chapterListSelector() = "ul.sub_vol_ul > li" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - element.select("a.chapter_list_a").let { - name = it.text() - url = it.attr("href").substringAfter(baseUrl).replace("%20", " ") - } - date_upload = parseChapterDate(element.select("span").text()) - } - - open fun parseChapterDate(date: String): Long { - val dateWords = date.split(" ") - - if (dateWords.size == 3) { - if (dateWords[1].contains(",")) { - return try { - SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } else { - val timeAgo = Integer.parseInt(dateWords[0]) - return Calendar.getInstance().apply { - when (dateWords[1]) { - "minutes" -> Calendar.MINUTE - "hours" -> Calendar.HOUR - else -> null - }?.let { - add(it, -timeAgo) - } - }.timeInMillis - } - } - return 0L - } - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("select#page").first().select("option").forEach { - add(Page(size, baseUrl + it.attr("value"))) - } - } - - override fun imageUrlParse(document: Document) = document.select("div.pic_box img.manga_pic").first().attr("src").orEmpty() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search/")!!.newBuilder() - - url.addQueryParameter("wd", query) - url.addQueryParameter("page", page.toString()) - - filters.forEach { filter -> - when (filter) { - is QueryCBEFilter -> url.addQueryParameter("name_sel", filter.toUriPart()) - is AuthorCBEFilter -> url.addQueryParameter("author_sel", filter.toUriPart()) - is AuthorFilter -> url.addQueryParameter("author", filter.state) - is ArtistCBEFilter -> url.addQueryParameter("artist_sel", filter.toUriPart()) - is ArtistFilter -> url.addQueryParameter("artist", filter.state) - is GenreList -> { - var genreInclude = "" - var genreExclude = "" - filter.state.forEach { - if (it.isIncluded()) genreInclude += "${it.id}," - if (it.isExcluded()) genreExclude += "${it.id}," - } - url.addQueryParameter("category_id", genreInclude) - url.addQueryParameter("out_category_id", genreExclude) - } - is CompletedFilter -> url.addQueryParameter("completed_series", filter.toUriPart()) - } - } - - url.addQueryParameter("type", "high") - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - open class Genre(name: String, val id: String) : Filter.TriState(name) - open class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genre", genres) - - class AuthorFilter : Filter.Text("Author") - class ArtistFilter : Filter.Text("Artist") - - protected open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - protected open class ContainBeginEndFilter(name: String) : UriPartFilter( - name, - arrayOf( - Pair("Contain", "contain"), - Pair("Begin", "begin"), - Pair("End", "end") - ) - ) - - private class QueryCBEFilter : ContainBeginEndFilter("Query") - private class AuthorCBEFilter : ContainBeginEndFilter("Author") - private class ArtistCBEFilter : ContainBeginEndFilter("Artist") - - private class CompletedFilter : UriPartFilter( - "Completed", - arrayOf( - Pair("Either", "either"), - Pair("Yes", "yes"), - Pair("No", "no") - ) - ) - - override fun getFilterList() = FilterList( - QueryCBEFilter(), - AuthorCBEFilter(), - AuthorFilter(), - ArtistCBEFilter(), - ArtistFilter(), - GenreList(getGenreList()), - CompletedFilter() - ) - - // $(document.querySelectorAll('.optionbox .typelist:nth-child(3) ul li.cate_list')).map((i, el)=>`Genre("${$(el).first().text().trim()}", "${$(el).attr("cate_id")}")`).get().sort().join(",\n") - // https://en.ninemanga.com/search/?type=high - open fun getGenreList() = listOf( - Genre("4-Koma", "56"), - Genre("Action", "1"), - Genre("Adventure", "2"), - Genre("Anime", "3"), - Genre("Award Winning", "59"), - Genre("Bara", "84"), - Genre("Comedy", "4"), - Genre("Cooking", "5"), - Genre("Crime", "132"), - Genre("Demons", "49"), - Genre("Doujinshi", "45"), - Genre("Drama", "6"), - Genre("Fantasy", "8"), - Genre("Game", "126"), - Genre("Gender Bender", "9"), - Genre("Historical", "11"), - Genre("Horror", "12"), - Genre("Isekai", "127"), - Genre("Josei", "13"), - Genre("Live Action", "14"), - Genre("Magic", "47"), - Genre("Magical Girls", "130"), - Genre("Manhua", "15"), - Genre("Manhwa", "16"), - Genre("Martial Arts", "17"), - Genre("Matsumoto Tomokicomedy", "37"), - Genre("Mecha", "18"), - Genre("Medical", "19"), - Genre("Military", "51"), - Genre("Music", "20"), - Genre("Mystery", "21"), - Genre("N/A", "54"), - Genre("None", "64"), - Genre("One Shot", "22"), - Genre("Oneshot", "57"), - Genre("Philosophical", "133"), - Genre("Psychological", "23"), - Genre("Reverse Harem", "55"), - Genre("Romance Shoujo", "38"), - Genre("Romance", "24"), - Genre("School Life", "25"), - Genre("Sci-Fi", "26"), - Genre("Seinen", "27"), - Genre("Shoujo Ai", "44"), - Genre("Shoujo", "28"), - Genre("Shoujo-Ai", "29"), - Genre("Shoujoai", "48"), - Genre("Shounen Ai", "42"), - Genre("Shounen", "30"), - Genre("Shounen-Ai", "31"), - Genre("Shounenai", "46"), - Genre("Slice Of Life", "32"), - Genre("Sports", "33"), - Genre("Staff Pick", "60"), - Genre("Super Power", "62"), - Genre("Superhero", "131"), - Genre("Supernatural", "34"), - Genre("Suspense", "53"), - Genre("Thriller", "129"), - Genre("Tragedy", "35"), - Genre("Vampire", "52"), - Genre("Webtoon", "58"), - Genre("Webtoons", "50"), - Genre("Wuxia", "128"), - Genre("[No Chapters]", "61") - ) -} diff --git a/src/all/ninemanga/src/eu/kanade/tachiyomi/extension/all/ninemanga/NineMangaFactory.kt b/src/all/ninemanga/src/eu/kanade/tachiyomi/extension/all/ninemanga/NineMangaFactory.kt deleted file mode 100644 index 522b762f5..000000000 --- a/src/all/ninemanga/src/eu/kanade/tachiyomi/extension/all/ninemanga/NineMangaFactory.kt +++ /dev/null @@ -1,791 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.ninemanga - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Request -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class NineMangaFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - NineMangaEn(), - NineMangaEs(), - NineMangaBr(), - NineMangaRu(), - NineMangaDe(), - NineMangaIt(), - NineMangaFr() - ) -} - -class NineMangaEn : NineManga("NineMangaEn", "https://en.ninemanga.com", "en") { - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - element.select("a.bookname").let { - url = it.attr("abs:href").replace("www", "en").substringAfter(baseUrl) - title = it.text() - } - thumbnail_url = element.select("img").attr("abs:src") - } -} - -class NineMangaEs : NineManga("NineMangaEs", "https://es.ninemanga.com", "es") { - // ES, FR, RU don't return results for searches with an apostrophe - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return super.searchMangaRequest(page, query.substringBefore("\'"), filters) - } - - override fun parseStatus(status: String) = when { - status.contains("En curso") -> SManga.ONGOING - status.contains("Completado") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun parseChapterDate(date: String) = parseChapterDateByLang(date) - - // https://es.ninemanga.com/search/?type=high - override fun getGenreList() = listOf( - Genre("4-Koma", "201"), - Genre("AcciÓN", "213"), - Genre("AccióN", "69"), - Genre("Action", "177"), - Genre("Adventure", "179"), - Genre("AnimacióN", "229"), - Genre("ApocalíPtico", "202"), - Genre("Artes Marciales", "66"), - Genre("Aventura", "64"), - Genre("Aventuras", "120"), - Genre("Boys Love", "228"), - Genre("Ciberpunk", "225"), - Genre("Ciencia FiccióN", "93"), - Genre("Comedia", "75"), - Genre("Comedy", "178"), - Genre("Cotidiano", "110"), - Genre("Crime", "245"), - Genre("Crimen", "227"), - Genre("Cyberpunk", "199"), - Genre("Delincuentes", "125"), - Genre("Demonios", "126"), - Genre("Deporte", "76"), - Genre("Deportes", "111"), - Genre("Doujinshi", "216"), - Genre("Drama", "79"), - Genre("Escolar", "81"), - Genre("Extranjero", "238"), - Genre("Familia", "237"), - Genre("Fantacia", "100"), - Genre("Fantasy", "180"), - Genre("FantasÍA", "214"), - Genre("FantasíA", "70"), - Genre("GL (Girls Love)", "222"), - Genre("Gender Bender", "175"), - Genre("Girls Love", "226"), - Genre("Gore", "108"), - Genre("Guerra", "234"), - Genre("GéNero Bender", "230"), - Genre("HaréN", "82"), - Genre("Hentai", "83"), - Genre("Historia", "233"), - Genre("Historical", "190"), - Genre("HistóRico", "95"), - Genre("Horror", "99"), - Genre("Isekai", "240"), - Genre("Josei", "112"), - Genre("Karate", "113"), - Genre("Maduro", "72"), - Genre("Mafia", "90"), - Genre("Magia", "172"), - Genre("Makoto", "102"), - Genre("Mangasutra", "103"), - Genre("Manhwa", "94"), - Genre("Manwha", "114"), - Genre("Martial Arts", "181"), - Genre("Martial", "189"), - Genre("Mecha", "115"), - Genre("Militar", "205"), - Genre("Misterio", "88"), - Genre("Music", "241"), - Genre("Musical", "197"), - Genre("Mystery", "187"), - Genre("MúSica", "121"), - Genre("NiñOs", "235"), - Genre("None", "71"), - Genre("Oeste", "239"), - Genre("One Shot", "184"), - Genre("One-Shot", "221"), - Genre("Oneshot", "195"), - Genre("OrgíA", "91"), - Genre("Parodia", "198"), - Genre("Policiaco", "236"), - Genre("Policial", "208"), - Genre("PolicíAca", "220"), - Genre("Porno", "109"), - Genre("PsicolóGica", "219"), - Genre("PsicolóGico", "96"), - Genre("Psychological", "192"), - Genre("Realidad Virtual", "196"), - Genre("Realidad", "231"), - Genre("Recuentos De La Vida", "169"), - Genre("ReencarnacióN", "207"), - Genre("Romance", "67"), - Genre("RomáNtica", "98"), - Genre("RomáNtico", "89"), - Genre("Samurai", "210"), - Genre("School Life", "176"), - Genre("Sci-Fi", "123"), - Genre("Seinen", "73"), - Genre("Shojo Ai", "186"), - Genre("Shojo", "80"), - Genre("Shojo-Ai (Yuri Soft)", "218"), - Genre("Shonen Ai", "128"), - Genre("Shonen", "77"), - Genre("Shonen-Ai (Yaoi Soft)", "217"), - Genre("Shonen-Ai", "174"), - Genre("Shota", "224"), - Genre("Shoujo Ai", "194"), - Genre("Shoujo", "85"), - Genre("Shoujo-Ai", "173"), - Genre("Shounen Ai", "185"), - Genre("Shounen", "68"), - Genre("Shounen-Ai", "118"), - Genre("Slice Of Life", "182"), - Genre("Sobrenatural", "74"), - Genre("Sports", "188"), - Genre("Super Natural", "124"), - Genre("Super Poderes", "206"), - Genre("Superhero", "246"), - Genre("Superheroes", "116"), - Genre("Supernatural", "119"), - Genre("Superpoderes", "215"), - Genre("Supervivencia", "203"), - Genre("Suspense", "171"), - Genre("Telenovela", "242"), - Genre("Terror PsicolóGico", "107"), - Genre("Terror", "106"), - Genre("Thiller", "204"), - Genre("Thriller", "97"), - Genre("Tragedia", "87"), - Genre("Tragedy", "191"), - Genre("Vampiros", "209"), - Genre("Ver En Lectormanga", "243"), - Genre("Vida Cotidiana", "84"), - Genre("Vida Escolar", "170"), - Genre("Vida Escolar.", "122"), - Genre("Webcomic", "92"), - Genre("Webtoon", "200"), - Genre("Wuxia", "244"), - Genre("Yonkoma", "232") - ) -} - -class NineMangaBr : NineManga("NineMangaBr", "https://br.ninemanga.com", "pt") { - override fun parseStatus(status: String) = when { - status.contains("Em tradução") -> SManga.ONGOING - status.contains("Completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun parseChapterDate(date: String) = parseChapterDateByLang(date) - - // https://br.ninemanga.com/search/?type=high - override fun getGenreList() = listOf( - Genre("4koma", "107"), - Genre("Adulto (18+)", "123"), - Genre("Adulto (YAOI)", "122"), - Genre("Artes Marciais", "83"), - Genre("Aventura", "72"), - Genre("AçãO", "71"), - Genre("Bara", "126"), - Genre("Carros", "118"), - Genre("Colegial", "63"), - Genre("ComéDia", "64"), - Genre("Criancas", "114"), - Genre("Culinaria", "116"), - Genre("Dementia", "119"), - Genre("Demonios", "109"), - Genre("Doujinshi", "124"), - Genre("Drama", "74"), - Genre("Escolar", "103"), - Genre("Espaco", "117"), - Genre("Esporte", "87"), - Genre("Esportes", "106"), - Genre("Fantasia", "65"), - Genre("FicçãO", "99"), - Genre("Gender Bender", "73"), - Genre("HistóRico", "77"), - Genre("Horror", "80"), - Genre("Isekai", "121"), - Genre("Jogo", "102"), - Genre("Josei", "89"), - Genre("Maduro", "105"), - Genre("Magia", "96"), - Genre("Manhua", "125"), - Genre("Manhwa", "129"), - Genre("Mecha", "94"), - Genre("Medicina", "131"), - Genre("Militar", "110"), - Genre("MistéRio", "78"), - Genre("Musical", "92"), - Genre("Nonsense", "120"), - Genre("Novel", "130"), - Genre("OneShot", "69"), - Genre("Parodia", "108"), - Genre("Policial", "101"), - Genre("PsicolóGico", "79"), - Genre("Romance", "66"), - Genre("Samurai", "111"), - Genre("Sci-Fi", "67"), - Genre("Seinen", "82"), - Genre("Shoujo Ai", "100"), - Genre("Shoujo", "70"), - Genre("Shoujo-Ai", "86"), - Genre("Shounen Ai", "95"), - Genre("Shounen", "68"), - Genre("Slice Of Life", "75"), - Genre("Sobrenatural", "76"), - Genre("Super Poderes", "113"), - Genre("Suspense", "127"), - Genre("Terror", "91"), - Genre("Teste 1", "97"), - Genre("Thriller", "115"), - Genre("TragéDia", "81"), - Genre("Vampiros", "112"), - Genre("Webtoon", "128"), - Genre("Xuanhuan", "104"), - Genre("Yaoi (Omegaverse)", "132") - ) -} - -class NineMangaRu : NineManga("NineMangaRu", "https://ru.ninemanga.com", "ru") { - // ES, FR, RU don't return results for searches with an apostrophe - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return super.searchMangaRequest(page, query.substringBefore("\'"), filters) - } - - override fun parseStatus(status: String) = when { - // No Ongoing status - status.contains("завершенный") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun parseChapterDate(date: String) = parseChapterDateByLang(date) - - // https://ru.ninemanga.com/search/?type=high - override fun getGenreList() = listOf( - Genre("Бдсм", "95"), - Genre("арт", "90"), - Genre("боевик", "53"), - Genre("боевые искусства", "58"), - Genre("вампиры", "85"), - Genre("гарем", "73"), - Genre("гендерная интрига", "81"), - Genre("героическое фэнтези", "68"), - Genre("детектив", "72"), - Genre("дзёсэй", "64"), - Genre("додзинси", "62"), - Genre("драма", "51"), - Genre("игра", "76"), - Genre("история", "75"), - Genre("киберпанк", "91"), - Genre("кодомо", "89"), - Genre("комедия", "57"), - Genre("махо-сёдзё", "88"), - Genre("меха", "84"), - Genre("мистика", "71"), - Genre("научная фантастика", "79"), - Genre("омегаверс", "94"), - Genre("повседневность", "65"), - Genre("постапокалиптика", "87"), - Genre("приключения", "59"), - Genre("психология", "54"), - Genre("романтика", "61"), - Genre("самурайский боевик", "82"), - Genre("сверхъестественное", "55"), - Genre("спорт", "69"), - Genre("сэйнэн", "74"), - Genre("сёдзё", "67"), - Genre("сёдзё-ай", "78"), - Genre("сёнэн", "52"), - Genre("сёнэн-ай", "63"), - Genre("трагедия", "70"), - Genre("триллер", "83"), - Genre("ужасы", "86"), - Genre("фантастика", "77"), - Genre("фэнтези", "56"), - Genre("школа", "66"), - Genre("эротика", "93"), - Genre("этти", "60"), - Genre("юри", "80"), - Genre("яой", "92") - ) -} - -class NineMangaDe : NineManga("NineMangaDe", "https://de.ninemanga.com", "de") { - override fun parseStatus(status: String) = when { - status.contains("Laufende") -> SManga.ONGOING - status.contains("Abgeschlossen") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun parseChapterDate(date: String) = parseChapterDateByLang(date) - - // https://de.ninemanga.com/search/?type=high - override fun getGenreList() = listOf( - Genre("4-Koma", "104"), - Genre("Abenteuer", "63"), - Genre("Action", "64"), - Genre("Alltagsdrama", "82"), - Genre("Boys Love", "106"), - Genre("Doujinshi", "97"), - Genre("Drama", "65"), - Genre("DäMonen", "76"), - Genre("Erotik", "88"), - Genre("Fantasy", "66"), - Genre("Geister", "108"), - Genre("Gender Bender", "91"), - Genre("Girls Love", "99"), - Genre("Historisch", "84"), - Genre("Horror", "72"), - Genre("Isekai", "109"), - Genre("Josei", "95"), - Genre("Kampfsport", "81"), - Genre("Kartenspiel", "78"), - Genre("Kinder", "101"), - Genre("Kochen", "107"), - Genre("KomöDie", "67"), - Genre("Krimi", "105"), - Genre("Magie", "68"), - Genre("Mecha", "89"), - Genre("MilitäR", "90"), - Genre("Monster", "100"), - Genre("Musik", "83"), - Genre("Mystery", "69"), - Genre("Psychodrama", "103"), - Genre("Romanze", "74"), - Genre("Schule", "70"), - Genre("Sci-Fi", "86"), - Genre("Seinen", "96"), - Genre("Shoujo", "85"), - Genre("Shounen", "75"), - Genre("Spiel", "92"), - Genre("Sport", "87"), - Genre("Super KräFte", "80"), - Genre("SuperkräFte", "102"), - Genre("Thriller", "94"), - Genre("Vampire", "71"), - Genre("Videospiel", "77") - ) -} - -class NineMangaIt : NineManga("NineMangaIt", "https://it.ninemanga.com", "it") { - override fun parseStatus(status: String) = when { - status.contains("In corso") -> SManga.ONGOING - status.contains("Completato") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun parseChapterDate(date: String) = parseChapterDateByLang(date) - - // https://it.ninemanga.com/search/?type=high - override fun getGenreList() = listOf( - Genre("Action", "98"), - Genre("Adventure", "108"), - Genre("Avventura", "63"), - Genre("Azione", "65"), - Genre("Bara", "88"), - Genre("Cartoon", "120"), - Genre("Comedy", "101"), - Genre("Commedia", "71"), - Genre("Demenziale", "79"), - Genre("Doujinshi", "114"), - Genre("Dounshinji", "92"), - Genre("Drama", "82"), - Genre("Fantasy", "74"), - Genre("Gender Bender", "109"), - Genre("Green", "119"), - Genre("Hentai", "90"), - Genre("Historical", "107"), - Genre("Horror", "80"), - Genre("Josei", "95"), - Genre("Magico", "91"), - Genre("Manga", "121"), - Genre("Martial Arts", "99"), - Genre("Maturo", "115"), - Genre("Mecha", "68"), - Genre("Misteri", "87"), - Genre("Musica", "96"), - Genre("Mystery", "105"), - Genre("Psicologico", "83"), - Genre("Psychological", "97"), - Genre("Raccolta", "93"), - Genre("Realistico", "118"), - Genre("Romance", "104"), - Genre("Romantico", "75"), - Genre("School Life", "103"), - Genre("Sci-Fi", "66"), - Genre("Scolastico", "64"), - Genre("Seinen", "67"), - Genre("Sentimentale", "72"), - Genre("Shota", "89"), - Genre("Shoujo", "73"), - Genre("Shounen", "69"), - Genre("Slice Of Life", "102"), - Genre("Sovrannaturale", "78"), - Genre("Splatter", "81"), - Genre("Sportivo", "85"), - Genre("Sports", "110"), - Genre("Storico", "84"), - Genre("Supereroistico", "117"), - Genre("Supernatural", "100"), - Genre("Tragedia", "116"), - Genre("Tragedy", "112"), - Genre("Vita Quotidiana", "77") - ) -} - -class NineMangaFr : NineManga("NineMangaFr", "https://fr.ninemanga.com", "fr") { - // ES, FR, RU don't return results for searches with an apostrophe - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return super.searchMangaRequest(page, query.substringBefore("\'"), filters) - } - - override fun parseStatus(status: String) = when { - status.contains("En cours") -> SManga.ONGOING - status.contains("Complété") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun parseChapterDate(date: String) = parseChapterDateByLang(date) - - // https://fr.ninemanga.com/search/?type=high - override fun getGenreList() = listOf( - Genre("AcadéMie", "175"), - Genre("Action", "5"), - Genre("Adolescence", "205"), - Genre("Adulte", "52"), - Genre("Adventure", "27"), - Genre("Agriculture", "121"), - Genre("Alice Aux Pays Des Merveilles", "253"), - Genre("Aliens", "109"), - Genre("Alpinisme", "243"), - Genre("Ambition", "282"), - Genre("Amitié", "13"), - Genre("Amour", "146"), - Genre("Anges", "98"), - Genre("Angleterre", "283"), - Genre("Animaux", "120"), - Genre("Apprentissage", "89"), - Genre("Argent", "263"), - Genre("Arnaque", "259"), - Genre("Arts Martiaux", "24"), - Genre("Assassinat", "84"), - Genre("Astronaute", "186"), - Genre("Autre Monde", "92"), - Genre("Aventure", "11"), - Genre("Aviation", "206"), - Genre("Bandit", "71"), - Genre("Baseball", "169"), - Genre("Basket", "287"), - Genre("Basketball", "288"), - Genre("Baston", "157"), - Genre("Bataille Navale", "203"), - Genre("Bateau", "266"), - Genre("Biographique", "285"), - Genre("Boxe", "103"), - Genre("Bug", "215"), - Genre("Cafard", "216"), - Genre("Campagne", "172"), - Genre("Camping", "291"), - Genre("Cartes", "268"), - Genre("Chantage", "57"), - Genre("Chasseur", "23"), - Genre("Chevalier", "72"), - Genre("Clonage", "207"), - Genre("Club", "123"), - Genre("Coach", "195"), - Genre("Cobaye", "217"), - Genre("CollèGe", "208"), - Genre("Combats", "14"), - Genre("Comedy", "25"), - Genre("CompéTition", "127"), - Genre("ComÉDie", "81"), - Genre("ComéDie", "6"), - Genre("Conte", "254"), - Genre("Cosmos", "270"), - Genre("Course", "245"), - Genre("Crime", "66"), - Genre("Crossdressing", "53"), - Genre("CréAture", "182"), - Genre("Cuisine", "34"), - Genre("Cyberpunk", "264"), - Genre("Cyborgs", "119"), - Genre("Death Game", "279"), - Genre("Destin", "269"), - Genre("Dette", "260"), - Genre("Dimension", "134"), - Genre("Don", "185"), - Genre("Doujinshi", "278"), - Genre("Dragons", "197"), - Genre("Drama", "35"), - Genre("Drame", "2"), - Genre("Drift", "246"), - Genre("Dystopie", "112"), - Genre("DéLinquant", "222"), - Genre("DéLinquants", "148"), - Genre("DéMons", "18"), - Genre("DéTective", "122"), - Genre("Ecole", "49"), - Genre("Empire", "223"), - Genre("Enfance", "231"), - Genre("Enfer", "237"), - Genre("EnquêTe", "228"), - Genre("Entomologie", "218"), - Genre("Erotique", "158"), - Genre("Escalade", "271"), - Genre("Espace", "135"), - Genre("Espionnage", "199"), - Genre("Esprit", "22"), - Genre("Extra-Terrestres", "136"), - Genre("Famille", "54"), - Genre("Fantastique", "1"), - Genre("Fantasy", "28"), - Genre("FantôMes", "20"), - Genre("Feu", "255"), - Genre("Filles Et Pistolets", "152"), - Genre("Flamme", "256"), - Genre("Folklore", "78"), - Genre("Football", "239"), - Genre("Fruit", "7"), - Genre("FrèRe", "187"), - Genre("Fuite", "214"), - Genre("Furyo", "209"), - Genre("Game", "129"), - Genre("Garde Du Corps", "167"), - Genre("Gastronomie", "97"), - Genre("Gender Bender", "51"), - Genre("Genderswap", "171"), - Genre("Glace", "257"), - Genre("Gore", "105"), - Genre("Guerre", "15"), - Genre("Guerrier", "225"), - Genre("GéNie", "229"), - Genre("GéNéTique", "219"), - Genre("Handicap", "162"), - Genre("HarcèLement", "161"), - Genre("Harem Inversé", "274"), - Genre("Heroic-Fantasy", "140"), - Genre("Histoire", "154"), - Genre("Histoires Courtes", "160"), - Genre("Historical", "41"), - Genre("Historique", "76"), - Genre("Homosexualité", "267"), - Genre("Horreur", "19"), - Genre("Horror", "63"), - Genre("Humour", "79"), - Genre("Idols", "191"), - Genre("Immortalité", "132"), - Genre("Insecte", "220"), - Genre("Isekai", "36"), - Genre("Jeu", "70"), - Genre("Jeunesse", "232"), - Genre("Jeux VidéO", "147"), - Genre("Josei", "94"), - Genre("Justicier", "176"), - Genre("Kaiju", "289"), - Genre("LittéRature", "196"), - Genre("Loli", "244"), - Genre("Love Hotel", "58"), - Genre("Lune", "188"), - Genre("LycéE", "126"), - Genre("Mafia", "142"), - Genre("Magical Girl", "99"), - Genre("Magical Girls", "286"), - Genre("Magie", "8"), - Genre("MaléDiction", "193"), - Genre("Maritime", "202"), - Genre("Mars", "221"), - Genre("Massacre", "258"), - Genre("Matchs", "125"), - Genre("Mecha", "68"), - Genre("Mechas", "153"), - Genre("Medical", "65"), - Genre("Mentor", "177"), - Genre("Militaire", "115"), - Genre("Mmo", "226"), - Genre("Monster Girls", "252"), - Genre("Monstres", "77"), - Genre("Montagne", "272"), - Genre("Mort", "133"), - Genre("Moto", "210"), - Genre("Moyen ÂGe", "106"), - Genre("Musique", "151"), - Genre("Mystery", "40"), - Genre("MystÈRe", "85"), - Genre("MystèRe", "3"), - Genre("MéDecine", "137"), - Genre("MéDiéVal", "139"), - Genre("Nasa", "189"), - Genre("Nature", "227"), - Genre("Navire", "265"), - Genre("Nekketsu", "178"), - Genre("Ninjas", "59"), - Genre("Nostalgie", "242"), - Genre("Nourriture", "33"), - Genre("One Shot", "173"), - Genre("Organisations SecrèTes", "138"), - Genre("Orphelin", "212"), - Genre("Otage", "280"), - Genre("Otaku", "190"), - Genre("Paranormal", "131"), - Genre("Parodie", "96"), - Genre("Philosophical", "64"), - Genre("Philosophique", "235"), - Genre("Pirates", "9"), - Genre("Plage", "275"), - Genre("PlongéE", "276"), - Genre("Police", "236"), - Genre("Policier", "150"), - Genre("Politique", "91"), - Genre("Post-Apocalypse", "234"), - Genre("Post-Apocalyptique", "113"), - Genre("Pouvoirs Psychiques", "130"), - Genre("Pouvoirs", "10"), - Genre("Princesse", "166"), - Genre("Prison", "156"), - Genre("Professeur", "181"), - Genre("Promenade", "273"), - Genre("Prostitution", "261"), - Genre("Psychological", "61"), - Genre("Psychologie", "74"), - Genre("Psychologique", "42"), - Genre("Quotidien", "93"), - Genre("Racing", "247"), - Genre("Religion", "201"), - Genre("Robots", "233"), - Genre("Roi", "12"), - Genre("Romance", "26"), - Genre("Rpg", "141"), - Genre("RéIncarnation", "107"), - Genre("RêVes", "149"), - Genre("Sabre", "144"), - Genre("Sadique", "55"), - Genre("Samourai", "145"), - Genre("Samurai", "155"), - Genre("School Life", "43"), - Genre("Sci-Fi", "44"), - Genre("Science-Fiction", "31"), - Genre("Scientifique", "174"), - Genre("Scolaire", "163"), - Genre("Secrets", "184"), - Genre("Seinen", "88"), - Genre("Sherlock Holmes", "284"), - Genre("Shinigami", "21"), - Genre("Shogi", "165"), - Genre("Shojo Ai", "87"), - Genre("Shojo", "101"), - Genre("Shonen Ai", "240"), - Genre("Shonen", "80"), - Genre("Shoujo Ai", "45"), - Genre("Shounen Ai", "39"), - Genre("Slice Of Life", "29"), - Genre("Social", "69"), - Genre("SociéTé", "118"), - Genre("Sonyun-Manhwa", "170"), - Genre("Sport", "102"), - Genre("Sports MéCaniques", "251"), - Genre("Sports", "67"), - Genre("Steampunk", "116"), - Genre("Suicide", "238"), - Genre("Super Pouvoirs", "16"), - Genre("Super-HéRos", "180"), - Genre("Super-Vilains", "179"), - Genre("Superhero", "62"), - Genre("Surnaturel", "4"), - Genre("Survival Game", "117"), - Genre("Survival", "290"), - Genre("Survivre", "213"), - Genre("Suspense", "75"), - Genre("Talent", "230"), - Genre("Tennis", "183"), - Genre("Thriller", "128"), - Genre("Titans", "114"), - Genre("Tournois", "30"), - Genre("Traditions", "204"), - Genre("Tragedy", "37"), - Genre("Tragique", "111"), - Genre("TragéDie", "73"), - Genre("Tranche De Vie", "48"), - Genre("Transidentité", "143"), - Genre("Travail", "198"), - Genre("Travestissement", "192"), - Genre("Triangle Amoureux", "168"), - Genre("Tuning", "248"), - Genre("Usurier", "262"), - Genre("Vampires", "100"), - Genre("Vengeance", "83"), - Genre("Video Games", "281"), - Genre("Vie Scolaire", "86"), - Genre("Violence", "194"), - Genre("Virtuel", "200"), - Genre("Vitesse", "249"), - Genre("Voiture", "250"), - Genre("Volley-Ball", "124"), - Genre("Voyage Dans Le Temps", "104"), - Genre("Voyage Temporel", "108"), - Genre("Voyage", "17"), - Genre("Voyou", "211"), - Genre("WTF", "110"), - Genre("Webtoon", "32"), - Genre("Wuxia", "46"), - Genre("Yakuza", "95"), - Genre("Yokai", "241"), - Genre("Yonkoma", "159"), - Genre("Zombies", "277"), - Genre("éChec", "164"), - Genre("éPéE", "224") - ) -} - -fun parseChapterDateByLang(date: String): Long { - val dateWords = date.split(" ") - - if (dateWords.size == 3) { - if (dateWords[1].contains(",")) { - return try { - SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } else { - val timeAgo = Integer.parseInt(dateWords[0]) - return Calendar.getInstance().apply { - when (dateWords[1]) { - "minutos" -> Calendar.MINUTE // ES - "horas" -> Calendar.HOUR - - // "minutos" -> Calendar.MINUTE // BR - "hora" -> Calendar.HOUR - - "минут" -> Calendar.MINUTE // RU - "часа" -> Calendar.HOUR - - "Stunden" -> Calendar.HOUR // DE - - "minuti" -> Calendar.MINUTE // IT - "ore" -> Calendar.HOUR - - "minutes" -> Calendar.MINUTE // FR - "heures" -> Calendar.HOUR - else -> null - }?.let { - add(it, -timeAgo) - } - }.timeInMillis - } - } - return 0L -} diff --git a/src/all/noisemanga/AndroidManifest.xml b/src/all/noisemanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/noisemanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/noisemanga/build.gradle b/src/all/noisemanga/build.gradle deleted file mode 100644 index d345a7231..000000000 --- a/src/all/noisemanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NOISE Manga' - pkgNameSuffix = 'all.noisemanga' - extClass = '.NoiseMangaFactory' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/noisemanga/res/mipmap-hdpi/ic_launcher.png b/src/all/noisemanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8af54945c..000000000 Binary files a/src/all/noisemanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/noisemanga/res/mipmap-mdpi/ic_launcher.png b/src/all/noisemanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8799d3a64..000000000 Binary files a/src/all/noisemanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/noisemanga/res/mipmap-xhdpi/ic_launcher.png b/src/all/noisemanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1a4216e30..000000000 Binary files a/src/all/noisemanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/noisemanga/res/mipmap-xxhdpi/ic_launcher.png b/src/all/noisemanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2f0bc1006..000000000 Binary files a/src/all/noisemanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/noisemanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/noisemanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a3d031dd3..000000000 Binary files a/src/all/noisemanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/noisemanga/res/web_hi_res_512.png b/src/all/noisemanga/res/web_hi_res_512.png deleted file mode 100644 index d53c26e7f..000000000 Binary files a/src/all/noisemanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/noisemanga/src/eu/kanade/tachiyomi/extension/all/noisemanga/NoiseManga.kt b/src/all/noisemanga/src/eu/kanade/tachiyomi/extension/all/noisemanga/NoiseManga.kt deleted file mode 100644 index d6c4473db..000000000 --- a/src/all/noisemanga/src/eu/kanade/tachiyomi/extension/all/noisemanga/NoiseManga.kt +++ /dev/null @@ -1,145 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.noisemanga - -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -abstract class NoiseManga(override val lang: String) : ParsedHttpSource() { - - override val name = "NOISE" - - override val baseUrl = "https://noisemanga.com" - - override val supportsLatest = false - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaSelector(): String = "ul#menu-home li a[title=\"Séries\"] + ul li a" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.text() - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = baseUrl + SLUG_TO_DETAILS_MAP[url]?.thumbnail_url - } - - override fun popularMangaNextPageSelector(): String? = null - - /** - * Since there are only three series, it's worth to do a client-side search. - */ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return super.fetchSearchManga(page, query, filters) - .map { - val mangas = it.mangas.filter { m -> m.title.contains(query, true) } - MangasPage(mangas, it.hasNextPage) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(page) - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsRequest(manga: SManga): Request = GET(baseUrl + manga.url, headers) - - override fun mangaDetailsParse(document: Document): SManga { - val mainContent = document.select("div.main-content-page").first() - val entryContent = mainContent.select("div.entry-content").first() - val descriptionSelector = if (lang == "en") "h4 + h4, h4 + div h4" else "h1 + h4" - val mangaSlug = document.location().replace(baseUrl, "") - - return SManga.create().apply { - title = mainContent.select("header h1.single-title").first()!!.text() - author = SLUG_TO_DETAILS_MAP[mangaSlug]?.author - artist = SLUG_TO_DETAILS_MAP[mangaSlug]?.artist - status = SManga.ONGOING - description = entryContent.select(descriptionSelector).last()!!.text() - thumbnail_url = baseUrl + SLUG_TO_DETAILS_MAP[mangaSlug]?.thumbnail_url - } - } - - override fun chapterListRequest(manga: SManga): Request = GET(baseUrl + manga.url, headers) - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector(): String { - val columnSelector = if (lang == "pt-BR") 1 else 2 - - return "div.entry-content div table tr td:nth-child($columnSelector) a" - } - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.text() - scanlator = "NOISE Manga" - setUrlWithoutDomain(element.attr("href")) - } - - override fun pageListRequest(chapter: SChapter): Request = GET(baseUrl + chapter.url, headers) - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.single-content div.single-entry-summary img.aligncenter") - .mapIndexed { i, element -> - val imgUrl = element.attr("srcset") - .substringAfterLast(", ") - .substringBeforeLast(" ") - Page(i, "", imgUrl) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" - - /** - * There isn't a good title list available with all the information. - * Since the service does only have three series, it's worth to manually - * add the missing information, such as artist, author, and thumbnail. - */ - private val SLUG_TO_DETAILS_MAP = mapOf( - "/quack/" to SManga.create().apply { - artist = "Kaji Pato" - author = "Kaji Pato" - thumbnail_url = "/wp-content/uploads/2019/11/quack1.jpg" - }, - "/japow/" to SManga.create().apply { - artist = "Eduardo Capelo" - author = "Jun Sugiyama" - thumbnail_url = "/wp-content/uploads/2019/11/JAPOW_000_NOISE_0000.jpg" - }, - "/tools-challenge/" to SManga.create().apply { - artist = "Max Andrade" - author = "Max Andrade" - thumbnail_url = "/wp-content/uploads/2019/11/TC_001_NOISE_0000-1.jpg" - } - ) - } -} diff --git a/src/all/noisemanga/src/eu/kanade/tachiyomi/extension/all/noisemanga/NoiseMangaFactory.kt b/src/all/noisemanga/src/eu/kanade/tachiyomi/extension/all/noisemanga/NoiseMangaFactory.kt deleted file mode 100644 index 9db00dffe..000000000 --- a/src/all/noisemanga/src/eu/kanade/tachiyomi/extension/all/noisemanga/NoiseMangaFactory.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.noisemanga - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class NoiseMangaFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - NoiseMangaEnglish(), - NoiseMangaPortuguese() - ) -} - -class NoiseMangaEnglish : NoiseManga("en") - -class NoiseMangaPortuguese : NoiseManga("pt-BR") { - // Hardcode the id because the language wasn't specific. - override val id: Long = 8279458690164834090 -} diff --git a/src/all/simplyhentai/AndroidManifest.xml b/src/all/simplyhentai/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/simplyhentai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/simplyhentai/build.gradle b/src/all/simplyhentai/build.gradle deleted file mode 100644 index 841078eb3..000000000 --- a/src/all/simplyhentai/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Simply Hentai' - pkgNameSuffix = 'all.simplyhentai' - extClass = '.SimplyHentaiFactory' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/simplyhentai/res/mipmap-hdpi/ic_launcher.png b/src/all/simplyhentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index deaf72f0a..000000000 Binary files a/src/all/simplyhentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/simplyhentai/res/mipmap-mdpi/ic_launcher.png b/src/all/simplyhentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ba94037e4..000000000 Binary files a/src/all/simplyhentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/simplyhentai/res/mipmap-xhdpi/ic_launcher.png b/src/all/simplyhentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ce32746e2..000000000 Binary files a/src/all/simplyhentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/simplyhentai/res/mipmap-xxhdpi/ic_launcher.png b/src/all/simplyhentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a318e0f65..000000000 Binary files a/src/all/simplyhentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/simplyhentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/simplyhentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a110242b6..000000000 Binary files a/src/all/simplyhentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/simplyhentai/res/web_hi_res_512.png b/src/all/simplyhentai/res/web_hi_res_512.png deleted file mode 100644 index ed84036a7..000000000 Binary files a/src/all/simplyhentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/simplyhentai/src/eu/kanade/tachiyomi/extension/all/simplyhentai/SimplyHentai.kt b/src/all/simplyhentai/src/eu/kanade/tachiyomi/extension/all/simplyhentai/SimplyHentai.kt deleted file mode 100644 index 32284f862..000000000 --- a/src/all/simplyhentai/src/eu/kanade/tachiyomi/extension/all/simplyhentai/SimplyHentai.kt +++ /dev/null @@ -1,218 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.simplyhentai - -import android.annotation.SuppressLint -import com.github.salomonbrys.kotson.forEach -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat - -abstract class SimplyHentai( - override val lang: String, - private val urlLang: String, - private val searchLang: String -) : ParsedHttpSource() { - override val name = "Simply Hentai" - - override val baseUrl = "https://www.simply-hentai.com" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/album/language/$urlLang/$page/popularity/desc", headers) - } - - override fun popularMangaSelector() = "div.col-sm-3" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - element.select("h3.object-title a").let { - manga.url = it.attr("href").substringAfter(baseUrl) - manga.title = it.text() - } - manga.thumbnail_url = element.select("img.img-responsive").attr("abs:data-src") - - return manga - } - - override fun popularMangaNextPageSelector() = "a[rel=next]" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/album/language/$urlLang/$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("query", query) - .addQueryParameter("language_ids[$searchLang]", searchLang) - .addQueryParameter("page", page.toString()) - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> { - filter.state.forEach { - if (it.state) url.addQueryParameter("tag_ids[${it.id}]", it.id) - } - } - is SeriesList -> { - filter.state.forEach { - if (it.state) url.addQueryParameter("series_id[${it.id}]", it.id) - } - } - is SortOrder -> { - url.addQueryParameter("sort", getSortOrder()[filter.state].second) - } - } - } - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - - document.select("div.padding-md-right-8").let { info -> - manga.artist = info.select("div.box-title:contains(Artists) + a").text() - manga.author = manga.artist - manga.genre = info.select("a[rel=tag]").joinToString { it.text() } - manga.description = info.select("div.link-box > div.box-title:contains(Series) ~ a").let { e -> - if (e.text().isNotEmpty()) "Series: ${e.joinToString { it.text() }}\n\n" else "" - } - manga.description += info.select("div.link-box > div.box-title:contains(Characters) ~ a").let { e -> - if (e.text().isNotEmpty()) ("Characters: ${e.joinToString { it.text() }}\n\n") else "" - } - manga.status = SManga.COMPLETED - } - manga.thumbnail_url = document.select("div.col-xs-12 img.img-responsive").attr("abs:data-src") - - return manga - } - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - return listOf( - SChapter.create().apply { - name = "Chapter" - url = response.request().url().toString().removeSuffix("/").substringAfterLast("/") - chapter_number = 1f - - date_upload = response.asJsoup().select(".stat-container div:contains(Uploaded) div.bold")?.text().let { - DATE_FORMAT.parse(it!!)?.time - } ?: 0L - } - ) - } - - override fun chapterListSelector() = "not used" - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return GET("https://api.simply-hentai.com/v1/images/album/${chapter.url}", headersBuilder().add("X-Requested-With", "XMLHttpRequest").build()) - } - - override fun pageListParse(response: Response): List<Page> { - val pages = mutableListOf<Page>() - - gson.fromJson<JsonObject>(response.body()!!.string()).forEach { _, jsonElement -> - pages.add(Page(pages.size, "", jsonElement["sizes"]["full"].string)) - } - - return pages - } - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - private class SortOrder(sortPairs: List<Pair<String, String>>) : Filter.Select<String>("Sort By", sortPairs.map { it.first }.toTypedArray()) - private class SearchPair(name: String, val id: String = name) : Filter.CheckBox(name) - private class GenreList(searchVal: List<SearchPair>) : Filter.Group<SearchPair>("Genres", searchVal) - private class SeriesList(searchVal: List<SearchPair>) : Filter.Group<SearchPair>("Series", searchVal) - - override fun getFilterList() = FilterList( - SortOrder(getSortOrder()), - GenreList(getGenreList()), - SeriesList(getSeriesList()) - ) - - // "Relevance" should be empty, don't add a "Views" sort order - private fun getSortOrder() = listOf( - Pair("Relevance", ""), - Pair("Popularity", "sort_value"), - Pair("Upload Date", "created_at") - ) - - // TODO: add more to getGenreList and getSeriesList - private fun getGenreList() = listOf( - SearchPair("Solo Female", "4807"), - SearchPair("Solo Male", "4805"), - SearchPair("Big Breasts", "2528"), - SearchPair("Nakadashi", "2418"), - SearchPair("Blowjob", "64"), - SearchPair("Schoolgirl Uniform", "2522"), - SearchPair("Stockings", "33") - ) - - private fun getSeriesList() = listOf( - SearchPair("Original Work", "1093"), - SearchPair("Kantai Collection", "1316"), - SearchPair("Touhou", "747"), - SearchPair("Fate Grand Order", "2171"), - SearchPair("Idolmaster", "306"), - SearchPair("Granblue Fantasy", "2041"), - SearchPair("Girls Und Panzer", "1324") - ) - - companion object { - - @SuppressLint("SimpleDateFormat") - private val DATE_FORMAT = SimpleDateFormat("dd.MM.yyyy") - } -} diff --git a/src/all/simplyhentai/src/eu/kanade/tachiyomi/extension/all/simplyhentai/SimplyHentaiFactory.kt b/src/all/simplyhentai/src/eu/kanade/tachiyomi/extension/all/simplyhentai/SimplyHentaiFactory.kt deleted file mode 100644 index a42d02915..000000000 --- a/src/all/simplyhentai/src/eu/kanade/tachiyomi/extension/all/simplyhentai/SimplyHentaiFactory.kt +++ /dev/null @@ -1,30 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.simplyhentai - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -@Nsfw -class SimplyHentaiFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - SimplyHentaiEN(), - SimplyHentaiJA(), - SimplyHentaiZH(), - SimplyHentaiKO(), - SimplyHentaiES(), - SimplyHentaiRU(), - SimplyHentaiFR(), - SimplyHentaiDE(), - SimplyHentaiPT() - ) -} - -class SimplyHentaiEN : SimplyHentai("en", "english", "1") -class SimplyHentaiJA : SimplyHentai("ja", "japanese", "2") -class SimplyHentaiZH : SimplyHentai("zh", "chinese", "11") -class SimplyHentaiKO : SimplyHentai("ko", "korean", "9") -class SimplyHentaiES : SimplyHentai("es", "spanish", "8") -class SimplyHentaiRU : SimplyHentai("ru", "russian", "7") -class SimplyHentaiFR : SimplyHentai("fr", "french", "3") -class SimplyHentaiDE : SimplyHentai("de", "german", "4") -class SimplyHentaiPT : SimplyHentai("pt", "portuguese", "6") diff --git a/src/all/thelibraryofohara/AndroidManifest.xml b/src/all/thelibraryofohara/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/thelibraryofohara/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/thelibraryofohara/build.gradle b/src/all/thelibraryofohara/build.gradle deleted file mode 100644 index 7a68b4416..000000000 --- a/src/all/thelibraryofohara/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'The Library of Ohara' - pkgNameSuffix = 'all.thelibraryofohara' - extClass = '.TheLibraryOfOharaFactory' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/thelibraryofohara/res/mipmap-hdpi/ic_launcher.png b/src/all/thelibraryofohara/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cb0ef243e..000000000 Binary files a/src/all/thelibraryofohara/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/thelibraryofohara/res/mipmap-mdpi/ic_launcher.png b/src/all/thelibraryofohara/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index de139bb84..000000000 Binary files a/src/all/thelibraryofohara/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/thelibraryofohara/res/mipmap-xhdpi/ic_launcher.png b/src/all/thelibraryofohara/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2599895fd..000000000 Binary files a/src/all/thelibraryofohara/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/thelibraryofohara/res/mipmap-xxhdpi/ic_launcher.png b/src/all/thelibraryofohara/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6d7b4fc33..000000000 Binary files a/src/all/thelibraryofohara/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/thelibraryofohara/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/thelibraryofohara/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7c06f583e..000000000 Binary files a/src/all/thelibraryofohara/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/thelibraryofohara/res/web_hi_res_512.png b/src/all/thelibraryofohara/res/web_hi_res_512.png deleted file mode 100644 index d6b8aba25..000000000 Binary files a/src/all/thelibraryofohara/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/thelibraryofohara/src/eu/kanade/tachiyomi/extension/all/thelibraryofohara/TheLibraryOfOhara.kt b/src/all/thelibraryofohara/src/eu/kanade/tachiyomi/extension/all/thelibraryofohara/TheLibraryOfOhara.kt deleted file mode 100644 index c32488077..000000000 --- a/src/all/thelibraryofohara/src/eu/kanade/tachiyomi/extension/all/thelibraryofohara/TheLibraryOfOhara.kt +++ /dev/null @@ -1,212 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.thelibraryofohara - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale - -class TheLibraryOfOhara(override val lang: String, private val siteLang: String) : ParsedHttpSource() { - - override val name = "The Library of Ohara" - - override val baseUrl = "https://thelibraryofohara.com" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - // only show entries which contain pictures only. - override fun popularMangaSelector() = when (lang) { - "en" -> - "#categories-7 ul li.cat-item-589813936," + // Chapter Secrets - "#categories-7 ul li.cat-item-607613583, " + // Chapter Secrets Specials - "#categories-7 ul li.cat-item-43972770, " + // Charlotte Family - "#categories-7 ul li.cat-item-9363667, " + // Complete Guides - "#categories-7 ul li.cat-item-634609261, " + // Parody Chapter - "#categories-7 ul li.cat-item-699200615, " + // Return to the Reverie - "#categories-7 ul li.cat-item-139757, " + // SBS - "#categories-7 ul li.cat-item-22695, " + // Timeline - "#categories-7 ul li.cat-item-648324575" // Vivre Card Databook - "id" -> "#categories-7 ul li.cat-item-702404482, #categories-7 ul li.cat-item-699200615" // Chapter Secrets Bahasa Indonesia, Return to the Reverie - "fr" -> "#categories-7 ul li.cat-item-699200615" // Return to the Reverie - "ar" -> "#categories-7 ul li.cat-item-699200615" // Return to the Reverie - "it" -> "#categories-7 ul li.cat-item-699200615" // Return to the Reverie - else -> "#categories-7 ul li.cat-item-693784776, #categories-7 ul li.cat-item-699200615" // Chapter Secrets (multilingual), Return to the Reverie - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.title = element.select("a").text() - manga.setUrlWithoutDomain(element.select("a").attr("href")) - return manga - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesRequest(page: Int) = popularMangaRequest(page) - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("This method should not be called!") - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(1) - - private fun searchMangaParse(response: Response, query: String): MangasPage { - return MangasPage(popularMangaParse(response).mangas.filter { it.title.contains(query, ignoreCase = true) }, false) - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.title = document.select("h1.page-title").text().replace("Category: ", "") - manga.thumbnail_url = chooseChapterThumbnail(document, manga.title) - manga.description = "" - manga.status = SManga.ONGOING - return manga - } - - // Use one of the chapter thumbnails as manga thumbnail - // Some thumbnails have a flag on them which indicates the Language. - // Try to choose a thumbnail with a matching flag - private fun chooseChapterThumbnail(document: Document, mangaTitle: String): String? { - var imgElement: Element? = null - - // Reverie - if (mangaTitle.contains("Reverie")) { - imgElement = document.select("article").firstOrNull { element -> - val chapterTitle = element.select("h2.entry-title a").text() - (chapterTitle.contains(siteLang) || (lang == "en" && !chapterTitle.contains(Regex("""(French|Arabic|Italian|Indonesia|Spanish)""")))) - } - } - // Chapter Secrets (multilingual) - if (mangaTitle.contains("Chapter Secrets") && lang != "en") { - imgElement = document.select("article").firstOrNull { - val chapterTitle = it.select("h2.entry-title a").text() - ((lang == "id" && chapterTitle.contains("Indonesia")) || (lang == "es" && !chapterTitle.contains("Indonesia"))) - } - } - - // Fallback - imgElement = imgElement ?: document.select("article:first-of-type").firstOrNull() - return imgElement?.select("img")?.attr("abs:src") - } - - // Chapters - - override fun chapterListSelector() = "article" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - chapter.setUrlWithoutDomain(element.select("a.entry-thumbnail").attr("href")) - chapter.name = element.select("h2.entry-title a").text() - chapter.date_upload = parseChapterDate(element.select("span.posted-on time").attr("datetime")) - - return chapter - } - - private fun parseChapterDate(date: String): Long { - val parsedDate = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()).parse(date.replace("+00:00", "+0000")) - return parsedDate?.time ?: 0L - } - - private fun chapterNextPageSelector() = "div.nav-previous a" - - override fun chapterListParse(response: Response): List<SChapter> { - val allChapters = mutableListOf<SChapter>() - var document = response.asJsoup() - - while (true) { - val pageChapters = document.select(chapterListSelector()).map { chapterFromElement(it) } - if (pageChapters.isEmpty()) - break - - allChapters += pageChapters - - val hasNextPage = document.select(chapterNextPageSelector()).isNotEmpty() - if (!hasNextPage) - break - - val nextUrl = document.select(chapterNextPageSelector()).attr("href") - document = client.newCall(GET(nextUrl, headers)).execute().asJsoup() - } - - if (allChapters.isNotEmpty() && allChapters[0].name.contains("Reverie")) { - return when (lang) { - "fr" -> allChapters.filter { it.name.contains("French") }.toMutableList() - "ar" -> allChapters.filter { it.name.contains("Arabic") }.toMutableList() - "it" -> allChapters.filter { it.name.contains("Italian") }.toMutableList() - "id" -> allChapters.filter { it.name.contains("Indonesia") }.toMutableList() - "es" -> allChapters.filter { it.name.contains("Spanish") }.toMutableList() - else -> allChapters.filter { // english - !it.name.contains("French") && - !it.name.contains("Arabic") && - !it.name.contains("Italian") && - !it.name.contains("Indonesia") && - !it.name.contains("Spanish") - }.toMutableList() - } - } - - // Remove Indonesian posts if lang is spanish - // Indonesian and Spanish posts are mixed in the same category "multilingual" on the website - // BTW, the same problem doesn't apply if lang is Indonesian because Indonesian has its own category - if (lang == "es") { - return allChapters.filter { !it.name.contains("Indonesia") }.toMutableList() - } - - return allChapters - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("div.entry-content").select("a img, img.size-full").forEachIndexed { i, img -> - pages.add(Page(i, "", img.attr("data-orig-file"))) - } - - return pages - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") -} diff --git a/src/all/thelibraryofohara/src/eu/kanade/tachiyomi/extension/all/thelibraryofohara/TheLibraryOfOharaFactory.kt b/src/all/thelibraryofohara/src/eu/kanade/tachiyomi/extension/all/thelibraryofohara/TheLibraryOfOharaFactory.kt deleted file mode 100644 index 8f084dfe3..000000000 --- a/src/all/thelibraryofohara/src/eu/kanade/tachiyomi/extension/all/thelibraryofohara/TheLibraryOfOharaFactory.kt +++ /dev/null @@ -1,21 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.thelibraryofohara - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class TheLibraryOfOharaFactory : SourceFactory { - override fun createSources(): List<Source> = languageList.map { TheLibraryOfOhara(it.tachiLang, it.siteLang) } -} - -private data class Source(val tachiLang: String, val siteLang: String) - -private val languageList = listOf( - - Source("id", "Indonesia"), - Source("en", "English"), - Source("es", "Spanish"), - Source("it", "Italian"), - Source("ar", "Arabic"), - Source("fr", "French") - -) diff --git a/src/all/toomics/AndroidManifest.xml b/src/all/toomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/toomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/toomics/build.gradle b/src/all/toomics/build.gradle deleted file mode 100644 index d4ca9f88f..000000000 --- a/src/all/toomics/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Toomics' - pkgNameSuffix = 'all.toomics' - extClass = '.ToomicsFactory' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/toomics/res/mipmap-hdpi/ic_launcher.png b/src/all/toomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3999f54bd..000000000 Binary files a/src/all/toomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/toomics/res/mipmap-mdpi/ic_launcher.png b/src/all/toomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8a0a435ec..000000000 Binary files a/src/all/toomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/toomics/res/mipmap-xhdpi/ic_launcher.png b/src/all/toomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5bffc34fd..000000000 Binary files a/src/all/toomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/toomics/res/mipmap-xxhdpi/ic_launcher.png b/src/all/toomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 371fc8a42..000000000 Binary files a/src/all/toomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/toomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/toomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ecb96fa07..000000000 Binary files a/src/all/toomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/toomics/res/web_hi_res_512.png b/src/all/toomics/res/web_hi_res_512.png deleted file mode 100644 index 320869867..000000000 Binary files a/src/all/toomics/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/toomics/src/eu/kanade/tachiyomi/extension/all/toomics/ToomicsFactory.kt b/src/all/toomics/src/eu/kanade/tachiyomi/extension/all/toomics/ToomicsFactory.kt deleted file mode 100644 index 248601772..000000000 --- a/src/all/toomics/src/eu/kanade/tachiyomi/extension/all/toomics/ToomicsFactory.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.toomics - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -import java.text.SimpleDateFormat -import java.util.Locale - -class ToomicsFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - ToomicsEnglish(), - ToomicsSimplifiedChinese(), - ToomicsTraditionalChinese(), - ToomicsSpanishLA(), - ToomicsSpanish(), - ToomicsItalian(), - ToomicsGerman(), - ToomicsFrench(), - ToomicsPortuguese() - ) -} - -class ToomicsEnglish : ToomicsGlobal("en", SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH)) -class ToomicsSimplifiedChinese : ToomicsGlobal("sc", SimpleDateFormat("yyyy.MM.dd", Locale.SIMPLIFIED_CHINESE), "zh", "简体") -class ToomicsTraditionalChinese : ToomicsGlobal("tc", SimpleDateFormat("yyyy.MM.dd", Locale.TRADITIONAL_CHINESE), "zh", "繁體") -class ToomicsSpanishLA : ToomicsGlobal("mx", SimpleDateFormat("d MMM, yyyy", Locale("es", "419")), "es", "LA") -class ToomicsSpanish : ToomicsGlobal("es", SimpleDateFormat("d MMM, yyyy", Locale("es", "419")), "es") -class ToomicsItalian : ToomicsGlobal("it", SimpleDateFormat("d MMM, yyyy", Locale.ITALIAN)) -class ToomicsGerman : ToomicsGlobal("de", SimpleDateFormat("d. MMM yyyy", Locale.GERMAN)) -class ToomicsFrench : ToomicsGlobal("fr", SimpleDateFormat("dd MMM. yyyy", Locale.ENGLISH)) - -class ToomicsPortuguese : ToomicsGlobal("por", SimpleDateFormat("d 'de' MMM 'de' yyyy", Locale("pt", "BR")), "pt-BR") { - // Hardcode the id because the language wasn't specific. - override val id: Long = 4488498756724948818 -} diff --git a/src/all/toomics/src/eu/kanade/tachiyomi/extension/all/toomics/ToomicsGlobal.kt b/src/all/toomics/src/eu/kanade/tachiyomi/extension/all/toomics/ToomicsGlobal.kt deleted file mode 100644 index 8d6e69244..000000000 --- a/src/all/toomics/src/eu/kanade/tachiyomi/extension/all/toomics/ToomicsGlobal.kt +++ /dev/null @@ -1,168 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.toomics - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.net.URLDecoder -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.concurrent.TimeUnit - -abstract class ToomicsGlobal( - private val siteLang: String, - private val dateFormat: SimpleDateFormat, - override val lang: String = siteLang, - displayName: String = "" -) : ParsedHttpSource() { - - override val name = "Toomics (Only free chapters)" + (if (displayName.isNotEmpty()) " ($displayName)" else "") - - override val baseUrl = "https://global.toomics.com" - - override val supportsLatest = true - - override val client: OkHttpClient = super.client.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/$siteLang") - .add("User-Agent", USER_AGENT) - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/$siteLang/webtoon/favorite", headers) - } - - // ToomicsGlobal does not have a popular list, so use recommended instead. - override fun popularMangaSelector(): String = "li > div.visual" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h4[class$=title]").first().ownText() - // sometimes href contains "/ab/on" at the end and redirects to a chapter instead of manga - setUrlWithoutDomain(element.select("a").attr("href").removeSuffix("/ab/on")) - thumbnail_url = element.select("img").attr("src") - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/$siteLang/webtoon/new_comics", headers) - } - - override fun latestUpdatesSelector(): String = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = headersBuilder() - .add("Content-Type", "application/x-www-form-urlencoded") - .build() - - val rbody = RequestBody.create(null, "toonData=$query&offset=0&limit=20") - - return POST("$baseUrl/$siteLang/webtoon/ajax_search", newHeaders, rbody) - } - - override fun searchMangaSelector(): String = "div.recently_list ul li" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("a div.search_box dl dt span.title").text() - thumbnail_url = element.select("div.search_box p.img img").attr("abs:src") - - // When the family mode is off, the url is encoded and is available in the onclick. - element.select("a:not([href^=javascript])").let { - if (it != null) { - setUrlWithoutDomain(it.attr("href")) - } else { - val toonId = element.select("a").attr("onclick") - .substringAfter("Base.setDisplay('A', '") - .substringBefore("'") - .let { url -> URLDecoder.decode(url, "UTF-8") } - .substringAfter("?toon=") - .substringBefore("&") - url = "/$siteLang/webtoon/episode/toon/$toonId" - } - } - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val header = document.select("#glo_contents header.ep-cover_ch div.title_content") - - title = header.select("h1").text() - author = header.select("p.type_box span.writer").text() - artist = header.select("p.type_box span.writer").text() - genre = header.select("p.type_box span.type").text().replace("/", ",") - description = header.select("h2").text() - thumbnail_url = document.select("head meta[property='og:image']").attr("content") - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return super.fetchChapterList(manga) - .map { it.reversed() } - } - - // coin-type1 - free chapter, coin-type6 - already read chapter - override fun chapterListSelector(): String = "li.normal_ep:has(.coin-type1, .coin-type6)" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val num = element.select("div.cell-num").text() - val numText = if (num.isNotEmpty()) "$num - " else "" - - name = numText + element.select("div.cell-title strong")?.first()?.ownText() - chapter_number = num.toFloatOrNull() ?: -1f - date_upload = parseChapterDate(element.select("div.cell-time time").text()!!) - scanlator = "Toomics" - url = element.select("a").attr("onclick") - .substringAfter("href='") - .substringBefore("'") - } - - override fun pageListParse(document: Document): List<Page> { - if (document.select("div.section_age_verif").isNotEmpty()) - throw Exception("Verify age via WebView") - - val url = document.select("head meta[property='og:url']").attr("content") - - return document.select("div[id^=load_image_] img") - .mapIndexed { i, el -> Page(i, url, el.attr("data-src")) } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun imageRequest(page: Page): Request { - val newHeaders = headers.newBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun parseChapterDate(date: String): Long { - return try { - dateFormat.parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36" - } -} diff --git a/src/all/vinnieVeritas/AndroidManifest.xml b/src/all/vinnieVeritas/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/vinnieVeritas/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/all/vinnieVeritas/build.gradle b/src/all/vinnieVeritas/build.gradle deleted file mode 100755 index 7a34c4dda..000000000 --- a/src/all/vinnieVeritas/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Vinne Veritas - CCC' - pkgNameSuffix = 'all.vinnieVeritas' - extClass = '.vinnieVeritasFactory' - extVersionCode = 2 - libVersion = '1.2' -} - -dependencies { -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/vinnieVeritas/res/mipmap-hdpi/ic_launcher.png b/src/all/vinnieVeritas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index e881d2753..000000000 Binary files a/src/all/vinnieVeritas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/vinnieVeritas/res/mipmap-mdpi/ic_launcher.png b/src/all/vinnieVeritas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index 08a704b8b..000000000 Binary files a/src/all/vinnieVeritas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/vinnieVeritas/res/mipmap-xhdpi/ic_launcher.png b/src/all/vinnieVeritas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 4cf6ca4c5..000000000 Binary files a/src/all/vinnieVeritas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/vinnieVeritas/res/mipmap-xxhdpi/ic_launcher.png b/src/all/vinnieVeritas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 9690cd103..000000000 Binary files a/src/all/vinnieVeritas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/vinnieVeritas/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/vinnieVeritas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 18f6d308f..000000000 Binary files a/src/all/vinnieVeritas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/all/vinnieVeritas/res/web_hi_res_512.png b/src/all/vinnieVeritas/res/web_hi_res_512.png deleted file mode 100755 index f1fd8ccc8..000000000 Binary files a/src/all/vinnieVeritas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/all/vinnieVeritas/src/eu/kanade/tachiyomi/extension/all/vinnieVeritas/vinnieVeritas.kt b/src/all/vinnieVeritas/src/eu/kanade/tachiyomi/extension/all/vinnieVeritas/vinnieVeritas.kt deleted file mode 100755 index dd3542c1f..000000000 --- a/src/all/vinnieVeritas/src/eu/kanade/tachiyomi/extension/all/vinnieVeritas/vinnieVeritas.kt +++ /dev/null @@ -1,108 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.vinnieVeritas - -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.online.ParsedHttpSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -open class vinnieVeritas(override val lang: String = "en") : ParsedHttpSource() { - - override val name = "Vinnie Veritas - CCC" - override val supportsLatest = false - - override val baseUrl = "https://vinnieveritas.com/" - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val manga = SManga.create() - manga.setUrlWithoutDomain("ccc-$lang/") - manga.title = if (lang == "en") { "CCC: The city of opportunities" } else { "CCC: La ciudad de las oportunidades" } - manga.artist = "Vinnie Veritas" - manga.author = "Vinnie Veritas" - manga.status = SManga.ONGOING - manga.description = if (lang == "en") { - """Almost 7 years ago I started working on a project where I would put everything I had drawn, characters, concepts and nonsense that came up while I was growing up .. it was so much I chose a city to put it all in. Like all people who draw, I abandoned many comics and concepts that I thought, sucked.. but I promised myself when I was around 19 years old that I would not abandon this one; because my ability to draw was less than today’s, the first two volumes of CCC: The city of the opportunities are… umm, ugly. When I was around 21-22 years old I began to animate in flash, so I decided to animate the world embodied in the comic and continue with the comic this time drawn in flash, therefore Volume 3 has color. - -In this period I had a lot of animation and illustration work would not let me continue the story of CCC: The city of opportunities, the hiatus lasted about 5 years, while I still did animations I did not carried on with the story in the comic .. Now new comics every Thursday. - -CCC is the name of the second largest city there is, is not an acronym or an abbreviation for something, CCC: The city of opportunies tells the story of Lucio Vasalle and his misadventures as a newcomer to CCC, comics, drawings and animations are related, they all have bits of story about the characters and their past, you are welcome to explore all this and draw your own conclusions, if you look closely you may find something that someone hasn’t noticed yet (: """ - } else { - """Hace casi 7 años empecé un proyecto donde iba a meter todas las cosas que había dibujado: personajes, conceptos y tonterias que se me habían ocurrido mientras crecía.. era tanto que pensé que en lo único donde cabría era en una ciudad. Como todos los que dibujamos, abandoné muchos comics y conceptos que no me convencieron al final.. pero me prometí a mi mismo a los 19 años que este comic no lo iba a abandonar; ya que mi habilidad para dibujar era menor a la de hoy en día los primeros dos volumenes de CCC: La ciudad de las oportunidades se ven tan… umm, culeros. Cuando tenía alrededor de 21-22 años comencé a animar en flash y me gustó, decidí animar el mundo que plasmaba en el comic y continuar con la historia dibujada en flash, por eso en el volumen 3 tiene color. - -En este lapso de tiempo tuve mucho trabajo de animación e ilustración que no me dejó continuar con la historieta de CCC: La ciudad de las oportunidades, el hiatus duró mas o menos 5 años, al mismo tiempo animaba pero ya no continuaba con la historia del comic.. Ahora ya la actualizo cada jueves. - -CCC es el nombre de la segunda ciudad mas grande que hay, no son siglas ni la abreviación de algo, CCC: La ciudad de las oportunidades narra la historia de Lucio Vasalle y sus desventuras en CCC como recién llegado, el comic, los dibujos sueltos y las animaciones están relacionados, todos cuentan pequeños pedazos de los personajes y de sus pasados, eres bienvenido a explorar todo esto y sacar tus propias conclusiones, si te fijas bien puede que encuentres algo que alguien no haya notado (: """ - } - manga.thumbnail_url = thumbnailUrl - manga.genre = "webcomic" - - return Observable.just(MangasPage(arrayListOf(manga), false)) - } - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = fetchPopularManga(1) - .map { it.mangas.first().apply { initialized = true } } - - // Chapters are listed old to new. - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "option.webcomic${if (lang == "en"){1}else {2}}-link" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.url = element.attr("data-webcomic-url") - chapter.name = element.text() - return chapter - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET(chapter.url, headers) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select(".webcomic-image img").mapIndexed { i, image -> Page(i, "", image.attr("src")) } - } - - companion object { - private const val thumbnailUrl = "https://i1.wp.com/vinnieveritas.com/wp-content/uploads/2016/02/CCC000.jpg" - } - - // unused - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = throw Exception("Not used") - - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - override fun popularMangaSelector(): String = throw Exception("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun searchMangaSelector(): String = throw Exception("Not used") - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - - override fun popularMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - override fun latestUpdatesSelector(): String = throw Exception("Not used") -} diff --git a/src/all/vinnieVeritas/src/eu/kanade/tachiyomi/extension/all/vinnieVeritas/vinnieVeritasFactory.kt b/src/all/vinnieVeritas/src/eu/kanade/tachiyomi/extension/all/vinnieVeritas/vinnieVeritasFactory.kt deleted file mode 100755 index 1b92fc4bb..000000000 --- a/src/all/vinnieVeritas/src/eu/kanade/tachiyomi/extension/all/vinnieVeritas/vinnieVeritasFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.vinnieVeritas - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class vinnieVeritasFactory : SourceFactory { - - override fun createSources(): List<Source> = - listOf( - vinnieVeritas("en"), - vinnieVeritas("es") - ) -} diff --git a/src/ar/andromedascans/AndroidManifest.xml b/src/ar/andromedascans/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ar/andromedascans/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ar/andromedascans/build.gradle b/src/ar/andromedascans/build.gradle deleted file mode 100644 index 6c2b4e28d..000000000 --- a/src/ar/andromedascans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'AndromedaScans' - pkgNameSuffix = 'ar.andromedascans' - extClass = '.AndromedaScans' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ar/andromedascans/res/mipmap-hdpi/ic_launcher.png b/src/ar/andromedascans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 3270a284d..000000000 Binary files a/src/ar/andromedascans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/andromedascans/res/mipmap-mdpi/ic_launcher.png b/src/ar/andromedascans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index 77bec6d1a..000000000 Binary files a/src/ar/andromedascans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/andromedascans/res/mipmap-xhdpi/ic_launcher.png b/src/ar/andromedascans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index e3ecfc492..000000000 Binary files a/src/ar/andromedascans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/andromedascans/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/andromedascans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index cda525643..000000000 Binary files a/src/ar/andromedascans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/andromedascans/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/andromedascans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 40aa17f5b..000000000 Binary files a/src/ar/andromedascans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/andromedascans/res/web_hi_res_512.png b/src/ar/andromedascans/res/web_hi_res_512.png deleted file mode 100755 index 7dc236422..000000000 Binary files a/src/ar/andromedascans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ar/andromedascans/src/eu/kanade/tachiyomi/extension/ar/andromedascans/AndromedaScans.kt b/src/ar/andromedascans/src/eu/kanade/tachiyomi/extension/ar/andromedascans/AndromedaScans.kt deleted file mode 100644 index d14ad740c..000000000 --- a/src/ar/andromedascans/src/eu/kanade/tachiyomi/extension/ar/andromedascans/AndromedaScans.kt +++ /dev/null @@ -1,89 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.andromedascans - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.MediaType -import okhttp3.Request -import okhttp3.RequestBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class AndromedaScans : ParsedHttpSource() { - override val name = "AndromedaScans" - - override val baseUrl = "https://andromedax.net" - - override val supportsLatest = true - - override val lang = "ar" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/projects", headers) - - override fun popularMangaSelector() = "div.flexbox2-content" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - title = element.select("a").attr("title") - thumbnail_url = element.select("img").attr("abs:src").substringBeforeLast("resize") - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/$page", headers) - - override fun latestUpdatesSelector() = "div.flexbox3-item" - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = "[rel=next]" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val type = "application/x-www-form-urlencoded; charset=UTF-8" - val body = RequestBody.create(MediaType.parse(type), "action=data_fetch&keyword=$query") - - return POST("$baseUrl/wp-admin/admin-ajax.php", headers, body) - } - - override fun searchMangaSelector() = "div.searchbox" - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - author = document.select("ul.series-infolist span")[3].text() - genre = document.select("div.series-genres > a[rel=tag]").joinToString { it.text() } - description = document.select("div.series-synops").text().trim() - } - - override fun chapterListSelector() = "ul.series-chapterlist > li" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("span").first().ownText() - date_upload = dateFormat.parse(element.select("span.date").text())?.time ?: 0 - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("MMMM dd, yyyy", Locale.US) - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("noscript > img:not([alt=Andromeda Scans])").mapIndexed { i, element -> - Page(i, "", element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun getFilterList() = FilterList() -} diff --git a/src/ar/gmanga/AndroidManifest.xml b/src/ar/gmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ar/gmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ar/gmanga/build.gradle b/src/ar/gmanga/build.gradle deleted file mode 100644 index c75dbb096..000000000 --- a/src/ar/gmanga/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'GMANGA' - pkgNameSuffix = 'ar.gmanga' - extClass = '.Gmanga' - extVersionCode = 4 - libVersion = '1.2' - containsNsfw = false -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ar/gmanga/res/mipmap-hdpi/ic_launcher.png b/src/ar/gmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a27c4d8a6..000000000 Binary files a/src/ar/gmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/gmanga/res/mipmap-mdpi/ic_launcher.png b/src/ar/gmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 90b5adf95..000000000 Binary files a/src/ar/gmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/gmanga/res/mipmap-xhdpi/ic_launcher.png b/src/ar/gmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 49d5b42dc..000000000 Binary files a/src/ar/gmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/gmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/gmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1283009d9..000000000 Binary files a/src/ar/gmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/gmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/gmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f17cd73d4..000000000 Binary files a/src/ar/gmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/gmanga/res/web_hi_res_512.png b/src/ar/gmanga/res/web_hi_res_512.png deleted file mode 100644 index 1d0cff9dd..000000000 Binary files a/src/ar/gmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/Gmanga.kt b/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/Gmanga.kt deleted file mode 100644 index 020b97c59..000000000 --- a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/Gmanga.kt +++ /dev/null @@ -1,184 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.gmanga - -import android.support.v7.preference.PreferenceScreen -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.nullString -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_CHAPTER_LISTING -import eu.kanade.tachiyomi.extension.ar.gmanga.GmangaPreferences.Companion.PREF_CHAPTER_LISTING_SHOW_POPULAR -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response - -class Gmanga : ConfigurableSource, HttpSource() { - - private val domain: String = "gmanga.me" - - override val baseUrl: String = "https://$domain" - - override val lang: String = "ar" - - override val name: String = "GMANGA" - - override val supportsLatest: Boolean = true - - private val gson = Gson() - - private val preferences = GmangaPreferences(id) - - private val rateLimitInterceptor = RateLimitInterceptor(4) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor) - .build() - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", USER_AGENT) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) = preferences.setupPreferenceScreen(screen) - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) = preferences.setupPreferenceScreen(screen) - - override fun chapterListRequest(manga: SManga): Request { - val mangaId = manga.url.substringAfterLast("/") - return GET("$baseUrl/api/mangas/$mangaId/releases", headers) - } - - @ExperimentalStdlibApi - override fun chapterListParse(response: Response): List<SChapter> { - val data = decryptResponse(response) - - val chapters: List<JsonArray> = buildList { - val allChapters = data["rows"][0]["rows"].asJsonArray.map { it.asJsonArray } - - when (preferences.getString(PREF_CHAPTER_LISTING)) { - PREF_CHAPTER_LISTING_SHOW_POPULAR -> addAll( - allChapters.groupBy { it.asJsonArray[6].asFloat } - .map { (_: Float, versions: List<JsonArray>) -> versions.maxByOrNull { it[4].asLong }!! } - ) - else -> addAll(allChapters) - } - } - - return chapters.map { - SChapter.create().apply { - chapter_number = it[6].asFloat - - val chapterName = it[8].asString.let { if (it.trim() != "") " - $it" else "" } - - url = "/r/${it[0]}" - name = "${chapter_number.let { if (it % 1 > 0) it else it.toInt() }}$chapterName" - date_upload = it[3].asLong * 1000 - scanlator = it[10].asString - } - } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage { - val data = gson.fromJson<JsonObject>(response.asJsoup().select(".js-react-on-rails-component").html()) - return MangasPage( - data["mangaDataAction"]["newMangas"].asJsonArray.map { - SManga.create().apply { - url = "/mangas/${it["id"].asString}" - title = it["title"].asString - - thumbnail_url = it["cover"].nullString?.let { coverFileName -> - val thumbnail = "medium_${coverFileName.substringBeforeLast(".")}.webp" - "https://media.$domain/uploads/manga/cover/${it["id"].asString}/$thumbnail" - } - } - }, - false - ) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/mangas/latest", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val data = gson.fromJson<JsonObject>(response.asJsoup().select(".js-react-on-rails-component").html()) - val mangaData = data["mangaDataAction"]["mangaData"].asJsonObject - return SManga.create().apply { - description = mangaData["summary"].nullString ?: "" - artist = mangaData["artists"].asJsonArray.joinToString(", ") { it.asJsonObject["name"].asString } - author = mangaData["authors"].asJsonArray.joinToString(", ") { it.asJsonObject["name"].asString } - genre = mangaData["categories"].asJsonArray.joinToString(", ") { it.asJsonObject["name"].asString } - } - } - - override fun pageListParse(response: Response): List<Page> { - val url = response.request().url().toString() - val data = gson.fromJson<JsonObject>(response.asJsoup().select(".js-react-on-rails-component").html()) - val releaseData = data["readerDataAction"]["readerData"]["release"].asJsonObject - - val hasWebP = releaseData["webp_pages"].asJsonArray.size() > 0 - return releaseData[if (hasWebP) "webp_pages" else "pages"].asJsonArray.map { it.asString }.mapIndexed { index, pageUri -> - Page( - index, - "$url#page_$index", - "https://media.$domain/uploads/releases/${releaseData["storage_key"].asString}/mq${if (hasWebP) "_webp" else ""}/$pageUri" - ) - } - } - - override fun popularMangaParse(response: Response) = searchMangaParse(response) - - override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", getFilterList()) - - override fun searchMangaParse(response: Response): MangasPage { - val data = decryptResponse(response) - val mangas = data["mangas"].asJsonArray - return MangasPage( - mangas.asJsonArray.map { - SManga.create().apply { - url = "/mangas/${it["id"].asString}" - title = it["title"].asString - val thumbnail = "medium_${it["cover"].asString.substringBeforeLast(".")}.webp" - thumbnail_url = "https://media.$domain/uploads/manga/cover/${it["id"].asString}/$thumbnail" - } - }, - mangas.size() == 50 - ) - } - - private fun decryptResponse(response: Response): JsonObject { - val encryptedData = gson.fromJson<JsonObject>(response.body()!!.string())["data"].asString - val decryptedData = decrypt(encryptedData) - return gson.fromJson(decryptedData) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GmangaFilters.buildSearchPayload(page, query, if (filters.isEmpty()) getFilterList() else filters).let { - val body = RequestBody.create(MEDIA_TYPE, it.toString()) - POST("$baseUrl/api/mangas/search", headers, body) - } - } - - override fun getFilterList() = GmangaFilters.getFilterList() - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36" - private val MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8") - } -} diff --git a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaCryptoUtils.kt b/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaCryptoUtils.kt deleted file mode 100644 index ad20bf9f8..000000000 --- a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaCryptoUtils.kt +++ /dev/null @@ -1,45 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.gmanga - -import android.util.Base64 -import java.security.MessageDigest -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -fun decrypt(responseData: String): String { - val enc = responseData.split("|") - val secretKey = enc[3].sha256().hexStringToByteArray() - - return enc[0].aesDecrypt(secretKey, enc[2]) -} - -private fun String.hexStringToByteArray(): ByteArray { - val len = this.length - val data = ByteArray(len / 2) - var i = 0 - while (i < len) { - data[i / 2] = ( - (Character.digit(this[i], 16) shl 4) + - Character.digit(this[i + 1], 16) - ).toByte() - i += 2 - } - return data -} - -private fun String.sha256(): String { - return MessageDigest - .getInstance("SHA-256") - .digest(this.toByteArray()) - .fold("", { str, it -> str + "%02x".format(it) }) -} - -private fun String.aesDecrypt(secretKey: ByteArray, ivString: String): String { - val c = Cipher.getInstance("AES/CBC/PKCS5Padding") - val sk = SecretKeySpec(secretKey, "AES") - val iv = IvParameterSpec(Base64.decode(ivString.toByteArray(Charsets.UTF_8), Base64.DEFAULT)) - c.init(Cipher.DECRYPT_MODE, sk, iv) - - val byteStr = Base64.decode(this.toByteArray(Charsets.UTF_8), Base64.DEFAULT) - return String(c.doFinal(byteStr)) -} diff --git a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaFilters.kt b/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaFilters.kt deleted file mode 100644 index 6c816697c..000000000 --- a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaFilters.kt +++ /dev/null @@ -1,334 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.gmanga - -import android.annotation.SuppressLint -import com.github.salomonbrys.kotson.addAll -import com.github.salomonbrys.kotson.addProperty -import com.google.gson.JsonArray -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import java.lang.Exception -import java.text.ParseException -import java.text.SimpleDateFormat - -class GmangaFilters() { - - companion object { - - fun getFilterList() = FilterList( - MangaTypeFilter(), - OneShotFilter(), - StoryStatusFilter(), - TranslationStatusFilter(), - ChapterCountFilter(), - DateRangeFilter(), - CategoryFilter() - ) - - fun buildSearchPayload(page: Int, query: String = "", filters: FilterList): JsonObject { - val mangaTypeFilter = filters.findInstance<MangaTypeFilter>()!! - val oneShotFilter = filters.findInstance<OneShotFilter>()!! - val storyStatusFilter = filters.findInstance<StoryStatusFilter>()!! - val translationStatusFilter = filters.findInstance<TranslationStatusFilter>()!! - val chapterCountFilter = filters.findInstance<ChapterCountFilter>()!! - val dateRangeFilter = filters.findInstance<DateRangeFilter>()!! - val categoryFilter = filters.findInstance<CategoryFilter>()!! - - return JsonObject().apply { - - oneShotFilter.state.first().let { - when { - it.isIncluded() -> addProperty("oneshot", true) - it.isExcluded() -> addProperty("oneshot", false) - else -> addProperty("oneshot", JsonNull.INSTANCE) - } - } - - addProperty("title", query) - addProperty("page", page) - addProperty( - "manga_types", - JsonObject().apply { - - addProperty( - "include", - JsonArray().apply { - addAll(mangaTypeFilter.state.filter { it.isIncluded() }.map { it.id }) - } - ) - - addProperty( - "exclude", - JsonArray().apply { - addAll(mangaTypeFilter.state.filter { it.isExcluded() }.map { it.id }) - } - ) - } - ) - addProperty( - "story_status", - JsonObject().apply { - - addProperty( - "include", - JsonArray().apply { - addAll(storyStatusFilter.state.filter { it.isIncluded() }.map { it.id }) - } - ) - - addProperty( - "exclude", - JsonArray().apply { - addAll(storyStatusFilter.state.filter { it.isExcluded() }.map { it.id }) - } - ) - } - ) - addProperty( - "translation_status", - JsonObject().apply { - - addProperty( - "include", - JsonArray().apply { - addAll(translationStatusFilter.state.filter { it.isIncluded() }.map { it.id }) - } - ) - - addProperty( - "exclude", - JsonArray().apply { - addAll(translationStatusFilter.state.filter { it.isExcluded() }.map { it.id }) - } - ) - } - ) - addProperty( - "categories", - JsonObject().apply { - - addProperty( - "include", - JsonArray().apply { - add(JsonNull.INSTANCE) // always included, maybe to avoid shifting index in the backend - addAll(categoryFilter.state.filter { it.isIncluded() }.map { it.id }) - } - ) - - addProperty( - "exclude", - JsonArray().apply { - addAll(categoryFilter.state.filter { it.isExcluded() }.map { it.id }) - } - ) - } - ) - addProperty( - "chapters", - JsonObject().apply { - - addPropertyFromValidatingTextFilter( - chapterCountFilter.state.first { - it.id == FILTER_ID_MIN_CHAPTER_COUNT - }, - "min", - ERROR_INVALID_MIN_CHAPTER_COUNT, - "" - ) - - addPropertyFromValidatingTextFilter( - chapterCountFilter.state.first { - it.id == FILTER_ID_MAX_CHAPTER_COUNT - }, - "max", - ERROR_INVALID_MAX_CHAPTER_COUNT, - "" - ) - } - ) - addProperty( - "dates", - JsonObject().apply { - - addPropertyFromValidatingTextFilter( - dateRangeFilter.state.first { - it.id == FILTER_ID_START_DATE - }, - "start", - ERROR_INVALID_START_DATE - ) - - addPropertyFromValidatingTextFilter( - dateRangeFilter.state.first { - it.id == FILTER_ID_END_DATE - }, - "end", - ERROR_INVALID_END_DATE - ) - } - ) - } - } - - // filter IDs - private const val FILTER_ID_ONE_SHOT = "oneshot" - private const val FILTER_ID_START_DATE = "start" - private const val FILTER_ID_END_DATE = "end" - private const val FILTER_ID_MIN_CHAPTER_COUNT = "min" - private const val FILTER_ID_MAX_CHAPTER_COUNT = "max" - - // error messages - private const val ERROR_INVALID_START_DATE = "تاريخ بداية غير صالح" - private const val ERROR_INVALID_END_DATE = " تاريخ نهاية غير صالح" - private const val ERROR_INVALID_MIN_CHAPTER_COUNT = "الحد الأدنى لعدد الفصول غير صالح" - private const val ERROR_INVALID_MAX_CHAPTER_COUNT = "الحد الأقصى لعدد الفصول غير صالح" - - private class MangaTypeFilter() : Filter.Group<TagFilter>( - "الأصل", - listOf( - TagFilter("1", "يابانية", TriState.STATE_INCLUDE), - TagFilter("2", "كورية", TriState.STATE_INCLUDE), - TagFilter("3", "صينية", TriState.STATE_INCLUDE), - TagFilter("4", "عربية", TriState.STATE_INCLUDE), - TagFilter("5", "كوميك", TriState.STATE_INCLUDE), - TagFilter("6", "هواة", TriState.STATE_INCLUDE), - TagFilter("7", "إندونيسية", TriState.STATE_INCLUDE), - TagFilter("8", "روسية", TriState.STATE_INCLUDE), - ) - ) - - private class OneShotFilter() : Filter.Group<TagFilter>( - "ونشوت؟", - listOf( - TagFilter(FILTER_ID_ONE_SHOT, "نعم", TriState.STATE_EXCLUDE) - ) - ) - - private class StoryStatusFilter() : Filter.Group<TagFilter>( - "حالة القصة", - listOf( - TagFilter("2", "مستمرة"), - TagFilter("3", "منتهية") - ) - ) - - private class TranslationStatusFilter() : Filter.Group<TagFilter>( - "حالة الترجمة", - listOf( - TagFilter("0", "منتهية"), - TagFilter("1", "مستمرة"), - TagFilter("2", "متوقفة"), - TagFilter("3", "غير مترجمة", TriState.STATE_EXCLUDE), - ) - ) - - private class ChapterCountFilter() : Filter.Group<IntFilter>( - "عدد الفصول", - listOf( - IntFilter(FILTER_ID_MIN_CHAPTER_COUNT, "على الأقل"), - IntFilter(FILTER_ID_MAX_CHAPTER_COUNT, "على الأكثر") - ) - ) - - private class DateRangeFilter() : Filter.Group<DateFilter>( - "تاريخ النشر", - listOf( - DateFilter(FILTER_ID_START_DATE, "تاريخ النشر"), - DateFilter(FILTER_ID_END_DATE, "تاريخ الإنتهاء") - ) - ) - - private class CategoryFilter() : Filter.Group<TagFilter>( - "التصنيفات", - listOf( - TagFilter("1", "إثارة"), - TagFilter("2", "أكشن"), - TagFilter("3", "الحياة المدرسية"), - TagFilter("4", "الحياة اليومية"), - TagFilter("5", "آليات"), - TagFilter("6", "تاريخي"), - TagFilter("7", "تراجيدي"), - TagFilter("8", "جوسيه"), - TagFilter("9", "حربي"), - TagFilter("10", "خيال"), - TagFilter("11", "خيال علمي"), - TagFilter("12", "دراما"), - TagFilter("13", "رعب"), - TagFilter("14", "رومانسي"), - TagFilter("15", "رياضة"), - TagFilter("16", "ساموراي"), - TagFilter("17", "سحر"), - TagFilter("18", "سينين"), - TagFilter("19", "شوجو"), - TagFilter("20", "شونين"), - TagFilter("21", "عنف"), - TagFilter("22", "غموض"), - TagFilter("23", "فنون قتال"), - TagFilter("24", "قوى خارقة"), - TagFilter("25", "كوميدي"), - TagFilter("26", "لعبة"), - TagFilter("27", "مسابقة"), - TagFilter("28", "مصاصي الدماء"), - TagFilter("29", "مغامرات"), - TagFilter("30", "موسيقى"), - TagFilter("31", "نفسي"), - TagFilter("32", "نينجا"), - TagFilter("33", "وحوش"), - TagFilter("34", "حريم"), - TagFilter("35", "راشد"), - TagFilter("38", "ويب-تون"), - TagFilter("39", "زمنكاني") - ) - ) - - private const val DATE_FILTER_PATTERN = "yyyy/MM/dd" - - @SuppressLint("SimpleDateFormat") - private val DATE_FITLER_FORMAT = SimpleDateFormat(DATE_FILTER_PATTERN).apply { - isLenient = false - } - - private fun SimpleDateFormat.isValid(date: String): Boolean { - return try { - this.parse(date) - true - } catch (e: ParseException) { - false - } - } - - private fun JsonObject.addPropertyFromValidatingTextFilter( - filter: ValidatingTextFilter, - property: String, - invalidErrorMessage: String, - default: String? = null - ) { - filter.let { - when { - it.state == "" -> if (default == null) { - addProperty(property, JsonNull.INSTANCE) - } else addProperty(property, default) - it.isValid() -> addProperty(property, it.state) - else -> throw Exception(invalidErrorMessage) - } - } - } - - private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T - - private class TagFilter(val id: String, name: String, state: Int = STATE_IGNORE) : Filter.TriState(name, state) - - private abstract class ValidatingTextFilter(name: String) : Filter.Text(name) { - abstract fun isValid(): Boolean - } - - private class DateFilter(val id: String, name: String) : ValidatingTextFilter("($DATE_FILTER_PATTERN) $name)") { - override fun isValid(): Boolean = DATE_FITLER_FORMAT.isValid(this.state) - } - - private class IntFilter(val id: String, name: String) : ValidatingTextFilter(name) { - override fun isValid(): Boolean = state.toIntOrNull() != null - } - } -} diff --git a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaPreferences.kt b/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaPreferences.kt deleted file mode 100644 index 76af1c357..000000000 --- a/src/ar/gmanga/src/eu/kanade/tachiyomi/extension/ar/gmanga/GmangaPreferences.kt +++ /dev/null @@ -1,88 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.gmanga - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class GmangaPreferences(id: Long) { - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - fun setupPreferenceScreen(screen: PreferenceScreen) { - - STRING_PREFERENCES.forEach { - val preference = ListPreference(screen.context).apply { - key = it.key - title = it.title - entries = it.entries() - entryValues = it.entryValues() - summary = "%s" - } - - if (!preferences.contains(it.key)) - preferences.edit().putString(it.key, it.default().key).apply() - - screen.addPreference(preference) - } - } - - fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - - STRING_PREFERENCES.forEach { - val preference = androidx.preference.ListPreference(screen.context).apply { - key = it.key - title = it.title - entries = it.entries() - entryValues = it.entryValues() - summary = "%s" - } - - if (!preferences.contains(it.key)) - preferences.edit().putString(it.key, it.default().key).apply() - - screen.addPreference(preference) - } - } - - fun getString(pref: StringPreference): String { - return preferences.getString(pref.key, pref.default().key)!! - } - - companion object { - - class StringPreferenceOption(val key: String, val title: String) - - class StringPreference( - val key: String, - val title: String, - private val options: List<StringPreferenceOption>, - private val defaultOptionIndex: Int = 0 - ) { - fun entries(): Array<String> = options.map { it.title }.toTypedArray() - fun entryValues(): Array<String> = options.map { it.key }.toTypedArray() - fun default(): StringPreferenceOption = options[defaultOptionIndex] - } - - // preferences - const val PREF_CHAPTER_LISTING_SHOW_ALL = "gmanga_gmanga_chapter_listing_show_all" - const val PREF_CHAPTER_LISTING_SHOW_POPULAR = "gmanga_chapter_listing_most_viewed" - - val PREF_CHAPTER_LISTING = StringPreference( - "gmanga_chapter_listing", - "كيفية عرض الفصل بقائمة الفصول", - listOf( - StringPreferenceOption(PREF_CHAPTER_LISTING_SHOW_POPULAR, "اختيار النسخة الأكثر مشاهدة"), - StringPreferenceOption(PREF_CHAPTER_LISTING_SHOW_ALL, "عرض جميع النسخ") - ) - ) - - private val STRING_PREFERENCES = listOf( - PREF_CHAPTER_LISTING - ) - } -} diff --git a/src/ar/mangaae/AndroidManifest.xml b/src/ar/mangaae/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ar/mangaae/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ar/mangaae/build.gradle b/src/ar/mangaae/build.gradle deleted file mode 100644 index 631b4f44b..000000000 --- a/src/ar/mangaae/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manga.ae' - pkgNameSuffix = 'ar.mangaae' - extClass = '.MangaAe' - extVersionCode = 7 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ar/mangaae/res/mipmap-hdpi/ic_launcher.png b/src/ar/mangaae/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b3b1d6bb9..000000000 Binary files a/src/ar/mangaae/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangaae/res/mipmap-mdpi/ic_launcher.png b/src/ar/mangaae/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e80c5a043..000000000 Binary files a/src/ar/mangaae/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangaae/res/mipmap-xhdpi/ic_launcher.png b/src/ar/mangaae/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6c37b9569..000000000 Binary files a/src/ar/mangaae/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangaae/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/mangaae/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c4a273f24..000000000 Binary files a/src/ar/mangaae/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangaae/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/mangaae/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7512d7255..000000000 Binary files a/src/ar/mangaae/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangaae/res/web_hi_res_512.png b/src/ar/mangaae/res/web_hi_res_512.png deleted file mode 100644 index 5fe3e4d1e..000000000 Binary files a/src/ar/mangaae/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ar/mangaae/src/eu/kanade/tachiyomi/extension/ar/mangaae/MangaAe.kt b/src/ar/mangaae/src/eu/kanade/tachiyomi/extension/ar/mangaae/MangaAe.kt deleted file mode 100644 index a2134a2c5..000000000 --- a/src/ar/mangaae/src/eu/kanade/tachiyomi/extension/ar/mangaae/MangaAe.kt +++ /dev/null @@ -1,164 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.mangaae - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class MangaAe : ParsedHttpSource() { - - override val name = "مانجا العرب" - - override val baseUrl = "https://mngaar.com" - - override val lang = "ar" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/75.0") - .add("Referer", baseUrl) - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/manga/page:$page", headers) - } - - override fun popularMangaNextPageSelector() = "div.pagination a:last-child:not(.active)" - - override fun popularMangaSelector() = "div.mangacontainer" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - val lazysrc = element.select("img").attr("data-pagespeed-lazy-src") - thumbnail_url = if (lazysrc.isNullOrEmpty()) { - element.select("img").attr("src") - } else { - lazysrc - } - element.select("div.mangacontainer a.manga")[0].let { - title = it.text() - setUrlWithoutDomain(it.attr("abs:href")) - } - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesSelector(): String = "div.popular-manga-container" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - val lazysrc = element.select("img").attr("data-pagespeed-lazy-src") - thumbnail_url = if (lazysrc.isNullOrEmpty()) { - element.select("img").attr("src") - } else { - lazysrc - } - setUrlWithoutDomain(element.select("a:has(img)").attr("href")) - title = element.select("a").last().text() - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = "$baseUrl/manga/search:$query|page:$page" - filters.forEach { filter -> - when (filter) { - is OrderByFilter -> { - if (filter.state != 0) { - url += "|order:${filter.toUriPart()}" - } - } - } - } - url += "|arrange:minus" - return GET(HttpUrl.parse(url)!!.newBuilder().build().toString(), headers) - } - - override fun searchMangaSelector(): String = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Manga summary page - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select("div.indexcontainer").first() - title = infoElement.select("h1.EnglishName").text().removeSurrounding("(", ")") - author = infoElement.select("div.manga-details-author h4")[0].text() - artist = author - status = parseStatus(infoElement.select("div.manga-details-extended h4")[1].text()) - genre = infoElement.select("div.manga-details-extended a[href*=tag]").joinToString(", ") { it.text() } - description = infoElement.select("div.manga-details-extended h4")[2].text() - thumbnail_url = infoElement.select("img.manga-cover").attr("src") - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("مستمرة") -> SManga.ONGOING - status.contains("مكتملة") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - override fun chapterListSelector() = "ul.new-manga-chapters > li" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - element.select("a").let { - // use full pages for easier links - chapter.setUrlWithoutDomain(it.attr("href").removeSuffix("/1/") + "/0/full") - chapter.name = "\u061C" + it.text() // Add unicode ARABIC LETTER MARK to ensure all titles are right to left - } - return chapter - } - - // Pages - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - document.select("div#showchaptercontainer img")?.forEach { - pages.add(Page(pages.size, "", it.attr("src"))) - } - return pages - } - - override fun imageUrlParse(document: Document): String = throw Exception("Not used") - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private class OrderByFilter : UriPartFilter( - "الترتيب حسب", - arrayOf( - Pair("اختيار", ""), - Pair("اسم المانجا", "english_name"), - Pair("تاريخ النشر", "release_date"), - Pair("عدد الفصول", "chapter_count"), - Pair("الحالة", "status") - ) - ) - - override fun getFilterList() = FilterList( - OrderByFilter() - ) -} diff --git a/src/ar/mangalink/AndroidManifest.xml b/src/ar/mangalink/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ar/mangalink/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ar/mangalink/build.gradle b/src/ar/mangalink/build.gradle deleted file mode 100644 index cb9ccebcb..000000000 --- a/src/ar/mangalink/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaLink' - pkgNameSuffix = 'ar.mangalink' - extClass = '.MangaLink' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ar/mangalink/res/mipmap-hdpi/ic_launcher.png b/src/ar/mangalink/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b577e79c9..000000000 Binary files a/src/ar/mangalink/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangalink/res/mipmap-mdpi/ic_launcher.png b/src/ar/mangalink/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e65846743..000000000 Binary files a/src/ar/mangalink/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangalink/res/mipmap-xhdpi/ic_launcher.png b/src/ar/mangalink/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1e1b69b64..000000000 Binary files a/src/ar/mangalink/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangalink/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/mangalink/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b3aaf36b6..000000000 Binary files a/src/ar/mangalink/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangalink/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/mangalink/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 902a843da..000000000 Binary files a/src/ar/mangalink/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangalink/res/web_hi_res_512.png b/src/ar/mangalink/res/web_hi_res_512.png deleted file mode 100644 index 1dbcbf5f1..000000000 Binary files a/src/ar/mangalink/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ar/mangalink/src/eu/kanade/tachiyomi/extension/ar/mangalink/MangaLink.kt b/src/ar/mangalink/src/eu/kanade/tachiyomi/extension/ar/mangalink/MangaLink.kt deleted file mode 100644 index 5e792fe43..000000000 --- a/src/ar/mangalink/src/eu/kanade/tachiyomi/extension/ar/mangalink/MangaLink.kt +++ /dev/null @@ -1,105 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.mangalink - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class MangaLink : ParsedHttpSource() { - - override val name = "MangaLink" - - override val baseUrl = "https://mangalink.org" - - override val lang = "ar" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/mangas?page=$page") - } - - override fun popularMangaSelector() = "div.card" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a:has(h6)").let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - thumbnail_url = element.select("img").attr("abs:data-src") - } - } - - override fun popularMangaNextPageSelector() = "a[rel=next]" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/mangas?page=$page&query=$query", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - document.select("div.card").first().let { info -> - title = info.select("h1").text() - genre = info.select("span.d-flex a.btn").joinToString { it.text() } - description = info.select("p.card-text").text() - thumbnail_url = info.select("img").attr("abs:src") - } - } - } - - // Chapters - - override fun chapterListSelector() = "div.card-body > a.btn" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - name = "# ${element.text()}" - setUrlWithoutDomain(element.attr("href")) - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div#content img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:data-src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/ar/mangazen/AndroidManifest.xml b/src/ar/mangazen/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ar/mangazen/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ar/mangazen/build.gradle b/src/ar/mangazen/build.gradle deleted file mode 100644 index e55e74a6b..000000000 --- a/src/ar/mangazen/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaZen' - pkgNameSuffix = 'ar.mangazen' - extClass = '.MangaZen' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ar/mangazen/res/mipmap-hdpi/ic_launcher.png b/src/ar/mangazen/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index f0f45cb6b..000000000 Binary files a/src/ar/mangazen/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangazen/res/mipmap-mdpi/ic_launcher.png b/src/ar/mangazen/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index d1e28e8bf..000000000 Binary files a/src/ar/mangazen/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangazen/res/mipmap-xhdpi/ic_launcher.png b/src/ar/mangazen/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 2de05349e..000000000 Binary files a/src/ar/mangazen/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangazen/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/mangazen/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 4b0a43847..000000000 Binary files a/src/ar/mangazen/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangazen/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/mangazen/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 47c9f27a5..000000000 Binary files a/src/ar/mangazen/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/mangazen/res/web_hi_res_512.png b/src/ar/mangazen/res/web_hi_res_512.png deleted file mode 100755 index 1bb9d873b..000000000 Binary files a/src/ar/mangazen/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ar/mangazen/src/eu/kanade/tachiyomi/extension/ar/mangazen/MangaZen.kt b/src/ar/mangazen/src/eu/kanade/tachiyomi/extension/ar/mangazen/MangaZen.kt deleted file mode 100644 index 7c6586602..000000000 --- a/src/ar/mangazen/src/eu/kanade/tachiyomi/extension/ar/mangazen/MangaZen.kt +++ /dev/null @@ -1,86 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.mangazen - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaZen : ParsedHttpSource() { - - override val baseUrl = "https://manga-zen.com" - - override val lang = "ar" - - override val name = "MangaZen" - - override val supportsLatest = true - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/قائمة-المانجا/page/$page", headers) - - override fun popularMangaSelector() = "a[title][alt]" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.attr("title") - thumbnail_url = element.select("img").attr("abs:src").substringBeforeLast("?quality") - } - - override fun popularMangaNextPageSelector() = "div.pagination:not(:has(span:last-child))" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/قائمة-المانجا/page/$page/?order=update", headers) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/قائمة-المانجا/page/$page/?title=$query", headers) - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - description = document.select("div.desc").text() - genre = document.select("div.genre-info > a[itemprop=genre]").joinToString { it.text() } - status = parseStatus(document.select("div.spe").first().text()) - } - - private fun parseStatus(status: String) = when { - status.contains("مستمر") -> SManga.ONGOING - else -> SManga.COMPLETED - } - - override fun chapterListSelector() = "div.epsleft" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("a").text().trim() - date_upload = dateFormat.parse(element.select("span.date").text().trim())?.time ?: 0 - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("MMM dd, yyyy", Locale("ar")) - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("noscript > img#imagech").mapIndexed { i, element -> - Page(i, "", element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun getFilterList() = FilterList() -} diff --git a/src/ar/shqqaa/AndroidManifest.xml b/src/ar/shqqaa/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ar/shqqaa/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ar/shqqaa/build.gradle b/src/ar/shqqaa/build.gradle deleted file mode 100644 index 54e2289e5..000000000 --- a/src/ar/shqqaa/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Shqqaa Manga' - pkgNameSuffix = 'ar.shqqaa' - extClass = '.Shqqaa' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ar/shqqaa/res/mipmap-hdpi/ic_launcher.png b/src/ar/shqqaa/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 92a98f26b..000000000 Binary files a/src/ar/shqqaa/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/shqqaa/res/mipmap-mdpi/ic_launcher.png b/src/ar/shqqaa/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 65d7042fc..000000000 Binary files a/src/ar/shqqaa/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/shqqaa/res/mipmap-xhdpi/ic_launcher.png b/src/ar/shqqaa/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1beb8ec4b..000000000 Binary files a/src/ar/shqqaa/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/shqqaa/res/mipmap-xxhdpi/ic_launcher.png b/src/ar/shqqaa/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b3990c6f1..000000000 Binary files a/src/ar/shqqaa/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/shqqaa/res/mipmap-xxxhdpi/ic_launcher.png b/src/ar/shqqaa/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 079f36bec..000000000 Binary files a/src/ar/shqqaa/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ar/shqqaa/res/web_hi_res_512.png b/src/ar/shqqaa/res/web_hi_res_512.png deleted file mode 100644 index 999e42808..000000000 Binary files a/src/ar/shqqaa/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ar/shqqaa/src/eu/kanade/tachiyomi/extension/ar/shqqaa/Shqqaa.kt b/src/ar/shqqaa/src/eu/kanade/tachiyomi/extension/ar/shqqaa/Shqqaa.kt deleted file mode 100644 index e5d9130a5..000000000 --- a/src/ar/shqqaa/src/eu/kanade/tachiyomi/extension/ar/shqqaa/Shqqaa.kt +++ /dev/null @@ -1,124 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.shqqaa - -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class Shqqaa : ParsedHttpSource() { - - override val name = "مانجا شقاع" - - override val baseUrl = "https://www.shqqaa.com" - - override val lang = "ar" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/manga", headers) - } - - override fun popularMangaSelector() = "div.card" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").first().attr("data-src") - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title").split(", ")[0] - } - return manga - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/manga/chapters/", headers) - } - - override fun latestUpdatesSelector(): String = "div.row > div.col-xl-3" - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain("${it.attr("href").substringBeforeLast('/')}/") - manga.title = element.select("small").first().text().split(", ")[0] - } - manga.thumbnail_url = element.select("img").first().attr("data-src") - return manga - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = fetchPopularManga(1) - .map { mp -> MangasPage(mp.mangas.filter { it.title.contains(query, ignoreCase = true) }, false) } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - - override fun searchMangaSelector(): String = throw Exception("Not Used") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not Used") - - override fun searchMangaNextPageSelector() = throw Exception("Not Used") - - // Manga summary page - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.col-sm-12") - val mangaInfo = infoElement[1] - val manga = SManga.create() - manga.title = mangaInfo.select("small.text-muted")[1].ownText().split(", ")[0] - manga.author = null - val status = mangaInfo.select("span.badge").first().ownText() - manga.status = parseStatus(status) - manga.genre = null - manga.description = infoElement.first().select(".text-muted").first().ownText() - manga.thumbnail_url = mangaInfo.select("img").attr("data-src") - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("مستمر") -> SManga.ONGOING - status.contains("منتهي") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - override fun chapterListSelector() = "a.m-1" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - element.select("a").let { - chapter.setUrlWithoutDomain(it.attr("href")) - chapter.name = it.text() - } - chapter.date_upload = 0 - return chapter - } - - // Pages - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - document.select("div.img-manga img").forEach { - pages.add(Page(pages.size, "", it.attr("src"))) - } - return pages - } - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/ca/fansubscat/AndroidManifest.xml b/src/ca/fansubscat/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ca/fansubscat/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ca/fansubscat/build.gradle b/src/ca/fansubscat/build.gradle deleted file mode 100644 index 6c723cb91..000000000 --- a/src/ca/fansubscat/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Fansubs.cat' - pkgNameSuffix = 'ca.fansubscat' - extClass = '.FansubsCat' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8e0468783..000000000 Binary files a/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e0702ecea..000000000 Binary files a/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b0027ad57..000000000 Binary files a/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2424e711f..000000000 Binary files a/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ccc47ea5f..000000000 Binary files a/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/web_hi_res_512.png b/src/ca/fansubscat/res/web_hi_res_512.png deleted file mode 100644 index 8e8555a10..000000000 Binary files a/src/ca/fansubscat/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt b/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt deleted file mode 100644 index 2f9d34e86..000000000 --- a/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt +++ /dev/null @@ -1,160 +0,0 @@ -package eu.kanade.tachiyomi.extension.ca.fansubscat - -import com.github.salomonbrys.kotson.float -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.long -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable - -class FansubsCat : HttpSource() { - - override val name = "Fansubs.cat" - - override val baseUrl = "https://manga.fansubs.cat" - - override val lang = "ca" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Tachiyomi/FansubsCat/${BuildConfig.VERSION_NAME}") - - override val client: OkHttpClient = network.client - - private val gson = Gson() - - private val apiBaseUrl = "https://api.fansubs.cat" - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - val mangas = jsonObject["result"].asJsonArray.map { json -> - SManga.create().apply { - url = json["slug"].string - title = json["name"].string - thumbnail_url = json["thumbnail_url"].string - author = json["author"].nullString - description = json["synopsis"].nullString - status = json["status"].string.toStatus() - genre = json["genres"].nullString - } - } - - return MangasPage(mangas, mangas.size >= 20) - } - - private fun parseChapterListFromJson(response: Response): List<SChapter> { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - return jsonObject["result"].asJsonArray.map { json -> - SChapter.create().apply { - url = json["id"].string - name = json["title"].string - chapter_number = json["number"].float - scanlator = json["fansub"].string - date_upload = json["created"].long - } - } - } - - private fun parsePageListFromJson(response: Response): List<Page> { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - return jsonObject["result"].asJsonArray.mapIndexed { i, it -> - Page(i, it["url"].asString, it["url"].asString) - } - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/popular/$page", headers) - } - - override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/recent/$page", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$apiBaseUrl/manga/search/$page")!!.newBuilder() - .addQueryParameter("query", query) - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Details - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = - client.newCall(apiMangaDetailsRequest(manga)).asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/${manga.url}", headers) - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET("$apiBaseUrl/manga/details/${manga.url.substringAfterLast('/')}", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - return SManga.create().apply { - url = jsonObject["result"]["slug"].string - title = jsonObject["result"]["name"].string - thumbnail_url = jsonObject["result"]["thumbnail_url"].string - author = jsonObject["result"]["author"].nullString - description = jsonObject["result"]["synopsis"].nullString - status = jsonObject["result"]["status"].string.toStatus() - genre = jsonObject["result"]["genres"].nullString - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("finished", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request = GET("$apiBaseUrl/manga/chapters/${manga.url.substringAfterLast('/')}", headers) - - override fun chapterListParse(response: Response): List<SChapter> = parseChapterListFromJson(response) - - // Pages - - override fun pageListRequest(chapter: SChapter): Request = GET("$apiBaseUrl/manga/pages/${chapter.url}", headers) - - override fun pageListParse(response: Response): List<Page> = parsePageListFromJson(response) - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/de/mangatube/AndroidManifest.xml b/src/de/mangatube/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/de/mangatube/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/de/mangatube/build.gradle b/src/de/mangatube/build.gradle deleted file mode 100644 index 0006524e5..000000000 --- a/src/de/mangatube/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manga Tube' - pkgNameSuffix = 'de.mangatube' - extClass = '.MangaTube' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/de/mangatube/res/mipmap-hdpi/ic_launcher.png b/src/de/mangatube/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e391b1687..000000000 Binary files a/src/de/mangatube/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/mangatube/res/mipmap-mdpi/ic_launcher.png b/src/de/mangatube/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index cbb42e4cd..000000000 Binary files a/src/de/mangatube/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/mangatube/res/mipmap-xhdpi/ic_launcher.png b/src/de/mangatube/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 209d0c612..000000000 Binary files a/src/de/mangatube/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/mangatube/res/mipmap-xxhdpi/ic_launcher.png b/src/de/mangatube/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index dc38d2e52..000000000 Binary files a/src/de/mangatube/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/mangatube/res/mipmap-xxxhdpi/ic_launcher.png b/src/de/mangatube/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 61523b112..000000000 Binary files a/src/de/mangatube/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/mangatube/res/web_hi_res_512.png b/src/de/mangatube/res/web_hi_res_512.png deleted file mode 100644 index 09a3f91d7..000000000 Binary files a/src/de/mangatube/res/web_hi_res_512.png and /dev/null differ diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt deleted file mode 100644 index 7d6b72314..000000000 --- a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt +++ /dev/null @@ -1,182 +0,0 @@ -package eu.kanade.tachiyomi.extension.de.mangatube - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MangaTube : ParsedHttpSource() { - - override val name = "Manga Tube" - - override val baseUrl = "https://manga-tube.me" - - override val lang = "de" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build() - - private val xhrHeaders: Headers = headersBuilder().add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").build() - - private val gson by lazy { Gson() } - - // Popular - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - parseMangaFromJson(response, page < 96) - } - } - - override fun popularMangaRequest(page: Int): Request { - val rbodyContent = "action=load_series_list_entries¶meter%5Bpage%5D=$page¶meter%5Bletter%5D=¶meter%5Bsortby%5D=popularity¶meter%5Border%5D=asc" - return POST("$baseUrl/ajax", xhrHeaders, RequestBody.create(null, rbodyContent)) - } - - // popular uses "success" as a key, search uses "suggestions" - // for future reference: if adding filters, advanced search might use a different key - private fun parseMangaFromJson(response: Response, hasNextPage: Boolean): MangasPage { - var titleKey = "manga_title" - val mangas = gson.fromJson<JsonObject>(response.body()!!.string()) - .let { it["success"] ?: it["suggestions"].also { titleKey = "value" } } - .asJsonArray - .map { json -> - SManga.create().apply { - title = json[titleKey].asString - url = "/series/${json["manga_slug"].asString}" - thumbnail_url = json["covers"][0]["img_name"].asString - } - } - return MangasPage(mangas, hasNextPage) - } - - override fun popularMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/?page=$page", headers) - } - - override fun latestUpdatesSelector() = "div#series-updates div.series-update:not([style\$=none])" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a.series-name").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("div.cover img").attr("abs:data-original") - } - } - - override fun latestUpdatesNextPageSelector() = "button#load-more-updates" - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val rbodyContent = "action=search_query¶meter%5Bquery%5D=$query" - return POST("$baseUrl/ajax", xhrHeaders, RequestBody.create(null, rbodyContent)) - } - - override fun searchMangaParse(response: Response): MangasPage { - return parseMangaFromJson(response, false) - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - document.select("div.series-detailed div.row").first().let { info -> - author = info.select("li:contains(Autor:) a").joinToString { it.text() } - artist = info.select("li:contains(Artist:) a").joinToString { it.text() } - status = info.select("li:contains(Offiziel)").firstOrNull()?.ownText().toStatus() - genre = info.select(".genre-list a").joinToString { it.text() } - thumbnail_url = info.select("img").attr("abs:data-original") - } - description = document.select("div.series-footer h4 ~ p").joinToString("\n\n") { it.text() } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("laufend", ignoreCase = true) -> SManga.ONGOING - this.contains("abgeschlossen", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "ul.chapter-list li" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a[title]").let { - name = "${it.select("b").text()} ${it.select("span:not(.btn)").joinToString(" ") { span -> span.text() }}" - setUrlWithoutDomain(it.attr("href")) - } - date_upload = element.select("p.chapter-date").text().let { - try { - SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()).parse(it.substringAfter(" "))?.time ?: 0L - } catch (_: ParseException) { - 0L - } - } - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val script = document.select("script:containsData(current_chapter:)").first().data() - val imagePath = Regex("""img_path: '(.*)'""").find(script)?.groupValues?.get(1) - ?: throw Exception("Couldn't find image path") - val jsonArray = Regex("""pages: (\[.*]),""").find(script)?.groupValues?.get(1) - ?: throw Exception("Couldn't find JSON array") - - return gson.fromJson<JsonArray>(jsonArray).mapIndexed { i, json -> - Page(i, "", imagePath + json.asJsonObject["file_name"].asString) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/de/wiemanga/AndroidManifest.xml b/src/de/wiemanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/de/wiemanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/de/wiemanga/build.gradle b/src/de/wiemanga/build.gradle deleted file mode 100644 index cfc5500c1..000000000 --- a/src/de/wiemanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'WieManga' - pkgNameSuffix = 'de.wiemanga' - extClass = '.WieManga' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/de/wiemanga/res/mipmap-hdpi/ic_launcher.png b/src/de/wiemanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index addbf8d53..000000000 Binary files a/src/de/wiemanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/wiemanga/res/mipmap-mdpi/ic_launcher.png b/src/de/wiemanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b343ddb6d..000000000 Binary files a/src/de/wiemanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/wiemanga/res/mipmap-xhdpi/ic_launcher.png b/src/de/wiemanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2e1ffb270..000000000 Binary files a/src/de/wiemanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/wiemanga/res/mipmap-xxhdpi/ic_launcher.png b/src/de/wiemanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b6fdc7fa4..000000000 Binary files a/src/de/wiemanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/wiemanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/de/wiemanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c395efcff..000000000 Binary files a/src/de/wiemanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/de/wiemanga/res/web_hi_res_512.png b/src/de/wiemanga/res/web_hi_res_512.png deleted file mode 100644 index d4b21b847..000000000 Binary files a/src/de/wiemanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/de/wiemanga/src/eu/kanade/tachiyomi/extension/de/wiemanga/WieManga.kt b/src/de/wiemanga/src/eu/kanade/tachiyomi/extension/de/wiemanga/WieManga.kt deleted file mode 100644 index aa54a9b07..000000000 --- a/src/de/wiemanga/src/eu/kanade/tachiyomi/extension/de/wiemanga/WieManga.kt +++ /dev/null @@ -1,137 +0,0 @@ -package eu.kanade.tachiyomi.extension.de.wiemanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class WieManga : ParsedHttpSource() { - - override val id: Long = 10 - - override val name = "Wie Manga!" - - override val baseUrl = "https://www.wiemanga.com" - - override val lang = "de" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Accept-Language", "en-US,en;q=0.5") - .add("Referer", baseUrl) - - override fun popularMangaSelector() = ".booklist td > div" - - override fun latestUpdatesSelector() = ".booklist td > div" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/list/Hot-Book/", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/list/New-Update/", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - element.select("dd a:first-child").let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = element.select("dt img").attr("abs:src") - - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/?wd=$query", headers) - } - - override fun searchMangaSelector() = ".searchresult td > div" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - element.select(".resultbookname").let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = element.select(".resultimg img").attr("abs:src") - - return manga - } - - override fun searchMangaNextPageSelector() = ".pagetor a.l" - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - - document.select("div.bookmessgae").let { details -> - manga.author = details.select("dd:contains(Autor:) a").text() - manga.artist = details.select("dd:contains(Zeichner:) a").text() - manga.genre = details.select("dd:contains(Genre:) a").joinToString { it.text() } - manga.description = details.select("dt").first()?.ownText() - manga.thumbnail_url = details.select("div.bookfrontpage img").attr("abs:src") - manga.status = parseStatus(details.select("dd:contains(Status:) a").text()) - } - - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - status.contains("finished", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - element.select(".col1 a").first().let { - chapter.setUrlWithoutDomain(it.attr("href")) - chapter.name = it.text() - } - chapter.date_upload = element.select(".col3 a").first()?.text()?.let { parseChapterDate(it) } ?: 0 - - return chapter - } - - private fun parseChapterDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault()).parse(date)?.time ?: 0L - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("select#page").first().select("option").forEach { - pages.add(Page(pages.size, it.attr("value"))) - } - return pages - } - - override fun imageUrlParse(document: Document): String { - return document.select("img#comicpic").first().attr("abs:src") - } -} diff --git a/src/en/boommanga/AndroidManifest.xml b/src/en/boommanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/boommanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/boommanga/res/mipmap-hdpi/ic_launcher.png b/src/en/boommanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9b3564ef3..000000000 Binary files a/src/en/boommanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/boommanga/res/mipmap-mdpi/ic_launcher.png b/src/en/boommanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 9fd137ddd..000000000 Binary files a/src/en/boommanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/boommanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/boommanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5e430f320..000000000 Binary files a/src/en/boommanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/boommanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/boommanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f71dfbfef..000000000 Binary files a/src/en/boommanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/boommanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/boommanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 05f2fff76..000000000 Binary files a/src/en/boommanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/boommanga/res/web_hi_res_512.png b/src/en/boommanga/res/web_hi_res_512.png deleted file mode 100644 index 5c5686b89..000000000 Binary files a/src/en/boommanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/catmanga/AndroidManifest.xml b/src/en/catmanga/AndroidManifest.xml deleted file mode 100644 index 308dd35c2..000000000 --- a/src/en/catmanga/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - <application> - <activity - android:name=".en.catmanga.CatMangaUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="catmanga.org" - android:pathPattern="/series/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/en/catmanga/build.gradle b/src/en/catmanga/build.gradle deleted file mode 100644 index 005d06a2a..000000000 --- a/src/en/catmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'CatManga' - pkgNameSuffix = "en.catmanga" - extClass = '.CatManga' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/catmanga/res/mipmap-hdpi/ic_launcher.png b/src/en/catmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4db917a88..000000000 Binary files a/src/en/catmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/catmanga/res/mipmap-mdpi/ic_launcher.png b/src/en/catmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8637c4583..000000000 Binary files a/src/en/catmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/catmanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/catmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 423723cf7..000000000 Binary files a/src/en/catmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/catmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/catmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f5cf760a0..000000000 Binary files a/src/en/catmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/catmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/catmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1e7256b0d..000000000 Binary files a/src/en/catmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/catmanga/res/web_hi_res_512.png b/src/en/catmanga/res/web_hi_res_512.png deleted file mode 100644 index 6e46ab8b8..000000000 Binary files a/src/en/catmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/catmanga/src/eu/kanade/tachiyomi/extension/en/catmanga/CatManga.kt b/src/en/catmanga/src/eu/kanade/tachiyomi/extension/en/catmanga/CatManga.kt deleted file mode 100644 index 1baae47a3..000000000 --- a/src/en/catmanga/src/eu/kanade/tachiyomi/extension/en/catmanga/CatManga.kt +++ /dev/null @@ -1,216 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.catmanga - -import android.app.Application -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import org.jsoup.nodes.Document -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class CatManga : HttpSource() { - - override val name = "CatManga" - override val baseUrl = "https://catmanga.org" - override val supportsLatest = true - override val lang = "en" - - override fun popularMangaRequest(page: Int) = GET(baseUrl) - - override fun latestUpdatesRequest(page: Int) = popularMangaRequest(page) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = popularMangaRequest(page) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - val mangas = if (query.startsWith(SERIES_ID_SEARCH_PREFIX)) { - getFilteredSeriesList( - response.asJsoup().getDataJsonObject(), - idFilter = query.removePrefix(SERIES_ID_SEARCH_PREFIX) - ) - } else { - getFilteredSeriesList( - response.asJsoup().getDataJsonObject(), - titleFilter = query - ) - } - MangasPage(mangas, false) - } - } - - override fun popularMangaParse(response: Response): MangasPage { - val mangas = getFilteredSeriesList(response.asJsoup().getDataJsonObject()) - return MangasPage(mangas, false) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val latests = response.asJsoup().getDataJsonObject() - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONArray("latests") - val mangas = (0 until latests.length()).map { i -> - val manga = latests.getJSONArray(i).getJSONObject(0) - SManga.create().apply { - url = "/series/${manga.getString("series_id")}" - title = manga.getString("title") - thumbnail_url = manga.getJSONObject("cover_art").getString("source") - } - } - return MangasPage(mangas, false) - } - - override fun mangaDetailsParse(response: Response): SManga { - return SManga.create().apply { - val series = response.asJsoup().getDataJsonObject() - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("series") - title = series.getString("title") - author = series.getJSONArray("authors").joinToString(", ") - description = series.getString("description") - genre = series.getJSONArray("genres").joinToString(", ") - status = when (series.getString("status")) { - "ongoing" -> SManga.ONGOING - "completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - thumbnail_url = series.getJSONObject("cover_art").getString("source") - } - } - - override fun chapterListParse(response: Response): List<SChapter> { - val jsonObject = response.asJsoup().getDataJsonObject() - - val querySeries = jsonObject.getJSONObject("query").getString("series") - val seriesUrl = jsonObject.getString("page").replace("[series]", querySeries) - val seriesPrefs = Injekt.get<Application>().getSharedPreferences("source_${id}_time_found:$querySeries", 0) - val seriesPrefsEditor = seriesPrefs.edit() - - val series = jsonObject.getJSONObject("props").getJSONObject("pageProps").getJSONObject("series") - val chapters = series.getJSONArray("chapters") - val list = (0 until chapters.length()).reversed().map { i -> - val chapter = chapters.getJSONObject(i) - val title = chapter.optString("title") - val groups = chapter.getJSONArray("groups").joinToString() - val number = chapter.getString("number") - val displayNumber = chapter.optString("display_number", number) - SChapter.create().apply { - url = "$seriesUrl/$number" - chapter_number = number.toFloat() - name = "Chapter $displayNumber" + if (title.isNotBlank()) " - $title" else "" - scanlator = groups - - // Save current time when a chapter is found for the first time, and reuse it on future checks to - // prevent manga entry without any new chapter bumped to the top of "Latest chapter" list - // when the library is updated. - val currentTimeMillis = System.currentTimeMillis() - if (!seriesPrefs.contains(number)) { - seriesPrefsEditor.putLong(number, currentTimeMillis) - } - date_upload = seriesPrefs.getLong(number, currentTimeMillis) - } - } - seriesPrefsEditor.apply() - return list - } - - override fun pageListParse(response: Response): List<Page> { - val pages = response.asJsoup().getDataJsonObject() - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONArray("pages") - return (0 until pages.length()).map { i -> Page(i, "", pages.getString(i)) } - } - - /** - * Returns json object of site data - */ - private fun Document.getDataJsonObject(): JSONObject { - return JSONObject(getElementById("__NEXT_DATA__").html()) - } - - /** - * @return filtered series from home page - * @param data json data from [getDataJsonObject] - * @param titleFilter will be used to check against title and alt_titles, null to disable filter - * @param idFilter will be used to check against id, null to disable filter, only used when [titleFilter] is unset - */ - private fun getFilteredSeriesList( - data: JSONObject, - titleFilter: String? = null, - idFilter: String? = null - ): List<SManga> { - val series = data.getJSONObject("props").getJSONObject("pageProps").getJSONArray("series") - val mangas = mutableListOf<SManga>() - for (i in 0 until series.length()) { - val manga = series.getJSONObject(i) - val mangaId = manga.getString("series_id") - val mangaTitle = manga.getString("title") - val mangaAltTitles = manga.getJSONArray("alt_titles") - - // Filtering - if (titleFilter != null) { - if (!(mangaTitle.contains(titleFilter, true) || mangaAltTitles.contains(titleFilter))) { - continue - } - } else if (idFilter != null) { - if (!mangaId.contains(idFilter, true)) { - continue - } - } - - mangas += SManga.create().apply { - url = "/series/$mangaId" - title = mangaTitle - thumbnail_url = manga.getJSONObject("cover_art").getString("source") - } - } - return mangas.toList() - } - - private fun JSONArray.joinToString(separator: String = ", "): String { - val stringBuilder = StringBuilder() - for (i in 0 until length()) { - if (i > 0) stringBuilder.append(separator) - val item = getString(i) - stringBuilder.append(item) - } - return stringBuilder.toString() - } - - /** - * For string objects - */ - private operator fun JSONArray.contains(other: CharSequence): Boolean { - for (i in 0 until length()) { - if (optString(i, "").contains(other, true)) { - return true - } - } - return false - } - - override fun searchMangaParse(response: Response): MangasPage { - throw UnsupportedOperationException("Not used.") - } - - override fun imageUrlParse(response: Response): String { - throw UnsupportedOperationException("Not used.") - } - - companion object { - const val SERIES_ID_SEARCH_PREFIX = "series_id:" - } -} diff --git a/src/en/catmanga/src/eu/kanade/tachiyomi/extension/en/catmanga/CatMangaUrlActivity.kt b/src/en/catmanga/src/eu/kanade/tachiyomi/extension/en/catmanga/CatMangaUrlActivity.kt deleted file mode 100644 index fc2a09fe1..000000000 --- a/src/en/catmanga/src/eu/kanade/tachiyomi/extension/en/catmanga/CatMangaUrlActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.catmanga - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://catmanga.org/series/xxxxxx intents and redirects them to - * the main Tachiyomi process. - */ -class CatMangaUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${CatManga.SERIES_ID_SEARCH_PREFIX}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("CatMangaUrlActivity", e.toString()) - } - } else { - Log.e("CatMangaUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/en/clonemanga/AndroidManifest.xml b/src/en/clonemanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/clonemanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/clonemanga/build.gradle b/src/en/clonemanga/build.gradle deleted file mode 100644 index f19faa9f6..000000000 --- a/src/en/clonemanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Clone Manga' - pkgNameSuffix = 'en.clonemanga' - extClass = '.CloneManga' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/clonemanga/res/mipmap-hdpi/ic_launcher.png b/src/en/clonemanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 5c9399902..000000000 Binary files a/src/en/clonemanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/clonemanga/res/mipmap-mdpi/ic_launcher.png b/src/en/clonemanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index db26e8188..000000000 Binary files a/src/en/clonemanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/clonemanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/clonemanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7a12000ed..000000000 Binary files a/src/en/clonemanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/clonemanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/clonemanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index db9f0537d..000000000 Binary files a/src/en/clonemanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/clonemanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/clonemanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b00849014..000000000 Binary files a/src/en/clonemanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/clonemanga/res/web_hi_res_512.png b/src/en/clonemanga/res/web_hi_res_512.png deleted file mode 100644 index a0edf190b..000000000 Binary files a/src/en/clonemanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/clonemanga/src/eu/kanade/tachiyomi/extension/en/clonemanga/CloneManga.kt b/src/en/clonemanga/src/eu/kanade/tachiyomi/extension/en/clonemanga/CloneManga.kt deleted file mode 100644 index 0f80170f9..000000000 --- a/src/en/clonemanga/src/eu/kanade/tachiyomi/extension/en/clonemanga/CloneManga.kt +++ /dev/null @@ -1,127 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.clonemanga - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class CloneManga : ParsedHttpSource() { - - override val name = "Clone Manga" - override val baseUrl = "https://manga.clone-army.org/" - override val lang = "en" - override val supportsLatest = false - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/viewer_landing.php") - } - - override fun popularMangaParse(response: Response): MangasPage { - // Gets every manga on landing page - val document = response.asJsoup() - val mangas = document.getElementsByClass(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - return MangasPage(mangas, false) - } - - override fun popularMangaSelector(): String { - return "comicPreviewContainer" - } - - override fun popularMangaFromElement(element: Element): SManga { - val attr = element.getElementsByClass("comicPreview").attr("style") - return SManga.create().apply { - title = element.select("h3").first().text() - artist = "Dan Kim" - author = artist - status = SManga.UNKNOWN - url = element.select("a").first().attr("href") - description = element.select("h4").first()?.text() ?: "" - thumbnail_url = baseUrl + attr.substring( - attr.indexOf("site/themes"), - attr.indexOf(")") - ) - } - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = fetchPopularManga(1) - .map { mp -> MangasPage(mp.mangas.filter { it.title.contains(query, ignoreCase = true) }, false) } - - override fun mangaDetailsParse(document: Document): SManga { - // Populate with already fetched details - return SManga.create() - } - - override fun chapterListParse(response: Response): List<SChapter> { - // Treat each page as an individual chapter - val document = response.asJsoup() - val series = document.location() - val numChapters = Regex( - pattern = "&page=(.*)&lang=" - ).findAll( - input = document.getElementsByTag("script")[3].toString() - ) - .elementAt(3).destructured.component1() - .toInt() - val chapters = ArrayList<SChapter>() - - for (i in 1..numChapters) { - val chapter = SChapter.create().apply { - url = "$series&page=$i" - name = "Chapter $i" - date_upload = 0 - chapter_number = i.toFloat() - } - chapters.add(chapter) - } - return chapters.reversed() // Reverse to correct ordering - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET(chapter.url) - } - - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - val imgAbsoluteUrl = document.getElementsByClass("subsectionContainer")[0] - .select("img").first().absUrl("src") - // List of pages will always contain only one page - return listOf(Page(1, "", imgAbsoluteUrl)) - } - - override fun imageUrlParse(document: Document): String { throw Exception("Not used") } - - override fun pageListParse(document: Document): List<Page> { throw Exception("Not used") } - - override fun chapterListSelector(): String { throw Exception("Not used") } - - override fun chapterFromElement(element: Element): SChapter { throw Exception("Not used") } - - override fun latestUpdatesFromElement(element: Element): SManga { throw Exception("Not used") } - - override fun latestUpdatesNextPageSelector(): String? { throw Exception("Not used") } - - override fun latestUpdatesRequest(page: Int): Request { throw Exception("Not used") } - - override fun latestUpdatesSelector(): String { throw Exception("Not used") } - - override fun popularMangaNextPageSelector(): String? { throw Exception("Not used") } - - override fun searchMangaFromElement(element: Element): SManga { throw Exception("Not used") } - - override fun searchMangaNextPageSelector(): String? { throw Exception("Not used") } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { throw Exception("Not used") } - - override fun searchMangaSelector(): String { throw Exception("Not used") } -} diff --git a/src/en/comicastle/AndroidManifest.xml b/src/en/comicastle/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/comicastle/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/comicastle/build.gradle b/src/en/comicastle/build.gradle deleted file mode 100644 index 4470c4b87..000000000 --- a/src/en/comicastle/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Comicastle' - pkgNameSuffix = 'en.comicastle' - extClass = '.Comicastle' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/comicastle/res/mipmap-hdpi/ic_launcher.png b/src/en/comicastle/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ed218573d..000000000 Binary files a/src/en/comicastle/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicastle/res/mipmap-mdpi/ic_launcher.png b/src/en/comicastle/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 25b0ea50c..000000000 Binary files a/src/en/comicastle/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicastle/res/mipmap-xhdpi/ic_launcher.png b/src/en/comicastle/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index adaeea6ad..000000000 Binary files a/src/en/comicastle/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicastle/res/mipmap-xxhdpi/ic_launcher.png b/src/en/comicastle/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f96e3c35a..000000000 Binary files a/src/en/comicastle/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicastle/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/comicastle/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 26d0efe56..000000000 Binary files a/src/en/comicastle/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicastle/res/web_hi_res_512.png b/src/en/comicastle/res/web_hi_res_512.png deleted file mode 100644 index c9b3c22e5..000000000 Binary files a/src/en/comicastle/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/comicastle/src/eu/kanade/tachiyomi/extension/en/comicastle/Comicastle.kt b/src/en/comicastle/src/eu/kanade/tachiyomi/extension/en/comicastle/Comicastle.kt deleted file mode 100644 index 15cea7eaa..000000000 --- a/src/en/comicastle/src/eu/kanade/tachiyomi/extension/en/comicastle/Comicastle.kt +++ /dev/null @@ -1,163 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.comicastle - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.net.URLEncoder -import java.util.Calendar -import java.util.Locale - -class Comicastle : ParsedHttpSource() { - - override val name = "Comicastle" - - override val versionId = 2 - - override val baseUrl = "https://www.comicastle.org" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private fun pageSegments(page: Int): String = if (page > 1) "/index/${(page - 1) * 30}" else "" - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/library/popular/desc" + pageSegments(page), headers) - } - - override fun popularMangaSelector() = "div.col-lg-2" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("p").text() - setUrlWithoutDomain(element.select("a").first().attr("href")) - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "li.page-item.next a" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/library/postdate/desc" + pageSegments(page), headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/library/search")!!.newBuilder() - var rBody: RequestBody? = null - - (filters.let { if (it.isEmpty()) getFilterList() else filters }) - .filterIsInstance<PostFilter>() - .firstOrNull { it.hasSelection() } - ?.let { filter -> - url.addPathSegment(filter.pathSegment) - rBody = filter.toRequestBody() - } - - if (rBody == null) rBody = createRequestBody(query) - - return POST(url.toString(), headers, rBody!!) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - with(document.select("div.card-body > div.mb-5")) { - thumbnail_url = select("img").attr("abs:src") - val publisher = select("p:contains(Publisher) button") - .firstOrNull()?.let { "Publisher: ${it.text()}\n" } - description = publisher + select("h5:contains(Description) + p").text() - author = select("p:contains(Writer) button").joinToString { it.text() } - artist = select("p:contains(Artist) button").joinToString { it.text() } - status = select("p span.mr-1 strong").text().toStatus() - genre = select("p:contains(Genre) button").joinToString { it.text() } - } - } - } - - private fun String.toStatus() = when { - this.contains("Ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("Completed", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "table tr a" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - name = element.text() - setUrlWithoutDomain(element.attr("href").replace("pbp", "swiper")) - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select(".swiper-wrapper .swiper-slide img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("Cannot combine search types!"), - Filter.Separator(), - PostFilter("Genre", getGenreList()), - PostFilter("Year", getYearList()), - PostFilter("Publisher", getPublisherList()) - ) - - private open class PostFilter(name: String, val vals: Array<String>) : Filter.Select<String>(name, vals) { - val pathSegment = name.toLowerCase(Locale.US) - fun hasSelection(): Boolean = state != 0 - fun toRequestBody(): RequestBody = createRequestBody(vals[state]) - } - - private fun getGenreList() = arrayOf("<Select>", "Action/Adventure", "Anthology", "Anthropomorphic", "Biography", "Children's", "Comedy", "Crime", "Drama", "Fantasy", "Gore", "Graphic Novels", "Historical", "Holiday", "Horror", "Leading Ladies", "LGBTQ", "Literature", "Manga", "Martial Arts", "Mature", "Military", "Movies & TV", "Music", "Mystery", "Mythology", "Non-Fiction", "Original Series", "Political", "Post-Apocalyptic", "Pulp", "Religious", "Risque", "Robots, Cyborgs & Mecha", "Romance", "School Life", "Science Fiction", "Slice of Life", "Spy", "Steampunk", "Superhero", "Supernatural/Occult", "Suspense", "Vampires", "Video Games", "Web Comics", "Werewolves", "Western", "Zombies") - private fun getYearList() = arrayOf("<Select>") + (Calendar.getInstance()[1] downTo 1963).map { it.toString() }.toTypedArray() - private fun getPublisherList() = arrayOf("<Select>", "Action Lab", "Aftershock", "AHOY", "American Mythology", "Aspen", "Avatar Press", "AWA Studios", "Black Mask", "BOOM! Studios", "Dark Horse", "DC", "Death Rattle", "Dynamite", "IDW", "Image", "Magnetic Press", "Marvel", "MAX", "Titan", "Ubiworkshop", "Valiant", "Vault", "Vertigo", "Wildstorm", "Zenescope") -} - -private fun createRequestBody(value: String) = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), "search=" + URLEncoder.encode(value, "UTF-8")) diff --git a/src/en/comicextra/AndroidManifest.xml b/src/en/comicextra/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/comicextra/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/comicextra/build.gradle b/src/en/comicextra/build.gradle deleted file mode 100644 index b3708b700..000000000 --- a/src/en/comicextra/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ComicExtra' - pkgNameSuffix = 'en.comicextra' - extClass = '.ComicExtra' - extVersionCode = 7 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/comicextra/res/mipmap-hdpi/ic_launcher.png b/src/en/comicextra/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 70ae1ce97..000000000 Binary files a/src/en/comicextra/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicextra/res/mipmap-mdpi/ic_launcher.png b/src/en/comicextra/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 59033d835..000000000 Binary files a/src/en/comicextra/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicextra/res/mipmap-xhdpi/ic_launcher.png b/src/en/comicextra/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8829441a0..000000000 Binary files a/src/en/comicextra/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicextra/res/mipmap-xxhdpi/ic_launcher.png b/src/en/comicextra/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ffecc94b1..000000000 Binary files a/src/en/comicextra/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicextra/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/comicextra/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 056721990..000000000 Binary files a/src/en/comicextra/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicextra/res/web_hi_res_512.png b/src/en/comicextra/res/web_hi_res_512.png deleted file mode 100644 index abb7d9831..000000000 Binary files a/src/en/comicextra/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/comicextra/src/eu/kanade/tachiyomi/extension/en/comicextra/ComicExtra.kt b/src/en/comicextra/src/eu/kanade/tachiyomi/extension/en/comicextra/ComicExtra.kt deleted file mode 100644 index 8ba50afca..000000000 --- a/src/en/comicextra/src/eu/kanade/tachiyomi/extension/en/comicextra/ComicExtra.kt +++ /dev/null @@ -1,256 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.comicextra - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.ArrayList -import java.util.Calendar -import java.util.Date -import java.util.Locale -import java.util.regex.Pattern - -class ComicExtra : ParsedHttpSource() { - - override val name = "ComicExtra" - - override val baseUrl = "https://www.comicextra.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val datePattern = Pattern.compile("(\\d+) days? ago") - - override fun popularMangaSelector() = "div.cartoon-box" - - override fun latestUpdatesSelector() = "div.hl-box" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/popular-comic", headers) - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/comic-updates", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/comic-search?key=$query", headers) - } else { - var url = baseUrl - filters.forEach { filter -> - when (filter) { - is GenreFilter -> url += "/${filter.toUriPart()}" - } - } - GET(url + if (page > 1) "/$page" else "", headers) - } - } - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("div.mb-right > h3 > a").attr("href")) - title = element.select("div.mb-right > h3 > a").text() - thumbnail_url = element.select("img").attr("src") - } - - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("div.hlb-t > a").attr("href")) - title = element.select("div.hlb-t > a").text() - thumbnail_url = fetchThumbnailURL(element.select("div.hlb-t > a").attr("href")) - } - - private fun fetchThumbnailURL(url: String) = client.newCall(GET(url, headers)).execute().asJsoup().select("div.movie-l-img > img").attr("src") - - private fun fetchPagesFromNav(url: String) = client.newCall(GET(url, headers)).execute().asJsoup() - - override fun popularMangaNextPageSelector() = "div.general-nav > a:contains(Next)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - title = document.select("div.movie-detail span.title-1").text() - thumbnail_url = document.select("div.movie-l-img > img").attr("src") - status = parseStatus(document.select("dt:contains(Status:) + dd").text()) - author = document.select("dt:contains(Author:) + dd").text() - description = document.select("div#film-content").text() - genre = document.select("dt.movie-dt:contains(Genres:) + dd a").joinToString { it.text() } - } - } - - private fun parseStatus(element: String): Int = when { - element.contains("Completed") -> SManga.COMPLETED - element.contains("Ongoing") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List<SChapter> { - - val document = response.asJsoup() - val nav = document.getElementsByClass("general-nav").first() - val chapters = ArrayList<SChapter>() - - document.select(chapterListSelector()).forEach { - chapters.add(chapterFromElement(it)) - } - - if (nav == null) { - return chapters - } - - val pg2url = nav.select("a:contains(next)").attr("href") - - // recursively build the chapter list - - fun parseChapters(nextURL: String) { - val newpage = fetchPagesFromNav(nextURL) - newpage.select(chapterListSelector()).forEach { - chapters.add(chapterFromElement(it)) - } - val newURL = newpage.select(".general-nav a:contains(next)")?.attr("href") - if (!newURL.isNullOrBlank()) parseChapters(newURL) - } - - parseChapters(pg2url) - - return chapters - } - - override fun chapterListSelector() = "table.table > tbody#list > tr:has(td)" - - override fun chapterFromElement(element: Element): SChapter { - val urlEl = element.select("td:nth-of-type(1) > a").first() - val dateEl = element.select("td:nth-of-type(2)") - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlEl.attr("href")) - chapter.name = urlEl.text() - chapter.date_upload = dateEl.text()?.let { dateParse(it) } ?: 0 - return chapter - } - - private fun dateParse(dateAsString: String): Long { - val date: Date? = try { - SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH).parse(dateAsString.replace(Regex("(st|nd|rd|th)"), "")) - } catch (e: ParseException) { - val m = datePattern.matcher(dateAsString) - - if (dateAsString != "Today" && m.matches()) { - val amount = m.group(1)!!.toInt() - Calendar.getInstance().apply { - add(Calendar.DATE, -amount) - }.time - } else if (dateAsString == "Today") { - Calendar.getInstance().time - } else return 0 - } - - return date?.time ?: 0L - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET(baseUrl + chapter.url + "/full", headers) - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("img.chapter_img").forEachIndexed { i, img -> - pages.add(Page(i, "", img.attr("abs:src"))) - } - return pages - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Unused method was called somehow!") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("Note: can't combine search types"), - Filter.Separator(), - GenreFilter(getGenreList) - ) - - private class GenreFilter(genrePairs: Array<Pair<String, String>>) : UriPartFilter("Category", genrePairs) - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private val getGenreList = arrayOf( - Pair("Action", "action-comic"), - Pair("Adventure", "adventure-comic"), - Pair("Anthology", "anthology-comic"), - Pair("Anthropomorphic", "anthropomorphic-comic"), - Pair("Biography", "biography-comic"), - Pair("Black Mask Studios", "black-mask-studios-comic"), - Pair("Children", "children-comic"), - Pair("Comedy", "comedy-comic"), - Pair("Crime", "crime-comic"), - Pair("DC Comics", "dc-comics-comic"), - Pair("Dark Horse", "dark-horse-comic"), - Pair("Drama", "drama-comic"), - Pair("Family", "family-comic"), - Pair("Fantasy", "fantasy-comic"), - Pair("Fighting", "fighting-comic"), - Pair("First Second Books", "first-second-books-comic"), - Pair("Graphic Novels", "graphic-novels-comic"), - Pair("Historical", "historical-comic"), - Pair("Horror", "horror-comic"), - Pair("LEOMACS", "a><span-class=-comic"), - Pair("LGBTQ", "lgbtq-comic"), - Pair("Leading Ladies", "leading-ladies-comic"), - Pair("Literature", "literature-comic"), - Pair("Manga", "manga-comic"), - Pair("Martial Arts", "martial-arts-comic"), - Pair("Marvel", "marvel-comic"), - Pair("Mature", "mature-comic"), - Pair("Military", "military-comic"), - Pair("Movie Cinematic Link", "movie-cinematic-link-comic"), - Pair("Movies & TV", "movies-&-tv-comic"), - Pair("Music", "music-comic"), - Pair("Mystery", "mystery-comic"), - Pair("Mythology", "mythology-comic"), - Pair("New", "new-comic"), - Pair("Personal", "personal-comic"), - Pair("Political", "political-comic"), - Pair("Post-Apocalyptic", "post-apocalyptic-comic"), - Pair("Psychological", "psychological-comic"), - Pair("Pulp", "pulp-comic"), - Pair("Religious", "religious-comic"), - Pair("Robots", "robots-comic"), - Pair("Romance", "romance-comic"), - Pair("School Life", "school-life-comic"), - Pair("Sci-Fi", "sci-fi-comic"), - Pair("Slice of Life", "slice-of-life-comic"), - Pair("Sport", "sport-comic"), - Pair("Spy", "spy-comic"), - Pair("Superhero", "superhero-comic"), - Pair("Supernatural", "supernatural-comic"), - Pair("Suspense", "suspense-comic"), - Pair("Thriller", "thriller-comic"), - Pair("Vampires", "vampires-comic"), - Pair("Video Games", "video-games-comic"), - Pair("War", "war-comic"), - Pair("Western", "western-comic"), - Pair("Zombies", "zombies-comic"), - Pair("Zulema Scotto Lavina", "zulema-scotto-lavina-comic") - ) -} diff --git a/src/en/comicpunch/AndroidManifest.xml b/src/en/comicpunch/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/comicpunch/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/comicpunch/build.gradle b/src/en/comicpunch/build.gradle deleted file mode 100644 index 308ca15f7..000000000 --- a/src/en/comicpunch/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Comicpunch' - pkgNameSuffix = 'en.comicpunch' - extClass = '.Comicpunch' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/comicpunch/res/mipmap-hdpi/ic_launcher.png b/src/en/comicpunch/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 159460b28..000000000 Binary files a/src/en/comicpunch/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicpunch/res/mipmap-mdpi/ic_launcher.png b/src/en/comicpunch/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 123d89624..000000000 Binary files a/src/en/comicpunch/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicpunch/res/mipmap-xhdpi/ic_launcher.png b/src/en/comicpunch/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7625c68a5..000000000 Binary files a/src/en/comicpunch/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicpunch/res/mipmap-xxhdpi/ic_launcher.png b/src/en/comicpunch/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 051e51ae4..000000000 Binary files a/src/en/comicpunch/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicpunch/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/comicpunch/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2c62dacdd..000000000 Binary files a/src/en/comicpunch/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comicpunch/res/web_hi_res_512.png b/src/en/comicpunch/res/web_hi_res_512.png deleted file mode 100644 index aab3d05f1..000000000 Binary files a/src/en/comicpunch/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/comicpunch/src/eu/kanade/tachiyomi/extension/en/comicpunch/Comicpunch.kt b/src/en/comicpunch/src/eu/kanade/tachiyomi/extension/en/comicpunch/Comicpunch.kt deleted file mode 100644 index 23a61ee59..000000000 --- a/src/en/comicpunch/src/eu/kanade/tachiyomi/extension/en/comicpunch/Comicpunch.kt +++ /dev/null @@ -1,156 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.comicpunch - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class Comicpunch : ParsedHttpSource() { - - override val name = "Comicpunch" - - override val baseUrl = "https://comicpunch.net" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/catalogue?page=${page - 1}", headers) - } - - override fun popularMangaSelector() = "div#content span.field-content a" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.text() - - return manga - } - - override fun popularMangaNextPageSelector() = "li.pager-next" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest-issues", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - return MangasPage(super.latestUpdatesParse(response).mangas.distinctBy { it.url }, false) - } - - override fun latestUpdatesSelector() = "div#Comics li.mtitle a:last-child" - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/comics-list", headers) - } - - private fun searchMangaParse(response: Response, query: String): MangasPage { - val mangas = response.asJsoup().select(searchMangaSelector()) - .filter { it.text().contains(query, ignoreCase = true) } - .map { searchMangaFromElement(it) } - - return MangasPage(mangas, false) - } - - override fun searchMangaSelector() = "table.cols-2 td a" - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - - // Website uses at least 2 different formats plus other inconsistencies, alter at your own peril - document.select("div.content.node-comic, div.content.node-comic-ii").let { details -> - manga.author = details.select("div.field-label:contains(publisher:) + div a").text() - manga.genre = details.select("div.field-label:contains(genres:) + div a").joinToString { it.text() } - manga.description = details.select("div.field-type-text-with-summary:not(:has(ul.splash))").text().let { desc -> - if (desc.isNotEmpty()) desc else document.select("ul.splash li.summary").first()?.ownText() - } - manga.thumbnail_url = details.select("img").attr("abs:src") ?: document.select("li.pic img").attr("abs:src") - } - - return manga - } - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = mutableListOf<SChapter>() - var elements = response.asJsoup().select(chapterListSelector()).toList() - - // Check if latest chapter is just a placeholder, drop it if it is - client.newCall(GET(elements[0].attr("abs:href"), headers)).execute().asJsoup().select("img").last().attr("src").let { img -> - if (img.contains("placeholder", ignoreCase = true)) elements = elements.drop(1) - } - elements.map { chapters.add(chapterFromElement(it)) } - - return chapters - } - - override fun chapterListSelector() = "li.chapter a" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - chapter.setUrlWithoutDomain(element.attr("href")) - chapter.name = element.text() - - return chapter - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - val pageUrls = document.select("div#code_contain > script") - .eq(1).first().data() - .substringAfter("= [").substringBefore("]").split(",") - pageUrls.forEachIndexed { i, img -> - pages.add(Page(i, "", img.removeSurrounding("\""))) - } - - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/dilbert/AndroidManifest.xml b/src/en/dilbert/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/dilbert/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/dilbert/build.gradle b/src/en/dilbert/build.gradle deleted file mode 100644 index c3f7ae261..000000000 --- a/src/en/dilbert/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Dilbert' - pkgNameSuffix = 'en.dilbert' - extClass = '.Dilbert' - extVersionCode = 3 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/dilbert/res/mipmap-hdpi/ic_launcher.png b/src/en/dilbert/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index bc06cdcdb..000000000 Binary files a/src/en/dilbert/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dilbert/res/mipmap-mdpi/ic_launcher.png b/src/en/dilbert/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a95ecc78d..000000000 Binary files a/src/en/dilbert/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dilbert/res/mipmap-xhdpi/ic_launcher.png b/src/en/dilbert/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 31c1f874f..000000000 Binary files a/src/en/dilbert/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dilbert/res/mipmap-xxhdpi/ic_launcher.png b/src/en/dilbert/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index bddf661a2..000000000 Binary files a/src/en/dilbert/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dilbert/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/dilbert/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ea384706b..000000000 Binary files a/src/en/dilbert/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dilbert/res/web_hi_res_512.png b/src/en/dilbert/res/web_hi_res_512.png deleted file mode 100644 index 82a6d5610..000000000 Binary files a/src/en/dilbert/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/dilbert/src/eu/kanade/tachiyomi/extension/en/dilbert/Dilbert.kt b/src/en/dilbert/src/eu/kanade/tachiyomi/extension/en/dilbert/Dilbert.kt deleted file mode 100644 index acb22eb19..000000000 --- a/src/en/dilbert/src/eu/kanade/tachiyomi/extension/en/dilbert/Dilbert.kt +++ /dev/null @@ -1,161 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dilbert - -import android.os.Build.VERSION -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Dilbert : ParsedHttpSource() { - - override val name = "Dilbert" - - override val baseUrl = "https://dilbert.com" - - override val lang = "en" - - override val supportsLatest = false - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(RateLimitInterceptor(4)).build() - - private val userAgent = "Mozilla/5.0 " + - "(Android ${VERSION.RELEASE}; Mobile) " + - "Tachiyomi/${BuildConfig.VERSION_NAME}" - - private val dateFormat = SimpleDateFormat("EEEE MMMM dd, yyyy", Locale.US) - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", userAgent) - add("Referer", baseUrl) - } - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val currentYear = Calendar.getInstance().get(Calendar.YEAR) - return Observable.just( - MangasPage( - (currentYear downTo 1989).map { - SManga.create().apply { - url = "?$it" - title = "$name ($it)" - artist = "Scott Adams" - author = "Scott Adams" - status = if (it < currentYear) SManga.COMPLETED else SManga.ONGOING - description = - """ - A satirical comic strip featuring Dilbert, a competent, but seldom recognized engineer. - (This entry includes all the chapters published in $it.) - """.trimIndent() - thumbnail_url = "https://dilbert.com/assets/favicon/favicon-196x196-cf4d86b485e628a034ab8b961c1c3520b5969252400a80b9eed544d99403e037.png" - } - }, - false - ) - ) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = fetchPopularManga(page) - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = - Observable.just(manga.apply { initialized = true }) - - private fun chapterListRequest(manga: SManga, page: Int = 1) = - GET("$baseUrl/search_results?sort=date_asc&year=${manga.year}&page=$page", headers) - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val date = element.first(".comic-title-date").text() - url = element.first(".img-comic-link").attr("href") - name = element.first(".comic-title-name").text().ifBlank { date } - date_upload = dateFormat.parse(date)?.time ?: 0L - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - val chapters = mutableListOf<SChapter>() - fun getChapters(page: Int = 1): Document { - val res = client.newCall(chapterListRequest(manga, page)).execute() - if (!res.isSuccessful) { - res.close() - throw Exception("HTTP error ${res.code()}") - } - return res.asJsoup().also { - chapters.addAll(it.select(".comic-item").map(::chapterFromElement)) - } - } - val pages = getChapters().first(".pagination > li:nth-last-child(2) > a")?.text()?.toIntOrNull() - if (pages != null) for (page in 2..pages) getChapters(page) - return Observable.just( - chapters.sortedBy(SChapter::date_upload).mapIndexed { - i, ch -> - ch.apply { chapter_number = i + 1f } - }.reversed() - ) - } - - override fun fetchPageList(chapter: SChapter) = - Observable.just(listOf(Page(0, chapter.url))) - - override fun imageUrlRequest(page: Page) = GET(page.url, headers) - - override fun imageUrlParse(document: Document) = - document.first(".img-comic").attr("src") - - private val SManga.year: Int - get() = url.substringAfterLast('?').toInt() - - private fun Element.first(selector: String) = select(selector).first() - - override fun popularMangaSelector() = "" - - override fun popularMangaNextPageSelector() = "" - - override fun searchMangaSelector() = "" - - override fun searchMangaNextPageSelector() = "" - - override fun latestUpdatesSelector() = "" - - override fun latestUpdatesNextPageSelector() = "" - - override fun chapterListSelector() = "" - - override fun popularMangaRequest(page: Int) = - throw UnsupportedOperationException("This method should not be called!") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesRequest(page: Int) = - throw UnsupportedOperationException("This method should not be called!") - - override fun chapterListRequest(manga: SManga) = - throw UnsupportedOperationException("This method should not be called!") - - override fun mangaDetailsParse(document: Document) = - throw UnsupportedOperationException("This method should not be called!") - - override fun pageListParse(document: Document) = - throw UnsupportedOperationException("This method should not be called!") - - override fun popularMangaFromElement(element: Element) = - throw UnsupportedOperationException("This method should not be called!") - - override fun searchMangaFromElement(element: Element) = - throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesFromElement(element: Element) = - throw UnsupportedOperationException("This method should not be called!") -} diff --git a/src/en/doujins/AndroidManifest.xml b/src/en/doujins/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/doujins/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/doujins/build.gradle b/src/en/doujins/build.gradle deleted file mode 100644 index 35e96dbdc..000000000 --- a/src/en/doujins/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Doujins' - pkgNameSuffix = 'en.doujins' - extClass = '.Doujins' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/doujins/res/mipmap-hdpi/ic_launcher.png b/src/en/doujins/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 21396b51e..000000000 Binary files a/src/en/doujins/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/doujins/res/mipmap-mdpi/ic_launcher.png b/src/en/doujins/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d00cba10c..000000000 Binary files a/src/en/doujins/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/doujins/res/mipmap-xhdpi/ic_launcher.png b/src/en/doujins/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7efb531c3..000000000 Binary files a/src/en/doujins/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/doujins/res/mipmap-xxhdpi/ic_launcher.png b/src/en/doujins/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f440235e5..000000000 Binary files a/src/en/doujins/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/doujins/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/doujins/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e33bbc81a..000000000 Binary files a/src/en/doujins/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/doujins/res/web_hi_res_512.png b/src/en/doujins/res/web_hi_res_512.png deleted file mode 100644 index 221eaf878..000000000 Binary files a/src/en/doujins/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/doujins/src/eu/kanade/tachiyomi/extension/en/doujins/Doujins.kt b/src/en/doujins/src/eu/kanade/tachiyomi/extension/en/doujins/Doujins.kt deleted file mode 100644 index 8340654a6..000000000 --- a/src/en/doujins/src/eu/kanade/tachiyomi/extension/en/doujins/Doujins.kt +++ /dev/null @@ -1,257 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.doujins - -import android.app.Application -import android.content.SharedPreferences -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Date -import java.util.Locale -import java.util.TimeZone - -@Nsfw -class Doujins : HttpSource() { - - override val baseUrl: String = "https://doujins.com" - - override val lang: String = "en" - - override val name: String = "Doujins" - - override val supportsLatest: Boolean = true - - private val gson = Gson() - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return listOf( - SChapter.create().apply { - name = "Chapter" - setUrlWithoutDomain(response.request().url().toString()) - - val dateAndPageCountString = response.asJsoup().select(".text-md-right.text-sm-left > .folder-message").text() - - val date = dateAndPageCountString.substringBefore(" • ") - for (dateFormat in MANGA_DETAILS_DATE_FORMAT) { - if (date_upload == 0L) - date_upload = dateFormat.parseOrNull(date)?.time ?: 0L - else - break - } - } - ) - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage { - return MangasPage( - gson.fromJson<JsonObject>(response.body()!!.string())["folders"].asJsonArray.map { - SManga.create().apply { - setUrlWithoutDomain(it["link"].asString) - title = it["name"].asString - artist = it["artistList"].asString - author = artist - genre = it["tags"].asJsonArray.joinToString(", ") { it["tag"].asString } - thumbnail_url = it["thumbnail2"].asString - } - }, - true - ) - } - - private fun getLatestPageUrl(page: Int): String { - val endDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { - add(Calendar.DATE, 1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - add(Calendar.DATE, -1 * PAGE_DAYS * (page - 1)) - } - - val endDateSec = endDate.timeInMillis / 1000 - val startDateSec = endDate.apply { - add(Calendar.DATE, -1 * PAGE_DAYS) - }.timeInMillis / 1000 - - return "$baseUrl/folders?start=$startDateSec&end=$endDateSec" - } - - override fun latestUpdatesRequest(page: Int) = GET(getLatestPageUrl(page)) - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - return SManga.create().apply { - title = document.select(".folder-title a").last().text() - artist = document.select(".gallery-artist a").joinToString { it.text() } - author = artist - genre = document.select(".tag-area").first().select("a").joinToString { it.text() } - } - } - - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - val pageUrl = response.request().url().toString() - return document.select(".doujin").mapIndexed { i, page -> - Page(i, "$pageUrl${page.attr("data-link")}", page.attr("data-file").replace("amp;", "")) - } - } - - override fun popularMangaParse(response: Response) = parseGalleryPage(response.asJsoup()) - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/top/month") - - override fun searchMangaParse(response: Response) = parseGalleryPage(response.asJsoup()) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filterList = if (filters.isEmpty()) getFilterList() else filters - val seriesFilter = filterList.findInstance<SeriesFilter>()!! - val sortFilter = filterList.findInstance<SortFilter>()!! - val popularityPeriodFilter = filterList.findInstance<PopularityPeriodFilter>()!! - - return when { - query != "" -> { - GET("$baseUrl/searches?words=$query&page=$page&sort=${sortFilter.toUriPart()}") - } - seriesFilter.toUriPart() != "" -> { - GET("$baseUrl${seriesFilter.toUriPart()}?sort=${sortFilter.toUriPart()}") - } - else -> { - GET("$baseUrl${popularityPeriodFilter.toUriPart()}") - } - } - } - - private fun parseGalleryPage(document: Document): MangasPage { - - val pagination = document.select(".pagination").first() - return MangasPage( - document.select("div:not(.premium-folder) > .thumbnail-doujin a.gallery-visited-from-favorites").map { - SManga.create().apply { - setUrlWithoutDomain(it.attr("href")) - title = it.select("div.title .text").text() - artist = it.parent().nextElementSibling().select(".single-line strong")?.last()?.text()?.substringAfter("Artist: ") - author = artist - thumbnail_url = it.select("img").attr("srcset") - } - }, - if (pagination != null) { - !pagination.select("li.page-item:last-child").hasClass("disabled") - } else { - false - } - ) - } - - override fun getFilterList(): FilterList = FilterList( - Filter.Header("Text search ignores series and period filters"), - Filter.Separator(), - - Filter.Header("Series filter overrides period filter"), - SeriesFilter(), - Filter.Separator(), - - Filter.Header("Period filter only applies at initial page"), - PopularityPeriodFilter(), - Filter.Separator(), - - Filter.Header("Sort only works with text search and series filter"), - SortFilter() - ) - - private class SeriesFilter : UriPartFilter( - "Series", - arrayOf( - Pair("None", ""), - Pair("Doujins - Original Series", "/doujins-original-series-19934"), - Pair("Hentai Magazine Chapters", "/hentai-magazine-chapters-2766"), - Pair("Hentai Manga", "/hentai-manga-19"), - Pair("Fate Grand Order", "/fate-grand-order-doujins-28615"), - Pair("CG Sets - Original Series", "/cg-sets-original-series-14865"), - Pair("Touhou", "/touhou-doujins-7748"), - Pair("Naruto", "/naruto-doujins-5761"), - Pair("Kantai Collection", "/kantai-collection-doujins-22720"), - Pair("Hentai Game CG-Sets", "/hentai-game-cg-sets-2422"), - Pair("One Piece", "/one-piece-doujins-6080"), - Pair("Granblue Fantasy", "/granblue-fantasy-doujins-28177"), - Pair("Azur Lane", "/azur-lane-doujins-34298"), - Pair("Sword Art Online", "/sword-art-online-doujins-7246"), - Pair("Idolmaster", "/idolmaster-4281"), - Pair("My Hero Academia", "/my-hero-academia-doujins-28744"), - Pair("Love Live", "/love-live-doujins-21865"), - Pair("Pokemon", "/pokemon-doujins-6393"), - Pair("Dragon Ball", "/dragon-ball-doujins-1238"), - Pair("CGs - Mixed Series", "/cgs-mixed-series-35311"), - Pair("Doujins - Mixed Series", "/doujins-mixed-series-20091"), - Pair("Hentai Magazine Chapters", "/hentai-magazine-chapters-2766"), - Pair("Hentai Magazine Chapters - Super-Shorts", "/hentai-magazine-chapters-super-shorts-19933"), - Pair("Hentai Manga", "/hentai-manga-19") - ) - ) - - private class SortFilter : UriPartFilter( - "Sort", - arrayOf( - Pair("Newest First", ""), - Pair("Oldest First", "created_at"), - Pair("Alphabetical", "name"), - Pair("Rating", "-cached_score"), - Pair("Popularity", "-cached_views") - ) - ) - - private class PopularityPeriodFilter : UriPartFilter( - "Period", - arrayOf( - Pair("This Month", "/top"), - Pair("This Year", "/top/year"), - Pair("All Time", "/top/all"), - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private fun SimpleDateFormat.parseOrNull(string: String): Date? { - return try { - parse(string) - } catch (e: ParseException) { - null - } - } - - private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T - - companion object { - private const val PAGE_DAYS = 3 - private val ORDINAL_SUFFIXES = listOf("th", "st", "nd", "rd") - private val MANGA_DETAILS_DATE_FORMAT = ORDINAL_SUFFIXES.map { - SimpleDateFormat("MMMM dd'$it', yyyy", Locale.US) - } - } -} diff --git a/src/en/dynasty/AndroidManifest.xml b/src/en/dynasty/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/dynasty/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/dynasty/build.gradle b/src/en/dynasty/build.gradle deleted file mode 100644 index b75b8eb58..000000000 --- a/src/en/dynasty/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Dynasty' - pkgNameSuffix = 'en.dynasty' - extClass = '.DynastyFactory' - extVersionCode = 12 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/dynasty/res/mipmap-hdpi/ic_launcher.png b/src/en/dynasty/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 14534ba83..000000000 Binary files a/src/en/dynasty/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dynasty/res/mipmap-mdpi/ic_launcher.png b/src/en/dynasty/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index bd1d60cdd..000000000 Binary files a/src/en/dynasty/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dynasty/res/mipmap-xhdpi/ic_launcher.png b/src/en/dynasty/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c8947f327..000000000 Binary files a/src/en/dynasty/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dynasty/res/mipmap-xxhdpi/ic_launcher.png b/src/en/dynasty/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ee2058085..000000000 Binary files a/src/en/dynasty/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dynasty/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/dynasty/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2db0d128a..000000000 Binary files a/src/en/dynasty/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/dynasty/res/web_hi_res_512.png b/src/en/dynasty/res/web_hi_res_512.png deleted file mode 100644 index f1eb35713..000000000 Binary files a/src/en/dynasty/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyAnthologies.kt b/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyAnthologies.kt deleted file mode 100644 index 6635cb30f..000000000 --- a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyAnthologies.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dynasty - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Request -import org.jsoup.nodes.Document - -class DynastyAnthologies : DynastyScans() { - - override val name = "Dynasty-Anthologies" - - override fun popularMangaInitialUrl() = "$baseUrl/anthologies?view=cover" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&classes%5B%5D=Anthology&sort=&page=$page", headers) - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = baseUrl + document.select("div.span2 > img").attr("src") - parseHeader(document, manga) - parseGenres(document, manga) - parseDescription(document, manga) - return manga - } -} diff --git a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyChapters.kt b/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyChapters.kt deleted file mode 100644 index 1ac7c34ed..000000000 --- a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyChapters.kt +++ /dev/null @@ -1,95 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dynasty - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class DynastyChapters : DynastyScans() { - override val name = "Dynasty-Chapters" - override fun popularMangaInitialUrl() = "" - - private fun popularMangaInitialUrl(page: Int) = "$baseUrl/search?q=&classes%5B%5D=Chapter&page=$page=$&sort=" - private fun latestUpdatesInitialUrl(page: Int) = "$baseUrl/search?q=&classes%5B%5D=Chapter&page=$page=$&sort=created_at" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&classes%5B%5D=Chapter&sort=&page=$page", headers) - } - - override val supportsLatest = true - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - - manga.thumbnail_url = document.select("img").last().absUrl("src") - manga.title = document.select("h3 b").text() - manga.status = SManga.COMPLETED - val artistAuthorElements = document.select("a[href*=author]") - if (!artistAuthorElements.isEmpty()) { - if (artistAuthorElements.size == 1) { - manga.author = artistAuthorElements[0].text() - } else { - manga.artist = artistAuthorElements[0].text() - manga.author = artistAuthorElements[1].text() - } - } - - val genreElements = document.select(".tags a") - parseGenres(genreElements, manga) - - return manga - } - - override fun searchMangaSelector() = "dd" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - val titleSelect = element.select("a.name") - manga.title = titleSelect.text() - manga.setUrlWithoutDomain(titleSelect.attr("href")) - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - - return document.select(chapterListSelector()).map { - chapterFromElement(it) - } - } - - override fun chapterListSelector() = ".chapters.show#main" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.baseUri()) - chapter.name = element.select("h3").text() - chapter.date_upload = element.select("span.released").firstOrNull()?.text().toDate("MMM dd, yyyy") - return chapter - } - - override fun popularMangaRequest(page: Int): Request { - return GET(popularMangaInitialUrl(page), headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET(latestUpdatesInitialUrl(page), headers) - } - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaSelector() = searchMangaSelector() - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element) - - override fun popularMangaParse(response: Response) = searchMangaParse(response) - override fun latestUpdatesParse(response: Response) = searchMangaParse(response) -} diff --git a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyDoujins.kt b/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyDoujins.kt deleted file mode 100644 index bef273b6b..000000000 --- a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyDoujins.kt +++ /dev/null @@ -1,77 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dynasty - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class DynastyDoujins : DynastyScans() { - - override val name = "Dynasty-Doujins" - - override fun popularMangaInitialUrl() = "$baseUrl/doujins?view=cover" - - override fun popularMangaFromElement(element: Element): SManga { - return super.popularMangaFromElement(element).apply { - thumbnail_url = element.select("img").attr("abs:src").let { - if (it.contains("cover_missing")) { - null - } else { - it - } - } - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&classes%5B%5D=Doujin&sort=&page=$page", headers) - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create().apply { - thumbnail_url = document.select("a.thumbnail img").firstOrNull()?.attr("abs:src") - ?.replace("/thumb/", "/medium/") - } - parseGenres(document, manga) - return manga - } - - override fun chapterListSelector() = "div.span9 > dl.chapter-list > dd" - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - - val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) }.toMutableList() - - document.select("a.thumbnail img").let { images -> - if (images.isNotEmpty()) chapters.add( - SChapter.create().apply { - name = "Images" - setUrlWithoutDomain(document.location() + "/images") - } - ) - } - - return chapters - } - - override fun pageListParse(document: Document): List<Page> { - return if (document.location().endsWith("/images")) { - document.select("a.thumbnail").mapIndexed { i, element -> - Page(i, element.attr("abs:href")) - } - } else { - super.pageListParse(document) - } - } - - override fun imageUrlParse(document: Document): String { - return document.select("div.image img").attr("abs:src") - } -} diff --git a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyFactory.kt b/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyFactory.kt deleted file mode 100644 index e2482f8bd..000000000 --- a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyFactory.kt +++ /dev/null @@ -1,17 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dynasty - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class DynastyFactory : SourceFactory { - override fun createSources(): List<Source> = getAllDynasty() -} - -fun getAllDynasty() = - listOf( - DynastyAnthologies(), - DynastyChapters(), - DynastyDoujins(), - DynastyIssues(), - DynastySeries() - ) diff --git a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyIssues.kt b/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyIssues.kt deleted file mode 100644 index cc74fe275..000000000 --- a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyIssues.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dynasty - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Request -import org.jsoup.nodes.Document - -class DynastyIssues : DynastyScans() { - - override val name = "Dynasty-Issues" - - override fun popularMangaInitialUrl() = "$baseUrl/issues?view=cover" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&classes%5B%5D=Issue&sort=&page=$page", headers) - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = baseUrl + document.select("div.span2 > img").attr("src") - parseHeader(document, manga) - parseGenres(document, manga) - parseDescription(document, manga) - return manga - } -} diff --git a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyScans.kt b/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyScans.kt deleted file mode 100644 index 469007f46..000000000 --- a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastyScans.kt +++ /dev/null @@ -1,243 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dynasty - -import android.net.Uri -import eu.kanade.tachiyomi.network.GET -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.nodes.Node -import org.jsoup.nodes.TextNode -import org.jsoup.select.Elements -import java.text.SimpleDateFormat -import java.util.ArrayList -import java.util.Locale - -abstract class DynastyScans : ParsedHttpSource() { - - override val baseUrl = "https://dynasty-scans.com" - - abstract fun popularMangaInitialUrl(): String - - override val lang = "en" - - override val supportsLatest = false - - private var parent: List<Node> = ArrayList() - - private var list = InternalList(ArrayList(), "") - - private var imgList = InternalList(ArrayList(), "") - - private var _valid: Validate = Validate(false, -1) - - override fun popularMangaRequest(page: Int): Request { - return GET(popularMangaInitialUrl(), headers) - } - - override fun popularMangaSelector() = "ul.thumbnails > li.span2" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").attr("href")) - manga.title = element.select("div.caption").text() - return manga - } - - override fun popularMangaParse(response: Response): MangasPage { - val mangas = response.asJsoup().select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - return MangasPage(mangas, false) - } - - override fun searchMangaSelector() = "a.name" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.text() - return manga - } - - override fun searchMangaNextPageSelector() = "div.pagination > ul > li.active + li > a" - - private fun buildListfromResponse(): List<Node> { - return client.newCall( - Request.Builder().headers(headers) - .url(popularMangaInitialUrl()).build() - ).execute().asJsoup() - .select("div#main").first { it.hasText() }.childNodes() - } - - protected fun parseHeader(document: Document, manga: SManga): Boolean { - val elements = document.select("div.tags > h2.tag-title").first().getElementsByTag("a") - if (elements.isEmpty()) { - return false - } - if (elements.lastIndex == 0) { - manga.author = elements[0].text() - } else { - manga.artist = elements[0].text() - manga.author = elements[1].text() - } - manga.status = document.select("div.tags > h2.tag-title > small").text().let { - when { - it.contains("Ongoing") -> SManga.ONGOING - it.contains("Completed") -> SManga.COMPLETED - it.contains("Licensed") -> SManga.LICENSED - else -> SManga.UNKNOWN - } - } - return true - } - - protected fun parseGenres(document: Document, manga: SManga, select: String = "div.tags > div.tag-tags a") { - parseGenres(document.select(select), manga) - } - - protected fun parseGenres(elements: Elements, manga: SManga) { - if (!elements.isEmpty()) { - val genres = mutableListOf<String>() - elements.forEach { - genres.add(it.text()) - } - manga.genre = genres.joinToString(", ") - } - } - - protected fun parseDescription(document: Document, manga: SManga) { - manga.description = document.select("div.tags > div.row div.description").text() - } - - private fun getValid(manga: SManga): Validate { - if (parent.isEmpty()) parent = buildListfromResponse() - if (list.isEmpty()) list = InternalList(parent, "href") - if (imgList.isEmpty()) imgList = InternalList(parent, "src") - val pos = list.indexOf(manga.url.substringBeforeLast("/") + "/" + Uri.encode(manga.url.substringAfterLast("/"))) - return Validate((pos > -1), pos) - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - _valid = getValid(manga) - return manga - } - - override fun chapterListSelector() = "div.span10 > dl.chapter-list > dd" - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).asReversed() - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - val nodes = InternalList(element.childNodes(), "text") - - chapter.setUrlWithoutDomain(element.select("a.name").attr("href")) - chapter.name = nodes[0] - if (nodes.contains(" by ")) { - chapter.name += " by ${nodes[nodes.indexOfPartial(" by ") + 1]}" - if (nodes.contains(" and ")) { - chapter.name += " and ${nodes[nodes.indexOfPartial(" and ") + 1]}" - } - } - chapter.date_upload = nodes[nodes.indexOfPartial("released")] - .substringAfter("released ") - .replace("\'", "") - .toDate("MMM dd yy") - return chapter - } - - protected fun String?.toDate(pattern: String): Long { - this ?: return 0 - return try { - SimpleDateFormat(pattern, Locale.ENGLISH).parse(this)?.time ?: 0 - } catch (_: Exception) { - 0 - } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - try { - val imageUrl = document.select("script").last().html().substringAfter("var pages = [").substringBefore("];") - val imageUrls = JSONArray("[$imageUrl]") - - (0 until imageUrls.length()) - .map { imageUrls.getJSONObject(it) } - .map { baseUrl + it.get("image") } - .forEach { pages.add(Page(pages.size, "", it)) } - } catch (e: Exception) { - e.printStackTrace() - } - return pages - } - - class InternalList(nodes: List<Node>, type: String) : ArrayList<String>() { - - init { - if (type == "text") { - for (node in nodes) { - if (node is TextNode) { - if (node.text() != " " && !node.text().contains("\n")) { - this.add(node.text()) - } - } else if (node is Element) this.add(node.text()) - } - } - if (type == "src") { - nodes - .filter { it is Element && it.hasClass("thumbnails") } - .flatMap { it.childNodes() } - .filterIsInstance<Element>() - .filter { it.hasClass("span2") } - .forEach { this.add(it.child(0).child(0).attr(type)) } - } - if (type == "href") { - nodes - .filter { it is Element && it.hasClass("thumbnails") } - .flatMap { it.childNodes() } - .filterIsInstance<Element>() - .filter { it.hasClass("span2") } - .forEach { this.add(it.child(0).attr(type)) } - } - } - - fun indexOfPartial(partial: String): Int { - return (0..this.lastIndex).firstOrNull { this[it].contains(partial) } - ?: -1 - } - - fun getItem(partial: String): String { - return (0..this.lastIndex) - .firstOrNull { super.get(it).contains(partial) } - ?.let { super.get(it) } - ?: "" - } - } - - class Validate(_isManga: Boolean, _pos: Int) { - val isManga = _isManga - val pos = _pos - } - - override fun popularMangaNextPageSelector() = "" - override fun latestUpdatesSelector() = "" - override fun latestUpdatesNextPageSelector() = "" - override fun imageUrlParse(document: Document): String = "" - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun latestUpdatesRequest(page: Int): Request { - return popularMangaRequest(page) - } -} diff --git a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastySeries.kt b/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastySeries.kt deleted file mode 100644 index 6bdbe15a7..000000000 --- a/src/en/dynasty/src/eu/kanade/tachiyomi/extension/en/dynasty/DynastySeries.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.dynasty - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Request -import org.jsoup.nodes.Document - -class DynastySeries : DynastyScans() { - - override val name = "Dynasty-Series" - - override fun popularMangaInitialUrl() = "$baseUrl/series?view=cover" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&classes%5B%5D=Series&sort=&page=$page", headers) - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = baseUrl + document.select("div.span2 > img").attr("src") - parseHeader(document, manga) - parseGenres(document, manga) - parseDescription(document, manga) - return manga - } -} diff --git a/src/en/earlymanga/AndroidManifest.xml b/src/en/earlymanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/earlymanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/earlymanga/build.gradle b/src/en/earlymanga/build.gradle deleted file mode 100644 index e56afba51..000000000 --- a/src/en/earlymanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'EarlyManga' - pkgNameSuffix = 'en.earlymanga' - extClass = '.EarlyManga' - extVersionCode = 16 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/earlymanga/res/mipmap-hdpi/ic_launcher.png b/src/en/earlymanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 13ae40d5a..000000000 Binary files a/src/en/earlymanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/earlymanga/res/mipmap-mdpi/ic_launcher.png b/src/en/earlymanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5a0456746..000000000 Binary files a/src/en/earlymanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/earlymanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/earlymanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 19ea42fa7..000000000 Binary files a/src/en/earlymanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/earlymanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/earlymanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0b71d23ed..000000000 Binary files a/src/en/earlymanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/earlymanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/earlymanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 8cad8cb17..000000000 Binary files a/src/en/earlymanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/earlymanga/res/web_hi_res_512.png b/src/en/earlymanga/res/web_hi_res_512.png deleted file mode 100644 index 3921235c0..000000000 Binary files a/src/en/earlymanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/earlymanga/src/eu/kanade/tachiyomi/extension/en/earlymanga/EarlyManga.kt b/src/en/earlymanga/src/eu/kanade/tachiyomi/extension/en/earlymanga/EarlyManga.kt deleted file mode 100644 index 91ae5a69e..000000000 --- a/src/en/earlymanga/src/eu/kanade/tachiyomi/extension/en/earlymanga/EarlyManga.kt +++ /dev/null @@ -1,161 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.earlymanga - -import android.util.Base64 -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import kotlin.math.absoluteValue -import kotlin.random.Random - -class EarlyManga : ParsedHttpSource() { - - override val name = "EarlyManga" - - override val baseUrl = "https://earlymanga.org" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - protected open val userAgentRandomizer1 = "${Random.nextInt(9).absoluteValue}" - protected open val userAgentRandomizer2 = "${Random.nextInt(10,99).absoluteValue}" - protected open val userAgentRandomizer3 = "${Random.nextInt(100,999).absoluteValue}" - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add( - "User-Agent", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + - "Chrome/8$userAgentRandomizer1.0.4$userAgentRandomizer3.1$userAgentRandomizer2 Safari/537.36" - ) - .add("Referer", baseUrl) - - // popular - override fun popularMangaRequest(page: Int) = GET("$baseUrl/hot-manga?page=$page", headers) - - override fun popularMangaSelector() = "div.content-homepage-item" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = element.select("a").attr("abs:href").substringAfter(baseUrl) - manga.title = element.select(".colum-content a.homepage-item-title").text() - manga.thumbnail_url = element.select("a img").attr("abs:src") - return manga - } - - override fun popularMangaNextPageSelector() = "li.paging:not(.disabled)" - - // latest - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers) - - override fun latestUpdatesSelector() = ".container > .main-content .content-homepage-item" - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = ".load-data-btn" - - // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?search=$query", headers) - } - - override fun searchMangaSelector() = "div.manga-entry" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = element.select("a").attr("abs:href").substringAfter(baseUrl) - manga.title = element.select("div:has(.flag)+a").attr("title") - manga.thumbnail_url = element.select("a img").attr("abs:src") - return manga - } - - override fun searchMangaNextPageSelector(): String? = null - - // manga details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - thumbnail_url = document.select(".manga-page-img").attr("abs:src") - title = document.select("title").text() - author = document.select(".author-link a").text() - artist = document.select(".artist-link a").text() - status = parseStatus(document.select(".pub_stutus").text()) - description = document.select(".desc:not([class*=none])").text().replace("_", "") - genre = document.select(".manga-info-card a.badge-secondary").joinToString { it.text() } - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("ongoing", true) -> SManga.ONGOING - status.contains("completed", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapters - override fun chapterListRequest(manga: SManga) = chapterListRequest(manga.url, 1) - - private fun chapterListRequest(mangaUrl: String, page: Int): Request { - return GET("$baseUrl$mangaUrl?page=$page", headers) - } - - override fun chapterListParse(response: Response): List<SChapter> { - var document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - var nextPage = 2 - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - while (document.select(paginationNextPageSelector).isNotEmpty()) { - val currentPage = document.select(".nav-link.active").attr("href") - document = client.newCall(chapterListRequest(currentPage, nextPage)).execute().asJsoup() - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - nextPage++ - } - - return chapters - } - - private val paginationNextPageSelector = popularMangaNextPageSelector() - - override fun chapterListSelector() = ".chapter-container > .row:not(:first-child,.d-none)" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val selectorEncoded1 = "TG1OdmJDro" + "wQWdJQ2NvbEFro" + "wnSUNBZ0lDQWdJQ0FrownSUNj" + "b2xBZ0lDQWdJQ0rowFnSUNBZ0xuSnZkeWN" + - "vbEFnSUNBZ0rowlDQWdJRDRjb2xnSUNBZ0xt" + "TnZiQzFzWnkwMUlDQWrowdJRDRnSU" + "NBZ1lUcHViM1FvT21acGNu" + "TjBMV05rowvYVd4a0tTd2dJY29s" + - "Q0FnSUM1amIyd2dJQ0Fn" + "SUNBdWNtOTNJQ0FnSWNvbENB" + "Z0lDQWdMbU52row" + "YkMxc1p5MDFJQ0FnY2" + - "9sSUNBZ0lDQWdJR0ZiYUhKbFppb" + "zlZMmhoY0hSbGNpMWRXY2ro" + "w9sMmh5WldZcVBWd3ZZMmhoY0hSbGN" + "sMDZhR0Z6S2NvbEdScG" + "Rpaz0=" - val selectorEncoded2 = String(Base64.decode(selectorEncoded1.replace("row", ""), Base64.DEFAULT)) - val selectorDecoded = String(Base64.decode(selectorEncoded2.replace("col", ""), Base64.DEFAULT)) - setUrlWithoutDomain(element.select(selectorDecoded).attr("href")) - name = "Chapter " + url.substringAfter("chapter-") - date_upload = parseChapterDate(element.select(".ml-1").attr("title")) - } - - private fun parseChapterDate(date: String): Long { - return try { - SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.US).parse(date)?.time ?: 0 - } catch (_: Exception) { - 0L - } - } - - // pages - override fun pageListParse(document: Document): List<Page> { - return document.select( - "img[src*=manga],img[src*=chapter],div>div>img[src]" - ).mapIndexed { i, element -> - Page(i, "", element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") -} diff --git a/src/en/eggporncomics/AndroidManifest.xml b/src/en/eggporncomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/eggporncomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/eggporncomics/build.gradle b/src/en/eggporncomics/build.gradle deleted file mode 100644 index 11f6f076c..000000000 --- a/src/en/eggporncomics/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Eggporncomics' - pkgNameSuffix = 'en.eggporncomics' - extClass = '.Eggporncomics' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/eggporncomics/res/mipmap-hdpi/ic_launcher.png b/src/en/eggporncomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a04710a0b..000000000 Binary files a/src/en/eggporncomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/eggporncomics/res/mipmap-mdpi/ic_launcher.png b/src/en/eggporncomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4aac20b94..000000000 Binary files a/src/en/eggporncomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/eggporncomics/res/mipmap-xhdpi/ic_launcher.png b/src/en/eggporncomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5a230fc85..000000000 Binary files a/src/en/eggporncomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/eggporncomics/res/mipmap-xxhdpi/ic_launcher.png b/src/en/eggporncomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e2c17c03a..000000000 Binary files a/src/en/eggporncomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/eggporncomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/eggporncomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e64583d28..000000000 Binary files a/src/en/eggporncomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/eggporncomics/res/web_hi_res_512.png b/src/en/eggporncomics/res/web_hi_res_512.png deleted file mode 100644 index 08d663d37..000000000 Binary files a/src/en/eggporncomics/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/eggporncomics/src/eu/kanade/tachiyomi/extension/en/eggporncomics/Eggporncomics.kt b/src/en/eggporncomics/src/eu/kanade/tachiyomi/extension/en/eggporncomics/Eggporncomics.kt deleted file mode 100644 index 0f86d02cd..000000000 --- a/src/en/eggporncomics/src/eu/kanade/tachiyomi/extension/en/eggporncomics/Eggporncomics.kt +++ /dev/null @@ -1,268 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.eggporncomics - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.util.Calendar - -@Nsfw -class Eggporncomics : ParsedHttpSource() { - - override val name = "Eggporncomics" - - override val baseUrl = "https://eggporncomics.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - // couldn't find a page with popular comics, defaulting to the popular "anime-comics" category - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/category/1/anime-comics?page=$page", headers) - } - - override fun popularMangaSelector() = "div.preview:has(div.name)" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a:has(img)").let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - thumbnail_url = it.select("img").attr("abs:src") - } - } - } - - override fun popularMangaNextPageSelector() = "ul.ne-pe li.next:not(.disabled)" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest-comics?page=$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservable().doOnNext { response -> - if (!response.isSuccessful) { - // when combining a category filter and comics filter, if there are no results the source - // issues a 404, override that so as not to confuse users - if (response.request().url().toString().contains("category-tag") && response.code() == 404) { - Observable.just(MangasPage(emptyList(), false)) - } else { - response.close() - throw Exception("HTTP error ${response.code()}") - } - } - } - .map { response -> - searchMangaParse(response) - } - } - - private val queryRegex = Regex("""[\s']""") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/search/${query.replace(queryRegex, "-")}?page=$page", headers) - } else { - val url = HttpUrl.parse(baseUrl)!!.newBuilder() - val filterList = if (filters.isEmpty()) getFilterList() else filters - val category = filterList.find { it is CategoryFilter } as UriPartFilter - val comics = filterList.find { it is ComicsFilter } as UriPartFilter - - when { - category.isNotNull() && comics.isNotNull() -> { - url.addPathSegments("category-tag/${category.toUriPart()}/${comics.toUriPart()}") - } - category.isNotNull() -> { - url.addPathSegments("category/${category.toUriPart()}") - } - comics.isNotNull() -> { - url.addPathSegments("comics-tag/${comics.toUriPart()}") - } - } - - url.addQueryParameter("page", page.toString()) - - GET(url.toString(), headers) - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - private val descriptionPrefixRegex = Regex(""":.*""") - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - thumbnail_url = document.select(pageListSelector).first().toFullSizeImage() - description = document.select("div.links ul").joinToString("\n") { element -> - element.select("a") - .joinToString(prefix = element.select("span").text().replace(descriptionPrefixRegex, ": ")) { it.text() } - } - } - } - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - return listOf( - SChapter.create().apply { - setUrlWithoutDomain(response.request().url().toString()) - name = "Chapter" - date_upload = response.asJsoup().select("div.info > div.meta li:contains(days ago)").firstOrNull() - ?.let { Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, -(it.text().substringBefore(" ").toIntOrNull() ?: 0)) }.timeInMillis } - ?: 0 - } - ) - } - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - // Pages - - private fun Element.toFullSizeImage() = this.attr("abs:src").replace("thumb300_", "") - - private val pageListSelector = "div.grid div.image img" - - override fun pageListParse(document: Document): List<Page> { - return document.select(pageListSelector).mapIndexed { i, img -> - Page(i, "", img.toFullSizeImage()) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("Leave query blank to use filters"), - Filter.Separator(), - CategoryFilter("Category", getCategoryList), - ComicsFilter("Comics", getComicsList) - ) - - private class CategoryFilter(name: String, vals: Array<Pair<String, String?>>) : UriPartFilter(name, vals) - private class ComicsFilter(name: String, vals: Array<Pair<String, String?>>) : UriPartFilter(name, vals) - - private val getCategoryList: Array<Pair<String, String?>> = arrayOf( - Pair("Any", null), - Pair("3d comics", "7/3d-comics"), - Pair("8muses", "18/8muses"), - Pair("Anime", "1/anime"), - Pair("Cartoon", "2/cartoon"), - Pair("Dickgirls & Shemale", "6/dickgirls-shemale"), - Pair("Furry", "4/furry"), - Pair("Games comics", "3/games-comics"), - Pair("Hentai manga", "10/hentai-manga"), - Pair("Interracial", "14/interracial"), - Pair("Milf", "11/milf"), - Pair("Mindcontrol", "15/mindcontrol"), - Pair("Porn Comix", "16/porn-comix"), - Pair("Western", "12/western"), - Pair("Yaoi/Gay", "8/yaoigay"), - Pair("Yuri and Lesbian", "9/yuri-and-lesbian") - ) - - private val getComicsList: Array<Pair<String, String?>> = arrayOf( - Pair("Any", null), - Pair("3d", "85/3d"), - Pair("Adventure Time", "2950/adventure-time"), - Pair("Anal", "13/anal"), - Pair("Ben 10", "641/ben10"), - Pair("Big boobs", "3025/big-boobs"), - Pair("Big breasts", "6/big-breasts"), - Pair("Big cock", "312/big-cock"), - Pair("Bigass", "604/big-ass-porn-comics-new"), - Pair("Black cock", "2990/black-cock"), - Pair("Blowjob", "7/blowjob"), - Pair("Bondage", "24/bondage"), - Pair("Breast expansion hentai", "102/breast-expansion-new"), - Pair("Cumshot", "427/cumshot"), - Pair("Dark skin", "29/dark-skin"), - Pair("Dofantasy", "1096/dofantasy"), - Pair("Double penetration", "87/double-penetration"), - Pair("Doujin moe", "3028/doujin-moe"), - Pair("Erotic", "602/erotic"), - Pair("Fairy tail porn", "3036/fairy-tail"), - Pair("Fakku", "1712/Fakku-Comics-new"), - Pair("Fakku comics", "1712/fakku-comics-new"), - Pair("Family Guy porn", "774/family-guy"), - Pair("Fansadox", "1129/fansadox-collection"), - Pair("Feminization", "385/feminization"), - Pair("Forced", "315/forced"), - Pair("Full color", "349/full-color"), - Pair("Furry", "19/furry"), - Pair("Futanari", "2994/futanari"), - Pair("Group", "58/group"), - Pair("Hardcore", "304/hardcore"), - Pair("Harry Potter porn", "338/harry-potter"), - Pair("Hentai", "321/hentai"), - Pair("Incest", "3007/incest"), - Pair("Incest - Family Therapy Top", "3007/family-therapy-top"), - Pair("Incognitymous", "545/incognitymous"), - Pair("Interracical", "608/interracical"), - Pair("Jab Comix", "1695/JAB-Comics-NEW-2"), - Pair("Kaos comics", "467/kaos"), - Pair("Kim Possible porn", "788/kim-possible"), - Pair("Lesbian", "313/lesbian"), - Pair("Locofuria", "343/locofuria"), - Pair("Milf", "48/milf"), - Pair("Milftoon", "1678/milftoon-comics"), - Pair("Muscle", "2/muscle"), - Pair("Nakadashi", "10/nakadashi"), - Pair("PalComix", "373/palcomix"), - Pair("Pokemon hentai", "657/pokemon"), - Pair("Shadbase", "1717/shadbase-comics"), - Pair("Shemale", "126/shemale"), - Pair("Slut", "301/slut"), - Pair("Sparrow hentai", "3035/sparrow-hentai"), - Pair("Star Wars hentai", "1344/star-wars"), - Pair("Stockings", "51/stockings"), - Pair("Superheroine Central", "615/superheroine-central"), - Pair("The Cummoner", "3034/the-cummoner"), - Pair("The Rock Cocks", "3031/the-rock-cocks"), - Pair("ZZZ Comics", "1718/zzz-comics") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String?>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - fun isNotNull(): Boolean = toUriPart() != null - } -} diff --git a/src/en/existentialcomics/AndroidManifest.xml b/src/en/existentialcomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/existentialcomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/existentialcomics/build.gradle b/src/en/existentialcomics/build.gradle deleted file mode 100644 index af66fdc36..000000000 --- a/src/en/existentialcomics/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Existential Comics' - pkgNameSuffix = 'en.existentialcomics' - extClass = '.ExistentialComics' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/existentialcomics/res/mipmap-hdpi/ic_launcher.png b/src/en/existentialcomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b3dbbf2ff..000000000 Binary files a/src/en/existentialcomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/existentialcomics/res/mipmap-mdpi/ic_launcher.png b/src/en/existentialcomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0eb4e103c..000000000 Binary files a/src/en/existentialcomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/existentialcomics/res/mipmap-xhdpi/ic_launcher.png b/src/en/existentialcomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a5eca115b..000000000 Binary files a/src/en/existentialcomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/existentialcomics/res/mipmap-xxhdpi/ic_launcher.png b/src/en/existentialcomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ed9032355..000000000 Binary files a/src/en/existentialcomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/existentialcomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/existentialcomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1b6b7fdca..000000000 Binary files a/src/en/existentialcomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/existentialcomics/res/web_hi_res_512.png b/src/en/existentialcomics/res/web_hi_res_512.png deleted file mode 100644 index 4985c5a3b..000000000 Binary files a/src/en/existentialcomics/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/existentialcomics/src/eu/kanade/tachiyomi/extension/en/existentialcomics/ExistentialComics.kt b/src/en/existentialcomics/src/eu/kanade/tachiyomi/extension/en/existentialcomics/ExistentialComics.kt deleted file mode 100644 index 1b8e8c5e1..000000000 --- a/src/en/existentialcomics/src/eu/kanade/tachiyomi/extension/en/existentialcomics/ExistentialComics.kt +++ /dev/null @@ -1,86 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.existentialcomics - -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.online.ParsedHttpSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class ExistentialComics : ParsedHttpSource() { - - override val name = "Existential Comics" - - override val baseUrl = "https://existentialcomics.com" - - override val lang = "en" - - override val supportsLatest = false - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val manga = SManga.create().apply { - title = "Existential Comics" - artist = "Corey Mohler" - author = "Corey Mohler" - status = SManga.ONGOING - url = "/archive/byDate" - description = "A philosophy comic about the inevitable anguish of living a brief life in an absurd world. Also Jokes." - thumbnail_url = "https://i.ibb.co/pykMVYM/existential-comics.png" - } - - return Observable.just(MangasPage(arrayListOf(manga), false)) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun fetchMangaDetails(manga: SManga) = Observable.just(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).distinct().reversed() - } - - override fun chapterListSelector() = "div#date-comics ul li a:eq(0)" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.attr("href")) - chapter.name = element.text() - chapter.chapter_number = chapter.url.substringAfterLast("/").toFloat() - return chapter - } - - override fun pageListParse(document: Document) = document.select(".comicImg").mapIndexed { i, element -> Page(i, "", "https:" + element.attr("src").substring(1)) } - - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - override fun popularMangaSelector(): String = throw Exception("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun searchMangaSelector(): String = throw Exception("Not used") - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - - override fun popularMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - override fun latestUpdatesSelector(): String = throw Exception("Not used") -} diff --git a/src/en/explosm/AndroidManifest.xml b/src/en/explosm/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/explosm/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/explosm/build.gradle b/src/en/explosm/build.gradle deleted file mode 100644 index 7d80a8f6b..000000000 --- a/src/en/explosm/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Cyanide & Happiness' - pkgNameSuffix = 'en.explosm' - extClass = '.Explosm' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/explosm/res/mipmap-hdpi/ic_launcher.png b/src/en/explosm/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 367996349..000000000 Binary files a/src/en/explosm/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/explosm/res/mipmap-mdpi/ic_launcher.png b/src/en/explosm/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c99982ad9..000000000 Binary files a/src/en/explosm/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/explosm/res/mipmap-xhdpi/ic_launcher.png b/src/en/explosm/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 24d58943d..000000000 Binary files a/src/en/explosm/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/explosm/res/mipmap-xxhdpi/ic_launcher.png b/src/en/explosm/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 434532a0f..000000000 Binary files a/src/en/explosm/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/explosm/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/explosm/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a14197407..000000000 Binary files a/src/en/explosm/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/explosm/res/web_hi_res_512.png b/src/en/explosm/res/web_hi_res_512.png deleted file mode 100644 index 00e116d77..000000000 Binary files a/src/en/explosm/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/explosm/src/eu/kanade/tachiyomi/extension/en/explosm/Explosm.kt b/src/en/explosm/src/eu/kanade/tachiyomi/extension/en/explosm/Explosm.kt deleted file mode 100644 index b8f8160f8..000000000 --- a/src/en/explosm/src/eu/kanade/tachiyomi/extension/en/explosm/Explosm.kt +++ /dev/null @@ -1,114 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.explosm - -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale - -class Explosm : HttpSource() { - - override val name = "Cyanide & Happiness" - - override val baseUrl = "https://explosm.net" - - override val lang = "en" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - private fun createManga(element: Element): SManga { - return SManga.create().apply { - initialized = true - title = "C&H ${element.attr("id").substringAfter("panel")}" // year - setUrlWithoutDomain(element.select("li a").first().attr("href")) // January - thumbnail_url = "https://vhx.imgix.net/vitalyuncensored/assets/13ea3806-5ebf-4987-bcf1-82af2b689f77/S2E4_Still1.jpg" - } - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return (GET("$baseUrl/comics/archive", headers)) - } - - override fun popularMangaParse(response: Response): MangasPage { - val eachYearAsAManga = response.asJsoup().select("dd.accordion-navigation > div").map { createManga(it) } - - return MangasPage(eachYearAsAManga, false) - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(response: Response): SManga { - return createManga(response.asJsoup().select("div.content.active").first()) - } - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - - val januaryChapters = document.chaptersFromDocument() // should be at bottom of final returned list - - // get the rest of the year - val chapters = document.select("div.content.active li:not(.active) a").reversed().map { - client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup().chaptersFromDocument() - }.flatten() - - return chapters + januaryChapters - } - - private fun Document.chaptersFromDocument(): List<SChapter> { - return this.select("div.inner-wrap > div.row div.row.collapse").map { element -> - SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - element.select("div#comic-author").text().let { cName -> - name = cName - date_upload = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()) - .parse(cName.substringBefore(" "))?.time ?: 0L - } - } - } - } - - // Pages - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return Observable.just(listOf(Page(0, baseUrl + chapter.url))) - } - - override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(response: Response): String { - return response.asJsoup().select("div#comic-wrap img").attr("abs:src") - } - - override fun getFilterList() = FilterList() -} diff --git a/src/en/gunnerkriggcourt/AndroidManifest.xml b/src/en/gunnerkriggcourt/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/gunnerkriggcourt/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/gunnerkriggcourt/build.gradle b/src/en/gunnerkriggcourt/build.gradle deleted file mode 100644 index dd51a1820..000000000 --- a/src/en/gunnerkriggcourt/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Gunnerkrigg Court' - pkgNameSuffix = 'en.gunnerkriggcourt' - extClass = '.GunnerkriggCourt' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/gunnerkriggcourt/res/mipmap-hdpi/ic_launcher.png b/src/en/gunnerkriggcourt/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b048a4d88..000000000 Binary files a/src/en/gunnerkriggcourt/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gunnerkriggcourt/res/mipmap-mdpi/ic_launcher.png b/src/en/gunnerkriggcourt/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index dfee8838a..000000000 Binary files a/src/en/gunnerkriggcourt/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gunnerkriggcourt/res/mipmap-xhdpi/ic_launcher.png b/src/en/gunnerkriggcourt/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 25359fa42..000000000 Binary files a/src/en/gunnerkriggcourt/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gunnerkriggcourt/res/mipmap-xxhdpi/ic_launcher.png b/src/en/gunnerkriggcourt/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d94083bc2..000000000 Binary files a/src/en/gunnerkriggcourt/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gunnerkriggcourt/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/gunnerkriggcourt/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index be1318e2c..000000000 Binary files a/src/en/gunnerkriggcourt/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/gunnerkriggcourt/res/web_hi_res_512.png b/src/en/gunnerkriggcourt/res/web_hi_res_512.png deleted file mode 100644 index 3092aca32..000000000 Binary files a/src/en/gunnerkriggcourt/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/gunnerkriggcourt/src/eu/kanade/tachiyomi/extension/en/gunnerkriggcourt/GunnerkriggCourt.kt b/src/en/gunnerkriggcourt/src/eu/kanade/tachiyomi/extension/en/gunnerkriggcourt/GunnerkriggCourt.kt deleted file mode 100644 index 5379a3568..000000000 --- a/src/en/gunnerkriggcourt/src/eu/kanade/tachiyomi/extension/en/gunnerkriggcourt/GunnerkriggCourt.kt +++ /dev/null @@ -1,97 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.gunnerkriggcourt - -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.online.ParsedHttpSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class GunnerkriggCourt : ParsedHttpSource() { - override val name = "Gunnerkrigg Court" - override val baseUrl = "https://www.gunnerkrigg.com" - override val lang = "en" - override val supportsLatest = false - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val manga = SManga.create().apply { - title = name - artist = "Tom Siddell" - author = artist - status = SManga.ONGOING - url = "/archives/" - description = "Gunnerkrigg Court is a Science Fantasy webcomic by Tom Siddell about a" + - " strange young girl attending an equally strange school. The intricate story is " + - "deeply rooted in world mythology, but has a strong focus on science (chemistry " + - "and robotics, most prominently) as well.\n\n" + - "Antimony Carver begins classes at the eponymous U.K. Boarding School, and soon " + - "notices that strange events are happening: a shadow creature follows her around;" + - " a robot calls her \"Mummy\"; a Rogat Orjak smashes in the dormitory roof; odd " + - "birds, ticking like clockwork, stand guard in out-of-the-way places.\n\n" + - "Stranger still, in the middle of all this, Annie remains calm and polite to a fault." - thumbnail_url = "https://i.imgur.com/g2ukAIKh.jpgss" - } - - return Observable.just(MangasPage(arrayListOf(manga).reversed(), false)) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return Observable.just(manga) - } - - override fun chapterListSelector() = - """div.chapters option[value~=\d*]""" - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - chapter_number = element.attr("value").toFloat() - setUrlWithoutDomain("/?p=" + element.attr("value")) - name = element.parent().previousElementSibling().text() + " (" + chapter_number.toInt() + ")" - // date_upload // Find by using hovertext above "Tom" on actual comic page - } - } - - override fun pageListParse(document: Document): List<Page> = - document.select(".comic_image").mapIndexed { i, element -> Page(i, "", baseUrl + element.attr("src")) } - - // <editor-fold desc="Not Used"> - override fun imageUrlParse(document: Document): String = throw Exception("Not Used") - - override fun popularMangaSelector(): String = throw Exception("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun searchMangaSelector(): String = throw Exception("Not used") - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - - override fun popularMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - override fun latestUpdatesSelector(): String = throw Exception("Not used") - // </editor-fold> -} diff --git a/src/en/guya/AndroidManifest.xml b/src/en/guya/AndroidManifest.xml deleted file mode 100644 index bea13c5a9..000000000 --- a/src/en/guya/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".en.guya.GuyaUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="guya.moe" - android:pathPattern="/read/manga/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> \ No newline at end of file diff --git a/src/en/guya/build.gradle b/src/en/guya/build.gradle deleted file mode 100644 index 2c407b4e8..000000000 --- a/src/en/guya/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Guya' - pkgNameSuffix = "en.guya" - extClass = '.Guya' - extVersionCode = 18 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/guya/res/mipmap-hdpi/ic_launcher.png b/src/en/guya/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3fed53ada..000000000 Binary files a/src/en/guya/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/guya/res/mipmap-mdpi/ic_launcher.png b/src/en/guya/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b31500c56..000000000 Binary files a/src/en/guya/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/guya/res/mipmap-xhdpi/ic_launcher.png b/src/en/guya/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 325b12a21..000000000 Binary files a/src/en/guya/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/guya/res/mipmap-xxhdpi/ic_launcher.png b/src/en/guya/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 06c421da2..000000000 Binary files a/src/en/guya/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/guya/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/guya/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 76c5d0784..000000000 Binary files a/src/en/guya/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/guya/res/web_hi_res_512.png b/src/en/guya/res/web_hi_res_512.png deleted file mode 100644 index b367e0c8c..000000000 Binary files a/src/en/guya/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/guya/src/eu/kanade/tachiyomi/extension/en/guya/Guya.kt b/src/en/guya/src/eu/kanade/tachiyomi/extension/en/guya/Guya.kt deleted file mode 100644 index ec262b789..000000000 --- a/src/en/guya/src/eu/kanade/tachiyomi/extension/en/guya/Guya.kt +++ /dev/null @@ -1,627 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.guya - -import android.app.Application -import android.content.SharedPreferences -import android.os.Build -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import rx.Observable -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.HashMap - -open class Guya : ConfigurableSource, HttpSource() { - - final override val name = "Guya" - final override val baseUrl = "https://guya.moe" - final override val supportsLatest = false - final override val lang = "en" - - private val scanlatorCacheUrl = "$baseUrl/api/get_all_groups" - - override fun headersBuilder() = Headers.Builder().apply { - add( - "User-Agent", - "(Android ${Build.VERSION.RELEASE}; " + - "${Build.MANUFACTURER} ${Build.MODEL}) " + - "Tachiyomi/${BuildConfig.VERSION_NAME} " + - Build.ID - ) - } - - private val scanlators: ScanlatorStore = ScanlatorStore() - - // Preferences configuration - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - private val scanlatorPreference = "SCANLATOR_PREFERENCE" - - // Request builder for the "browse" page of the manga - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/api/get_all_series/", headers) - } - - // Gets the response object from the request - override fun popularMangaParse(response: Response): MangasPage { - val res = response.body()!!.string() - return parseManga(JSONObject(res)) - } - - // Overridden to use our overload - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return when { - manga.url.startsWith(PROXY_PREFIX) -> { - client.newCall(proxyChapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - proxyMangaDetailsParse(response, manga) - } - } - else -> { - client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response, manga) - } - } - } - } - - // Called when the series is loaded, or when opening in browser - override fun mangaDetailsRequest(manga: SManga): Request { - return when { - manga.url.startsWith(PROXY_PREFIX) -> proxySeriesRequest(manga.url, false) - else -> GET("$baseUrl/reader/series/${manga.url}/", headers) - } - } - - // Stub - override fun mangaDetailsParse(response: Response): SManga { - throw Exception("Unused") - } - - private fun mangaDetailsParse(response: Response, manga: SManga): SManga { - val res = response.body()!!.string() - return parseMangaFromJson(JSONObject(res), "", manga.title) - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return when { - manga.url.startsWith(PROXY_PREFIX) -> { - client.newCall(proxyChapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - proxyChapterListParse(response, manga) - } - } - else -> { - client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga) - } - } - } - } - - // Gets the chapter list based on the series being viewed - override fun chapterListRequest(manga: SManga): Request { - return GET("$baseUrl/api/series/${manga.url}/", headers) - } - - override fun chapterListParse(response: Response): List<SChapter> { - throw Exception("Unused") - } - - // Called after the request - private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { - val res = response.body()!!.string() - return parseChapterList(res, manga) - } - - // Overridden fetch so that we use our overloaded method instead - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return when { - chapter.url.startsWith(PROXY_PREFIX) -> { - client.newCall(proxyPageListRequest(chapter)) - .asObservableSuccess() - .map { response -> - proxyPageListParse(response, chapter) - } - } - else -> { - client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .map { response -> - pageListParse(response, chapter) - } - } - } - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET("$baseUrl/api/series/${chapter.url.split("/")[0]}/", headers) - } - - // Stub - override fun pageListParse(response: Response): List<Page> { - throw Exception("Unused") - } - - private fun pageListParse(response: Response, chapter: SChapter): List<Page> { - val res = response.body()!!.string() - - val json = JSONObject(res) - val chapterNum = chapter.name.split(" - ")[0] - val pages = json.getJSONObject("chapters") - .getJSONObject(chapterNum) - .getJSONObject("groups") - val metadata = JSONObject() - - metadata.put("chapter", chapterNum) - metadata.put("scanlator", scanlators.getKeyFromValue(chapter.scanlator.toString())) - metadata.put("slug", json.getString("slug")) - metadata.put( - "folder", - json.getJSONObject("chapters") - .getJSONObject(chapterNum) - .getString("folder") - ) - - return parsePageFromJson(pages, metadata) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return when { - query.startsWith(SLUG_PREFIX) -> { - val slug = query.removePrefix(SLUG_PREFIX) - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParseWithSlug(response, slug) - } - } - query.startsWith(PROXY_PREFIX) && query.contains("/") -> { - client.newCall(proxySearchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - proxySearchMangaParse(response, query) - } - } - else -> { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/api/get_all_series/", headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - throw Exception("Unused.") - } - - private fun searchMangaParseWithSlug(response: Response, slug: String): MangasPage { - val results = JSONObject(response.body()!!.string()) - val mangaIter = results.keys() - val truncatedJSON = JSONObject() - - while (mangaIter.hasNext()) { - val mangaTitle = mangaIter.next() - val mangaDetails = results.getJSONObject(mangaTitle) - - if (mangaDetails.get("slug") == slug) { - truncatedJSON.put(mangaTitle, mangaDetails) - } - } - - return parseManga(truncatedJSON) - } - - private fun searchMangaParse(response: Response, query: String): MangasPage { - val res = response.body()!!.string() - val json = JSONObject(res) - val truncatedJSON = JSONObject() - - val iter = json.keys() - - while (iter.hasNext()) { - val candidate = iter.next() - if (candidate.contains(query.toRegex(RegexOption.IGNORE_CASE))) { - truncatedJSON.put(candidate, json.get(candidate)) - } - } - - return parseManga(truncatedJSON) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val preference = androidx.preference.ListPreference(screen.context).apply { - key = "preferred_scanlator" - title = "Preferred scanlator" - entries = arrayOf<String>() - entryValues = arrayOf<String>() - for (key in scanlators.keys()) { - entries += scanlators.getValueFromKey(key) - entryValues += key - } - summary = "Current: %s\n\n" + - "This setting sets the scanlation group to prioritize " + - "on chapter refresh/update. It will get the next available if " + - "your preferred scanlator isn't an option (yet)." - - this.setDefaultValue("1") - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue.toString() - preferences.edit().putString(scanlatorPreference, selected).commit() - } - } - - screen.addPreference(preference) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val preference = ListPreference(screen.context).apply { - key = "preferred_scanlator" - title = "Preferred scanlator" - entries = arrayOf<String>() - entryValues = arrayOf<String>() - for (key in scanlators.keys()) { - entries += scanlators.getValueFromKey(key) - entryValues += key - } - summary = "Current: %s\n\n" + - "This setting sets the scanlation group to prioritize " + - "on chapter refresh/update. It will get the next available if " + - "your preferred scanlator isn't an option (yet)." - - this.setDefaultValue("1") - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue.toString() - preferences.edit().putString(scanlatorPreference, selected).commit() - } - } - - screen.addPreference(preference) - } - - // ---------------- Proxy methods ------------------ - - private fun proxySeriesRequest(query: String, api: Boolean = true): Request { - val res = query.removePrefix(PROXY_PREFIX) - val options = res.split("/") - val proxyType = options[0] - val slug = options[1] - return if (api) { - GET("$baseUrl/proxy/api/$proxyType/series/$slug/", headers) - } else { - GET("$baseUrl/proxy/$proxyType/$slug/", headers) - } - } - - private fun proxyMangaDetailsParse(response: Response, manga: SManga): SManga { - return mangaDetailsParse(response, manga) - } - - private fun proxyChapterListRequest(manga: SManga): Request { - return proxySeriesRequest(manga.url) - } - - private fun proxyChapterListParse(response: Response, manga: SManga): List<SChapter> { - return chapterListParse(response, manga) - } - - private fun proxyPageListRequest(chapter: SChapter): Request { - val proxyUrl = chapter.url.removePrefix(PROXY_PREFIX) - return when { - proxyUrl.startsWith(NESTED_PROXY_API_PREFIX) -> { - GET("$baseUrl$proxyUrl", headers) - } - else -> proxySeriesRequest(chapter.url) - } - } - - private fun proxyPageListParse(response: Response, chapter: SChapter): List<Page> { - val res = response.body()!!.string() - val pages = if (chapter.url.removePrefix(PROXY_PREFIX).startsWith(NESTED_PROXY_API_PREFIX)) { - JSONArray(res) - } else { - val json = JSONObject(res) - val metadata = chapter.url.split("/").takeLast(2) - val chapterNum = metadata[0] - val groupNum = metadata[1] - json.getJSONObject("chapters") - .getJSONObject(chapterNum) - .getJSONObject("groups") - .getJSONArray(groupNum) - } - val pageArray = ArrayList<Page>() - for (i in 0 until pages.length()) { - val page = if (pages.optJSONObject(i) != null) { - pages.getJSONObject(i).getString("src") - } else { - pages[i] - } - pageArray.add(Page(i + 1, "", page.toString())) - } - return pageArray - } - - private fun proxySearchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return proxySeriesRequest(query) - } - - private fun proxySearchMangaParse(response: Response, query: String): MangasPage { - return MangasPage( - arrayListOf(parseMangaFromJson(JSONObject(response.body()!!.string()), query)), - false - ) - } - - // ------------- Helpers and whatnot --------------- - - private fun parseChapterList(payload: String, manga: SManga): List<SChapter> { - val sortKey = "preferred_sort" - val response = JSONObject(payload) - val chapters = response.getJSONObject("chapters") - val mapping = response.getJSONObject("groups") - - val chapterList = ArrayList<SChapter>() - - val iter = chapters.keys() - - while (iter.hasNext()) { - val chapterNum = iter.next() - val chapterObj = chapters.getJSONObject(chapterNum) - when { - chapterObj.has(sortKey) -> { - chapterList.add( - parseChapterFromJson( - chapterObj, - chapterNum, - chapterObj.getJSONArray(sortKey), - response.getString("slug") - ) - ) - } - response.has(sortKey) -> { - chapterList.add( - parseChapterFromJson( - chapterObj, - chapterNum, - response.getJSONArray(sortKey), - response.getString("slug") - ) - ) - } - else -> { - val groups = chapterObj.getJSONObject("groups") - val groupsIter = groups.keys() - - while (groupsIter.hasNext()) { - val chapter = SChapter.create() - val groupNum = groupsIter.next() - - chapter.scanlator = mapping.getString(groupNum) - if (chapterObj.has("release_date")) { - chapter.date_upload = - chapterObj.getJSONObject("release_date").getLong(groupNum) * 1000 - } - chapter.name = chapterNum + " - " + chapterObj.getString("title") - chapter.chapter_number = chapterNum.toFloat() - chapter.url = - if (groups.optJSONArray(groupNum) != null) { - val mangaUrl = manga.url - "$mangaUrl/$chapterNum/$groupNum" - } else { - val url = groups.getString(groupNum) - "$PROXY_PREFIX$url" - } - chapterList.add(chapter) - } - } - } - } - - return chapterList.reversed() - } - - // Helper function to get all the listings - private fun parseManga(payload: JSONObject): MangasPage { - val mangas = ArrayList<SManga>() - - val iter = payload.keys() - - while (iter.hasNext()) { - val series = iter.next() - val json = payload.getJSONObject(series) - val manga = parseMangaFromJson(json, "", series) - mangas.add(manga) - } - - return MangasPage(mangas, false) - } - - // Takes a json of the manga to parse - private fun parseMangaFromJson(json: JSONObject, slug: String, title: String = ""): SManga { - val manga = SManga.create() - manga.title = if (title.isNotEmpty()) title else json.getString("title") - manga.artist = json.optString("artist", "Unknown") - manga.author = json.optString("author", "Unknown") - manga.description = json.optString("description", "None") - manga.url = if (slug.startsWith(PROXY_PREFIX)) slug else json.getString("slug") - - val cover = json.optString("cover", "") - manga.thumbnail_url = if (cover.startsWith("http")) cover else "$baseUrl/$cover" - return manga - } - - private fun parseChapterFromJson(json: JSONObject, num: String, sort: JSONArray, slug: String): SChapter { - val chapter = SChapter.create() - - // Get the scanlator info based on group ranking; do it first since we need it later - val firstGroupId = getBestScanlator(json.getJSONObject("groups"), sort) - chapter.scanlator = scanlators.getValueFromKey(firstGroupId) - chapter.date_upload = json.getJSONObject("release_date").getLong(firstGroupId) * 1000 - chapter.name = num + " - " + json.getString("title") - chapter.chapter_number = num.toFloat() - chapter.url = "$slug/$num/$firstGroupId" - - return chapter - } - - private fun parsePageFromJson(json: JSONObject, metadata: JSONObject): List<Page> { - val pages = json.getJSONArray(metadata.getString("scanlator")) - val pageArray = ArrayList<Page>() - - for (i in 0 until pages.length()) { - val page = Page( - i + 1, - "", - pageBuilder( - metadata.getString("slug"), - metadata.getString("folder"), - pages[i].toString(), - metadata.getString("scanlator") - ) - ) - pageArray.add(page) - } - - return pageArray - } - - private fun getBestScanlator(json: JSONObject, sort: JSONArray): String { - val preferred = preferences.getString(scanlatorPreference, null) - - if (preferred != null && json.has(preferred)) { - return preferred - } else { - for (i in 0 until sort.length()) { - if (json.has(sort.get(i).toString())) { - return sort.get(i).toString() - } - } - // If all fails, fall-back to the next available key - return json.keys().next() - } - } - - private fun pageBuilder(slug: String, folder: String, filename: String, groupId: String): String { - return "$baseUrl/media/manga/$slug/chapters/$folder/$groupId/$filename" - } - - inner class ScanlatorStore { - private val scanlatorMap = HashMap<String, String>() - private val TOTAL_RETRIES = 10 - private var retryCount = 0 - - init { - update(false) - } - - fun getKeyFromValue(value: String): String { - update() - for (key in scanlatorMap.keys) { - if (scanlatorMap[key].equals(value)) { - return key - } - } - // Fall back to value as key if endpoint fails - return value - } - - fun getValueFromKey(key: String): String { - update() - // Fallback to key as value if endpoint fails - return if (!scanlatorMap[key].isNullOrEmpty()) - scanlatorMap[key].toString() else key - } - - fun keys(): MutableSet<String> { - update() - return scanlatorMap.keys - } - - private fun onResponse(response: Response) { - if (!response.isSuccessful) { - retryCount++ - } else { - val json = JSONObject(response.body()!!.string()) - val iter = json.keys() - while (iter.hasNext()) { - val scanId = iter.next() - scanlatorMap[scanId] = json.getString(scanId) - } - } - } - - private fun onError(error: Throwable) { - // Do nothing for now - } - - private fun update(blocking: Boolean = true) { - if (scanlatorMap.isEmpty() && retryCount < TOTAL_RETRIES) { - try { - val call = client.newCall(GET(scanlatorCacheUrl, headers)) - .asObservable() - - if (blocking) { - call.toBlocking() - .subscribe({ res -> onResponse(res) }, { err -> onError(err) }) - } else { - call.subscribeOn(Schedulers.io()) - .subscribe({ res -> onResponse(res) }, { err -> onError(err) }) - } - } catch (e: Exception) { - // Prevent the extension from failing to load - } - } - } - } - - // ----------------- Things we aren't supporting ----------------- - - override fun imageUrlParse(response: Response): String { - throw Exception("imageUrlParse not supported.") - } - - override fun latestUpdatesRequest(page: Int): Request { - throw Exception("Latest updates not supported.") - } - - override fun latestUpdatesParse(response: Response): MangasPage { - throw Exception("Latest updates not supported.") - } - - companion object { - const val SLUG_PREFIX = "slug:" - const val PROXY_PREFIX = "proxy:" - const val NESTED_PROXY_API_PREFIX = "/proxy/api/" - } -} diff --git a/src/en/guya/src/eu/kanade/tachiyomi/extension/en/guya/GuyaUrlActivity.kt b/src/en/guya/src/eu/kanade/tachiyomi/extension/en/guya/GuyaUrlActivity.kt deleted file mode 100644 index 23d71c3b5..000000000 --- a/src/en/guya/src/eu/kanade/tachiyomi/extension/en/guya/GuyaUrlActivity.kt +++ /dev/null @@ -1,57 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.guya - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Accepts https://guya.moe/read/manga/xyz intents - * - * Added due to requests from various users to allow for opening of titles when given the - * Guya URL whilst on mobile. - */ -class GuyaUrlActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val host = intent?.data?.host - val pathSegments = intent?.data?.pathSegments - - if (host != null && pathSegments != null) { - val query = fromGuya(pathSegments) - - if (query == null) { - Log.e("GuyaUrlActivity", "Unable to parse URI from intent $intent") - finish() - exitProcess(1) - } - - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", query) - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("GuyaUrlActivity", e.toString()) - } - } - - finish() - exitProcess(0) - } - - private fun fromGuya(pathSegments: MutableList<String>): String? { - return if (pathSegments.size >= 3) { - val slug = pathSegments[2] - "${Guya.SLUG_PREFIX}$slug" - } else { - null - } - } -} diff --git a/src/en/hbrowse/AndroidManifest.xml b/src/en/hbrowse/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/hbrowse/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/hbrowse/build.gradle b/src/en/hbrowse/build.gradle deleted file mode 100644 index 9c6770a60..000000000 --- a/src/en/hbrowse/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HBrowse' - pkgNameSuffix = 'en.hbrowse' - extClass = '.HBrowse' - extVersionCode = 4 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/hbrowse/res/mipmap-hdpi/ic_launcher.png b/src/en/hbrowse/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b5a43f854..000000000 Binary files a/src/en/hbrowse/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hbrowse/res/mipmap-mdpi/ic_launcher.png b/src/en/hbrowse/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b3f8bf5eb..000000000 Binary files a/src/en/hbrowse/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hbrowse/res/mipmap-xhdpi/ic_launcher.png b/src/en/hbrowse/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index de4b9ad1d..000000000 Binary files a/src/en/hbrowse/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hbrowse/res/mipmap-xxhdpi/ic_launcher.png b/src/en/hbrowse/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index bd90e5c50..000000000 Binary files a/src/en/hbrowse/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hbrowse/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/hbrowse/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 251593a90..000000000 Binary files a/src/en/hbrowse/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hbrowse/res/web_hi_res_512.png b/src/en/hbrowse/res/web_hi_res_512.png deleted file mode 100644 index 3a020d8c5..000000000 Binary files a/src/en/hbrowse/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/hbrowse/src/eu/kanade/tachiyomi/extension/en/hbrowse/HBrowse.kt b/src/en/hbrowse/src/eu/kanade/tachiyomi/extension/en/hbrowse/HBrowse.kt deleted file mode 100644 index 391049e02..000000000 --- a/src/en/hbrowse/src/eu/kanade/tachiyomi/extension/en/hbrowse/HBrowse.kt +++ /dev/null @@ -1,217 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.hbrowse - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.CookieJar -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.io.IOException - -@Nsfw -class HBrowse : ParsedHttpSource() { - - override val name = "HBrowse" - - override val baseUrl = "https://www.hbrowse.com" - - override val lang = "en" - - override val supportsLatest = true - - // Clients - - private lateinit var phpSessId: String - - private val searchClient = OkHttpClient().newBuilder() - .followRedirects(false) - .cookieJar(CookieJar.NO_COOKIES) - .build() - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .followRedirects(false) - .addInterceptor { chain -> - val originalRequest = chain.request() - when { - originalRequest.url().toString() == searchUrl -> { - phpSessId = searchClient.newCall(originalRequest).execute() - .headers("Set-Cookie") - .firstOrNull { it.contains("PHPSESSID") } - ?.toString() - ?.substringBefore(";") - ?: throw IOException("PHPSESSID missing") - - val newHeaders = headersBuilder() - .add("Cookie", phpSessId) - - val contentLength = originalRequest.body()!!.contentLength() - - searchClient.newCall(GET("$baseUrl/${if (contentLength > 8000) "result" else "search"}/1", newHeaders.build())).execute() - } - originalRequest.url().toString().contains(nextSearchPageUrlRegex) -> { - searchClient.newCall(originalRequest).execute() - } - else -> chain.proceed(originalRequest) - } - } - .build() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/browse/title/rank/DESC/$page", headers) - } - - override fun popularMangaSelector() = "table.thumbTable tbody" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.thumbDiv a").let { - setUrlWithoutDomain(it.attr("href")) - title = it.attr("title").substringAfter("\'").substringBeforeLast("\'") - } - thumbnail_url = element.select("img.thumbImg").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "a[title^=\"jump to next\"]" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/browse/title/date/DESC/$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - private val searchUrl = "$baseUrl/content/process.php" - private val nextSearchPageUrlRegex = Regex("""(/search/|/result/)""") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filterList = if (filters.isEmpty()) getFilterList() else filters - - return if (page == 1) { - val rBody = FormBody.Builder().apply { - if (query.isNotBlank()) { - add("type", "search") - add("needle", query) - } else { - add("type", "advance") - filterList.filterIsInstance<AdvancedFilter>() - .flatMap { it.vals } - .forEach { filter -> add(filter.formName, filter.formValue()) } - } - } - POST(searchUrl, headers, rBody.build()) - } else { - val url = "$baseUrl/${if (query.isNotBlank()) "search" else "result"}/$page" - val nextPageHeaders = headersBuilder().add("Cookie", phpSessId).build() - GET(url, nextPageHeaders) - } - } - - override fun searchMangaSelector() = "tbody > tr td.browseTitle a" - - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.text() - thumbnail_url = "$baseUrl/thumbnails/${url.removePrefix("/").substringBefore("/")}_1.jpg" - } - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET("$baseUrl/thumbnails${manga.url}") - } - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - with(document.select("div#main").first()) { - title = select("td:contains(title) + td.listLong").first().text() - artist = select("td:contains(artist) + td.listLong").text() - genre = select("td:contains(genre) + td.listLong").joinToString { it.text() } - description = select("tr:has(.listLong)") - .filterNot { it.select("td:first-child").text().contains(Regex("""(Title|Artist|Genre)""")) } - .joinToString("\n") { tr -> tr.select("td").joinToString(": ") { it.text() } } - thumbnail_url = select("tbody img").first().attr("abs:src") - } - } - } - - // Chapters - - override fun chapterListSelector() = "h2:contains(read manga online) + table a:contains(chapter)" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - name = element.text().substringAfter("View ") - setUrlWithoutDomain(element.attr("href")) - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("td.pageList a").mapIndexed { i, element -> - Page(i, element.attr("abs:href")) - } - } - - override fun imageUrlParse(document: Document): String { - return document.select("img#mangaImage").attr("abs:src") - } - - // Filters - - override fun getFilterList(): FilterList { - return FilterList( - listOf(Filter.Header("Can't combine with text search!"), Filter.Separator()) + - advFilterMap.map { AdvancedFilter(getAdvTriStateList(it.key, it.value.split(", "))) } - ) - } - - private class AdvTriStateFilter(val groupName: String, name: String) : Filter.TriState(name) { - val formName = "${groupName[0].toLowerCase() + groupName.drop(1).replace(" ", "")}_$name" - fun formValue() = when { - this.isIncluded() -> "y" - this.isExcluded() -> "n" - else -> "" - } - } - private class AdvancedFilter(val vals: List<AdvTriStateFilter>) : Filter.Group<AdvTriStateFilter>(vals.first().groupName, vals) - - private val advFilterMap = mapOf( - Pair("Genre", "action, adventure, anime, bizarre, comedy, drama, fantasy, gore, historic, horror, medieval, modern, myth, psychological, romance, school_life, scifi, supernatural, video_game, visual_novel"), - Pair("Type", "anthology, bestiality, dandere, deredere, deviant, fully_colored, furry, futanari, gender_bender, guro, harem, incest, kuudere, lolicon, long_story, netorare, non-con, partly_colored, reverse_harem, ryona, short_story, shotacon, transgender, tsundere, uncensored, vanilla, yandere, yaoi, yuri"), - Pair("Setting", "amusement_park, attic, automobile, balcony, basement, bath, beach, bedroom, cabin, castle, cave, church, classroom, deck, dining_room, doctors, dojo, doorway, dream, dressing_room, dungeon, elevator, festival, gym, haunted_building, hospital, hotel, hot_springs, kitchen, laboratory, library, living_room, locker_room, mansion, office, other, outdoor, outer_space, park, pool, prison, public, restaurant, restroom, roof, sauna, school, school_nurses_office, shower, shrine, storage_room, store, street, teachers_lounge, theater, tight_space, toilet, train, transit, virtual_reality, warehouse, wilderness"), - Pair("Fetish", "androphobia, apron, assertive_girl, bikini, bloomers, breast_expansion, business_suit, chastity_device, chinese_dress, christmas, collar, corset, cosplay_(female), cosplay_(male), crossdressing_(female), crossdressing_(male), eye_patch, food, giantess, glasses, gothic_lolita, gyaru, gynophobia, high_heels, hot_pants, impregnation, kemonomimi, kimono, knee_high_socks, lab_coat, latex, leotard, lingerie, maid_outfit, mother_and_daughter, none, nonhuman_girl, olfactophilia, pregnant, rich_girl, school_swimsuit, shy_girl, sisters, sleeping_girl, sporty, stockings, strapon, student_uniform, swimsuit, tanned, tattoo, time_stop, twins_(coed), twins_(female), twins_(male), uniform, wedding_dress"), - Pair("Role", "alien, android, angel, athlete, bride, bunnygirl, cheerleader, delinquent, demon, doctor, dominatrix, escort, foreigner, ghost, housewife, idol, magical_girl, maid, mamono, massagist, miko, mythical_being, neet, nekomimi, newlywed, ninja, normal, nun, nurse, office_lady, other, police, priest, princess, queen, school_nurse, scientist, sorcerer, student, succubus, teacher, tomboy, tutor, waitress, warrior, witch"), - Pair("Relationship", "acquaintance, anothers_daughter, anothers_girlfriend, anothers_mother, anothers_sister, anothers_wife, aunt, babysitter, childhood_friend, classmate, cousin, customer, daughter, daughter-in-law, employee, employer, enemy, fiance, friend, friends_daughter, friends_girlfriend, friends_mother, friends_sister, friends_wife, girlfriend, landlord, manager, master, mother, mother-in-law, neighbor, niece, none, older_sister, patient, pet, physician, relative, relatives_friend, relatives_girlfriend, relatives_wife, servant, server, sister-in-law, slave, stepdaughter, stepmother, stepsister, stranger, student, teacher, tutee, tutor, twin, underclassman, upperclassman, wife, workmate, younger_sister"), - Pair("Male Body", "adult, animal, animal_ears, bald, beard, dark_skin, elderly, exaggerated_penis, fat, furry, goatee, hairy, half_animal, horns, large_penis, long_hair, middle_age, monster, muscular, mustache, none, short, short_hair, skinny, small_penis, tail, tall, tanned, tan_line, teenager, wings, young"), - Pair("Female Body", "adult, animal_ears, bald, big_butt, chubby, dark_skin, elderly, elf_ears, exaggerated_breasts, fat, furry, hairy, hair_bun, half_animal, halo, hime_cut, horns, large_breasts, long_hair, middle_age, monster_girl, muscular, none, pigtails, ponytail, short, short_hair, skinny, small_breasts, tail, tall, tanned, tan_line, teenager, twintails, wings, young"), - Pair("Grouping", "foursome_(1_female), foursome_(1_male), foursome_(mixed), foursome_(only_female), one_on_one, one_on_one_(2_females), one_on_one_(2_males), orgy_(1_female), orgy_(1_male), orgy_(mainly_female), orgy_(mainly_male), orgy_(mixed), orgy_(only_female), orgy_(only_male), solo_(female), solo_(male), threesome_(1_female), threesome_(1_male), threesome_(only_female), threesome_(only_male)"), - Pair("Scene", "adultery, ahegao, anal_(female), anal_(male), aphrodisiac, armpit_sex, asphyxiation, blackmail, blowjob, bondage, breast_feeding, breast_sucking, bukkake, cheating_(female), cheating_(male), chikan, clothed_sex, consensual, cunnilingus, defloration, discipline, dominance, double_penetration, drunk, enema, exhibitionism, facesitting, fingering_(female), fingering_(male), fisting, footjob, grinding, groping, handjob, humiliation, hypnosis, intercrural, interracial_sex, interspecies_sex, lactation, lotion, masochism, masturbation, mind_break, nonhuman, orgy, paizuri, phone_sex, props, rape, reverse_rape, rimjob, sadism, scat, sex_toys, spanking, squirt, submission, sumata, swingers, tentacles, voyeurism, watersports, x-ray_blowjob, x-ray_sex"), - Pair("Position", "69, acrobat, arch, bodyguard, butterfly, cowgirl, dancer, deck_chair, deep_stick, doggy, drill, ex_sex, jockey, lap_dance, leg_glider, lotus, mastery, missionary, none, other, pile_driver, prison_guard, reverse_piggyback, rodeo, spoons, standing, teaspoons, unusual, victory") - ) - - private fun getAdvTriStateList(groupName: String, vals: List<String>) = vals.map { AdvTriStateFilter(groupName, it) } -} diff --git a/src/en/hentai2read/AndroidManifest.xml b/src/en/hentai2read/AndroidManifest.xml deleted file mode 100644 index a0c116d9e..000000000 --- a/src/en/hentai2read/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".en.hentai2read.Hentai2ReadActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="hentai2read.com" - android:pathPattern="/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/en/hentai2read/build.gradle b/src/en/hentai2read/build.gradle deleted file mode 100644 index ec16e74c2..000000000 --- a/src/en/hentai2read/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Hentai2Read' - pkgNameSuffix = 'en.hentai2read' - extClass = '.Hentai2Read' - extVersionCode = 12 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/hentai2read/res/mipmap-hdpi/ic_launcher.png b/src/en/hentai2read/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3ecaf3a52..000000000 Binary files a/src/en/hentai2read/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentai2read/res/mipmap-mdpi/ic_launcher.png b/src/en/hentai2read/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 6168571bd..000000000 Binary files a/src/en/hentai2read/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentai2read/res/mipmap-xhdpi/ic_launcher.png b/src/en/hentai2read/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 73b376749..000000000 Binary files a/src/en/hentai2read/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentai2read/res/mipmap-xxhdpi/ic_launcher.png b/src/en/hentai2read/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2524faed3..000000000 Binary files a/src/en/hentai2read/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentai2read/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/hentai2read/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d13f583bc..000000000 Binary files a/src/en/hentai2read/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentai2read/res/web_hi_res_512.png b/src/en/hentai2read/res/web_hi_res_512.png deleted file mode 100644 index 92a908055..000000000 Binary files a/src/en/hentai2read/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/hentai2read/src/eu/kanade/tachiyomi/extension/en/hentai2read/Hentai2Read.kt b/src/en/hentai2read/src/eu/kanade/tachiyomi/extension/en/hentai2read/Hentai2Read.kt deleted file mode 100644 index 391af2df5..000000000 --- a/src/en/hentai2read/src/eu/kanade/tachiyomi/extension/en/hentai2read/Hentai2Read.kt +++ /dev/null @@ -1,1753 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.hentai2read - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.lang.UnsupportedOperationException -import java.util.Calendar -import java.util.regex.Pattern - -@Nsfw -class Hentai2Read : ParsedHttpSource() { - - override val name = "Hentai2Read" - - override val baseUrl = "https://hentai2read.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - companion object { - const val imageBaseUrl = "https://static.hentaicdn.com/hentai" - - const val PREFIX_ID_SEARCH = "id:" - - val pagesUrlPattern by lazy { - Pattern.compile("""'images' : \[\"(.*?)[,]?\"\]""") - } - - val chapterDatePattern by lazy { - Pattern.compile("""about (\d+\s+\w+\s+ago)""") - } - - lateinit var nextSearchPage: String - } - - override fun popularMangaSelector() = "div.book-grid-item" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/hentai-list/all/any/all/most-popular/$page/", headers) - - override fun latestUpdatesRequest(page: Int) = - GET("$baseUrl/hentai-list/all/any/all/last-updated/$page/", headers) - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:data-src") - element.select("div.overlay-title a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - } - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a#js-linkNext" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(GET("$baseUrl/$id/", headers)).asObservableSuccess() - .map { MangasPage(listOf(mangaDetailsParse(it).apply { url = "/$id/" }), false) } - } else { - val search = requestSearch(page, query, filters) - client.newCall(search.first).asObservableSuccess() - .map { parseSearch(it, page, search.second) } - } - } - - private fun requestSearch(page: Int, query: String, filters: FilterList): Pair<Request, String?> { - val searchUrl = "$baseUrl/hentai-list/advanced-search" - var sortOrder: String? = null - - return if (page == 1) { - val form = FormBody.Builder().apply { - add("cmd_wpm_wgt_mng_sch_sbm", "Search") - add("txt_wpm_wgt_mng_sch_nme", "") - add("cmd_wpm_pag_mng_sch_sbm", "") - add("txt_wpm_pag_mng_sch_nme", query) - - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is MangaNameSelect -> add("cbo_wpm_pag_mng_sch_nme", filter.state.toString()) - is ArtistName -> add("txt_wpm_pag_mng_sch_ats", filter.state) - is ArtistNameSelect -> add("cbo_wpm_pag_mng_sch_ats", filter.state.toString()) - is CharacterName -> add("txt_wpm_pag_mng_sch_chr", filter.state) - is CharacterNameSelect -> add("cbo_wpm_pag_mng_sch_chr", filter.state.toString()) - is ReleaseYear -> add("txt_wpm_pag_mng_sch_rls_yer", filter.state) - is ReleaseYearSelect -> add("cbo_wpm_pag_mng_sch_rls_yer", filter.state.toString()) - is Status -> add("rad_wpm_pag_mng_sch_sts", filter.state.toString()) - is TagSearchMode -> add("rad_wpm_pag_mng_sch_tag_mde", arrayOf("and", "or")[filter.state]) - is TagList -> filter.state.forEach { tag -> - when (tag.state) { - Filter.TriState.STATE_INCLUDE -> add("chk_wpm_pag_mng_sch_mng_tag_inc[]", tag.id.toString()) - Filter.TriState.STATE_EXCLUDE -> add("chk_wpm_pag_mng_sch_mng_tag_exc[]", tag.id.toString()) - } - } - is SortOrder -> sortOrder = filter.toUriPart() - } - } - } - Pair(POST(searchUrl, headers, form.build()), sortOrder) - } else { - Pair(GET(nextSearchPage, headers), sortOrder) - } - } - - // If the user wants to search by a sort order other than alphabetical, we have to make another call - private fun parseSearch(response: Response, page: Int, sortOrder: String?): MangasPage { - val document = if (page == 1 && sortOrder != null) { - response.asJsoup().select("li.dropdown li:contains($sortOrder) a").first().attr("abs:href") - .let { client.newCall(GET(it, headers)).execute().asJsoup() } - } else { - response.asJsoup() - } - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = document.select(searchMangaNextPageSelector()).firstOrNull()?.let { - nextSearchPage = it.attr("abs:href") - true - } ?: false - - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("ul.list-simple-mini").first() - - val manga = SManga.create() - manga.author = infoElement.select("li:contains(Author) > a")?.text() - manga.artist = infoElement.select("li:contains(Artist) > a")?.text() - manga.genre = infoElement.select("li:contains(Category) > a, li:contains(Content) > a").joinToString(", ") { it.text() } - manga.description = buildDescription(infoElement) - manga.status = infoElement.select("li:contains(Status) > a")?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = document.select("a#js-linkNext > img")?.attr("src") - manga.title = document.select("h3.block-title > a").first().ownText().trim() - return manga - } - - private fun buildDescription(infoElement: Element): String { - - val topDescriptions = listOf( - Pair( - "Alternative Title", - infoElement.select("li").first().text().let { - if (it.trim() == "-") emptyList() - else it.split(", ") - } - ), - Pair( - "Storyline", - listOf(infoElement.select("li:contains(Storyline) > p")?.text()) - ) - ) - - val descriptions = listOf( - "Parody", - "Page", - "Character", - "Language" - ).map { it to infoElement.select("li:contains($it) a").map { v -> v.text() } } - .let { topDescriptions + it } // start with topDescriptions - .filter { !it.second.isNullOrEmpty() && it.second[0] != "-" } - .map { "${it.first}:\n${it.second.joinToString()}" } - - return descriptions.joinToString("\n\n") - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "ul.nav-chapters > li > div.media > a" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.ownText().trim() - date_upload = element.select("div > small").text()?.let { - val matcher = chapterDatePattern.matcher(it) - if (matcher.find()) { - parseChapterDate(matcher.group(1)!!) - } else { - 0L - } - } ?: 0L - } - } - - private fun parseChapterDate(date: String): Long { - val dateWords = date.split(" ") - if (dateWords.size == 3) { - val timeAgo = Integer.parseInt(dateWords[0]) - return Calendar.getInstance().apply { - when (dateWords[1]) { - "minute", "minutes" -> Calendar.MINUTE - "hour", "hours" -> Calendar.HOUR - "day", "days" -> Calendar.DAY_OF_YEAR - "week", "weeks" -> Calendar.WEEK_OF_YEAR - "month", "months" -> Calendar.MONTH - "year", "years" -> Calendar.YEAR - else -> null - }?.let { - add(it, -timeAgo) - } - }.timeInMillis - } - return 0L - } - - override fun pageListParse(response: Response): List<Page> { - val pages = mutableListOf<Page>() - val m = pagesUrlPattern.matcher(response.body()!!.string()) - var i = 0 - while (m.find()) { - m.group(1)?.split(",")?.forEach { - pages.add(Page(i++, "", imageBaseUrl + it.trim('"').replace("""\/""", "/"))) - } - } - return pages - } - - override fun pageListParse(document: Document): List<Page> = throw Exception("Not used") - - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - private class MangaNameSelect : Filter.Select<String>("Manga Name", arrayOf("Contains", "Starts With", "Ends With")) - private class ArtistName : Filter.Text("Artist") - private class ArtistNameSelect : Filter.Select<String>("Artist Name", arrayOf("Contains", "Starts With", "Ends With")) - private class CharacterName : Filter.Text("Character") - private class CharacterNameSelect : Filter.Select<String>("Character Name", arrayOf("Contains", "Starts With", "Ends With")) - private class ReleaseYear : Filter.Text("Release Year") - private class ReleaseYearSelect : Filter.Select<String>("Release Year", arrayOf("In", "Before", "After")) - private class Status : Filter.Select<String>("Status", arrayOf("Any", "Completed", "Ongoing")) - private class TagSearchMode : Filter.Select<String>("Tag Search Mode", arrayOf("AND", "OR")) - private class Tag(name: String, val id: Int) : Filter.TriState(name) - private class TagList(title: String, tags: List<Tag>) : Filter.Group<Tag>(title, tags) - private class SortOrder(values: Array<Pair<String, String?>>) : UriPartFilter("Order", values) - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String?>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - override fun getFilterList() = FilterList( - SortOrder(getSortOrder()), - MangaNameSelect(), - Filter.Separator(), - ArtistName(), - ArtistNameSelect(), - Filter.Separator(), - CharacterName(), - CharacterNameSelect(), - Filter.Separator(), - ReleaseYear(), - ReleaseYearSelect(), - Filter.Separator(), - Status(), - Filter.Separator(), - TagSearchMode(), - Filter.Separator(), - TagList("Categories", getCategoryList()), - Filter.Separator(), - TagList("Tags", getTagList()), - Filter.Separator(), - TagList("Doujins", getDoujinList()) - ) - - private fun getSortOrder() = arrayOf( - Pair("Alphabetical", null), - Pair("Most Popular", "most popular"), - Pair("Last Updated", "last updated") - ) - - // Categories : 27 - // $("div#tab-category > div:has(a.block)").map((i, el) => `Tag("${$(el).select("a").first().text().trim()}", ${$(el).find("input").first().attr("value")})`).get().sort().join(",\n") - // on https://hentai2read.com/hentai-search/" - private fun getCategoryList() = listOf( - Tag("Adult", 34), - Tag("Anal", 7), - Tag("Big Breasts", 20), - Tag("Comedy", 43), - Tag("Compilation", 46), - Tag("Doujinshi", 42), - Tag("Ecchi", 40), - Tag("Futanari", 14), - Tag("Gender Bender", 26), - Tag("Harem", 31), - Tag("Incest", 15), - Tag("Lactation", 16), - Tag("Licensed", 50), - Tag("Lolicon", 17), - Tag("Masturbation", 18), - Tag("Oneshot", 33), - Tag("Rape", 23), - Tag("Robotics", 30), - Tag("Romance", 41), - Tag("School Life", 48), - Tag("Serialized", 32), - Tag("Shotacon", 44), - Tag("Tentacles", 24), - Tag("Tragedy", 49), - Tag("Un-censored", 47), - Tag("Yaoi", 27), - Tag("Yuri", 28) - ) - - // Tags : 355 - // $("div#tab-tag > div:has(a.block)").map((i, el) => `Tag("${$(el).select("a").first().text().trim()}", ${$(el).find("input").first().attr("value")})`).get().sort().join(",\n") - // on https://hentai2read.com/hentai-search/" - // 360 Tags - private fun getTagList() = listOf( - Tag("Abortion", 529), - Tag("Absent Parents", 1423), - Tag("Abusive Lover", 878), - Tag("Abusive", 1587), - Tag("Adapted to H-Anime", 416), - Tag("Addiction", 1438), - Tag("Adopted Sister", 1634), - Tag("Adoption", 522), - Tag("Adoptive Siblings", 1451), - Tag("Adultery", 661), - Tag("Affair", 1459), - Tag("Aggressive Lover", 1599), - Tag("Ahegao", 1702), - Tag("Airheads", 1807), - Tag("All-Girls School", 347), - Tag("Alternative Ending", 666), - Tag("Anal Play", 1365), - Tag("Analingus (Rimjob)", 2298), - Tag("Angels", 1093), - Tag("Animal Girls", 1904), - Tag("Animal Transformation", 710), - Tag("Anthology", 589), - Tag("Anthropomorphism", 913), - Tag("Apron", 975), - Tag("Armpit Licking", 2360), - Tag("Armpit Sex", 1843), - Tag("Arranged Marriage", 846), - Tag("Artificial Intelligence", 1719), - Tag("Assjob", 2190), - Tag("Aunt-Nephew Relationship", 327), - Tag("Aunts", 1661), - Tag("Authority Figures", 1821), - Tag("Bad Grammar", 2188), - Tag("Bathroom Intercourse", 350), - Tag("BBW", 1867), - Tag("BDSM", 831), - Tag("Beach", 403), - Tag("Best Friends", 1227), - Tag("Bestiality", 372), - Tag("Betrayal", 610), - Tag("Big Ass", 1591), - Tag("Bikini", 1514), - Tag("Bishoujo", 645), - Tag("Bittersweet Ending", 1439), - Tag("Blackmail", 391), - Tag("Blind Characters", 1875), - Tag("Blindfold", 1177), - Tag("Bloomers", 1444), - Tag("Blow job", 952), - Tag("Blowjob Face", 2542), - Tag("Body Modification", 1760), - Tag("Body Swap", 444), - Tag("Body Writing", 2183), - Tag("Bondage", 317), - Tag("Borderline H", 395), - Tag("Brainwash", 1321), - Tag("Breast Expansion", 2191), - Tag("Brides", 1351), - Tag("Brother and Sister", 1305), - Tag("Brother Complex", 574), - Tag("Brother-in-law", 1782), - Tag("BSDM", 1263), - Tag("Bukkake", 551), - Tag("Bullying", 600), - Tag("Bunny Girls", 1226), - Tag("Cat Ears", 1633), - Tag("Cat Girls", 957), - Tag("Caught in the Act", 339), - Tag("Censored", 1196), - Tag("Cervix Penetration", 2184), - Tag("CGs", 1512), - Tag("Character Who Bullies the One They Love", 549), - Tag("Cheating", 351), - Tag("Cheerleaders", 1339), - Tag("Child Abuse", 1852), - Tag("Child Born From Incest", 575), - Tag("Child Prostitute", 1663), - Tag("Childhood Friends", 309), - Tag("Childhood Love", 310), - Tag("Chinese Dress", 2344), - Tag("Chubby", 1819), - Tag("Club President", 1586), - Tag("Clumsy Character", 1808), - Tag("Co-workers", 1739), - Tag("Collection of Inter-Linked Stories", 415), - Tag("Collection of Short Stories/Oneshots", 352), - Tag("Confession", 834), - Tag("Corruption", 755), - Tag("Cosplay", 379), - Tag("Cousins", 1340), - Tag("Cow Girls", 1371), - Tag("Creampie", 1037), - Tag("Crossdressing", 343), - Tag("Cunnilingus", 1754), - Tag("Dark Skin", 1277), - Tag("Debt-Motivated Prostitution", 1364), - Tag("Debts", 1154), - Tag("Deception", 516), - Tag("Deep Throat", 1436), - Tag("Defloration", 1246), - Tag("Delinquents", 680), - Tag("Demon Girls", 1453), - Tag("Demon Hunters", 1666), - Tag("Demons", 1152), - Tag("Doctor-Patient Relationship", 900), - Tag("Dog Girls", 353), - Tag("Double Penetration", 427), - Tag("Drugs", 446), - Tag("Drunk Intercourse", 438), - Tag("Drunk", 864), - Tag("Elf-Elves", 1370), - Tag("Emotionless Sex", 2312), - Tag("Enema Play", 1740), - Tag("Enemies Become Lovers", 803), - Tag("Ero-Guro", 302), - Tag("Exhibitionism", 404), - Tag("Facesitting", 1831), - Tag("Fairy-Fairies", 1368), - Tag("Family Love", 1616), - Tag("Family Secrets", 1623), - Tag("Father and Daughter", 1021), - Tag("Father-in-Law", 1922), - Tag("Female Dominance", 320), - Tag("Fetish", 1030), - Tag("Fight Between Lovers", 1745), - Tag("Fingering", 1389), - Tag("First Love", 670), - Tag("Fisting", 428), - Tag("Foot job", 1058), - Tag("Foot Licking", 2505), - Tag("Forced into a Relationship", 1746), - Tag("Forced Marriage", 1617), - Tag("Forced Sex", 1320), - Tag("Foursome", 330), - Tag("Fox Girls", 1268), - Tag("Friends Become Lovers", 1769), - Tag("Full Color", 468), - Tag("Furry", 2315), - Tag("Futa on Female", 2309), - Tag("Futa on Male", 1850), - Tag("Gang Rape", 429), - Tag("Gangbang", 462), - Tag("Ganguro", 696), - Tag("Giantess", 2278), - Tag("Girls Only", 1960), - Tag("Glasses", 465), - Tag("God-Human Relationship", 720), - Tag("Goddess", 1309), - Tag("Group Intercourse", 311), - Tag("Gyaru", 2185), - Tag("Hairy Armpit", 2359), - Tag("Hand Job", 534), - Tag("Happy Sex", 491), - Tag("Hardcore", 1397), - Tag("Harem-seeking Male Lead", 1487), - Tag("Hot Springs", 1744), - Tag("Housewife-Housewives", 555), - Tag("Human Pet", 1853), - Tag("Human Toilets", 1724), - Tag("Human-Nonhuman Relationship", 459), - Tag("Humiliation", 552), - Tag("Hypnotism", 1732), - Tag("Idols", 1211), - Tag("Impregnation", 1358), - Tag("Inari", 2506), - Tag("Incest as a Subplot", 753), - Tag("Infidelity", 423), - Tag("Inflation", 2186), - Tag("Inverted Nipples", 431), - Tag("Jealous Lover", 441), - Tag("Jealousy", 331), - Tag("Kidnapping", 408), - Tag("Kimono", 1283), - Tag("Konkon", 2507), - Tag("Korean Comic", 2158), - Tag("Kunoichi (Ninja Girls)", 1759), - Tag("Large Dicks", 990), - Tag("Leotard", 2187), - Tag("Lingerie", 332), - Tag("Little Sisters", 1580), - Tag("Live-in Lover", 1771), - Tag("Love At First Sight", 1326), - Tag("Love Rivals", 1490), - Tag("Love Triangles", 1167), - Tag("Magic", 539), - Tag("Magical Girls", 565), - Tag("Maids", 912), - Tag("Male Dominance", 681), - Tag("Mangaka", 659), - Tag("Masochists", 662), - Tag("Master-Pet Relationship", 880), - Tag("Master-Servant Relationship", 460), - Tag("Master-Slave Relationship", 389), - Tag("Mermaids", 1714), - Tag("MILFs", 354), - Tag("Mind Break", 303), - Tag("Mind Control", 304), - Tag("Molesters", 1114), - Tag("Monster Girls", 1409), - Tag("Monster Sex", 1243), - Tag("Monsters", 1175), - Tag("Mother and Daughter", 784), - Tag("Mother and Son", 393), - Tag("Mother Complex", 342), - Tag("Mother-in-Law", 1716), - Tag("Multiple Penetration", 1826), - Tag("Neighbors", 1741), - Tag("Netorare", 370), - Tag("Netori", 2243), - Tag("Newlyweds", 451), - Tag("Nipple Intercourse", 1717), - Tag("Nipple Play", 944), - Tag("No Penetration", 2503), - Tag("Non-Human Pregnancy", 461), - Tag("Nuns", 1185), - Tag("Nurses", 1046), - Tag("Obsessive Love", 605), - Tag("Office Ladies", 1738), - Tag("Old Man", 2331), - Tag("Older Brother", 1366), - Tag("Older Female Young Boy", 1648), - Tag("Older Female Younger Male", 355), - Tag("Older Male Younger Female", 1229), - Tag("Older Sister", 1241), - Tag("Outdoor Intercourse", 504), - Tag("Paizuri", 360), - Tag("Pantyhose", 1275), - Tag("Partial Censorship", 2303), - Tag("Partially Colored", 665), - Tag("Pegging", 1736), - Tag("Personality Change", 576), - Tag("Perverted Boss", 387), - Tag("Perverted Characters", 300), - Tag("Perverted Teachers", 366), - Tag("Perverts", 1526), - Tag("Pets", 1328), - Tag("Piercings", 1465), - Tag("Plain Girls", 1657), - Tag("Plastic Surgery", 688), - Tag("Polygamy", 312), - Tag("Poor Characters", 1317), - Tag("Poor Grammar", 2282), - Tag("Porn Industry", 1440), - Tag("Porn Stars", 367), - Tag("Porn with Plot", 530), - Tag("Possessed", 1656), - Tag("Possession", 677), - Tag("Possessive Lover", 1602), - Tag("Pregnancy", 305), - Tag("Pretend Rape", 1543), - Tag("Priestesses", 1662), - Tag("Priests", 1140), - Tag("Princes", 1160), - Tag("Princesses", 1161), - Tag("Prisoners", 618), - Tag("Proactive Protagonist", 1608), - Tag("Producers", 1528), - Tag("Prostitution", 394), - Tag("Public Intercourse", 507), - Tag("Public Nudity", 406), - Tag("Punishment Sex", 787), - Tag("Punishment", 1418), - Tag("Queens", 1434), - Tag("Rabbit Girls", 470), - Tag("Reverse Harem", 567), - Tag("Reverse Rape", 376), - Tag("Rewrite", 2281), - Tag("Rich Boy", 726), - Tag("Rich Family", 727), - Tag("Rich Girl", 368), - Tag("Rushed Ending/Axed", 505), - Tag("Sadist", 663), - Tag("Sadomasochism", 499), - Tag("Scat", 432), - Tag("School Girls", 995), - Tag("School Intercourse", 313), - Tag("School Nurse-Student Relationship", 478), - Tag("Secret Crush", 1491), - Tag("Secret Relationship", 399), - Tag("Seduction", 1556), - Tag("Senpai-Kouhai Relationship", 443), - Tag("Sex Addicts", 301), - Tag("Sex Friends Become Lovers", 400), - Tag("Sex Friends", 629), - Tag("Sex Industry", 450), - Tag("Sex Slaves", 1273), - Tag("Sex Toys", 837), - Tag("Sexual Abuse", 695), - Tag("Sexual Assault", 476), - Tag("Sexual Frustration", 1611), - Tag("Shemale", 2325), - Tag("Shy Characters", 652), - Tag("Sibling Love", 655), - Tag("Sister and Brother", 1403), - Tag("Sister Complex", 440), - Tag("Sisters", 1330), - Tag("Sketchy Art Style", 811), - Tag("Sleep Intercourse", 583), - Tag("Sluts", 1377), - Tag("Small Breasts", 434), - Tag("Son Complex", 909), - Tag("Spirits", 1387), - Tag("Star-Crossed Lover/s", 1346), - Tag("Step-Daughter", 531), - Tag("Step-Father", 532), - Tag("Step-Father/Step-Daughter Relationship", 533), - Tag("Step-Mother", 770), - Tag("Step-Mother/Step-Son Relationship", 334), - Tag("Step-Sibling Love", 371), - Tag("Step-Siblings", 521), - Tag("Step-Son", 833), - Tag("Stockings", 1327), - Tag("Student Council", 641), - Tag("Student-Tutor Relationship", 315), - Tag("Succubus", 378), - Tag("Sudden Appearance", 609), - Tag("Sudden Confession", 425), - Tag("Swimsuit/s", 1179), - Tag("Tanned", 1479), - Tag("Teacher-Student Relationship", 1126), - Tag("Teacher-Teacher Relationship", 369), - Tag("Teachers", 1388), - Tag("Threesome (MFF)", 1686), - Tag("Threesome (MMF)", 1688), - Tag("Threesome (Other)", 335), - Tag("Time Stop", 2300), - Tag("Tomboy", 650), - Tag("Torture", 2189), - Tag("Transgender", 518), - Tag("Trap", 344), - Tag("Tribadism", 553), - Tag("Tsundere", 653), - Tag("Tutors", 591), - Tag("Twincest", 578), - Tag("Twins", 336), - Tag("Uncle and Niece", 1923), - Tag("Unlucky Character/s", 414), - Tag("Unrequited Love", 1284), - Tag("Unusual Pupils", 2313), - Tag("Urethral Intercourse", 1157), - Tag("Urethral Play", 817), - Tag("Urination", 435), - Tag("Vampires", 1136), - Tag("Virgins", 1137), - Tag("Virtual Reality", 588), - Tag("Vore", 1841), - Tag("Voyeurism", 361), - Tag("Waitresses", 1334), - Tag("Werewolf", 877), - Tag("Widow", 544), - Tag("Wife Corruption", 613), - Tag("Wife Depravity", 545), - Tag("Wife-Wives", 1385), - Tag("Witches", 1485), - Tag("Wolf Girls", 1412), - Tag("X-Ray", 1071), - Tag("Yandere", 873), - Tag("Youkai", 1029), - Tag("Young Master", 500), - Tag("Yuri as a Subplot", 2342) - ) - - // Doujins : 868 - // $("div#tab-doujin > div:has(a.block)").map((i, el) => `Tag("${$(el).select("a").first().text().trim()}", ${$(el).find("input").first().attr("value")})`).get().sort().join(",\n") - // on https://hentai2read.com/hentai-search/" - // 1035 Doujin tags - private fun getDoujinList() = listOf( - Tag("3-gatsu no Lion", 2350), - Tag("3x3 Eyes", 1118), - Tag("7th Dragon", 1401), - Tag("81diver", 1880), - Tag("A Channel", 2168), - Tag("Accel World", 1584), - Tag("Ace Attorney", 1990), - Tag("Action Heroine: Cheer Fruits", 2339), - Tag("Agent Aika", 2141), - Tag("Ah! My Goddess", 1027), - Tag("Ai yori Aoshi", 1522), - Tag("Aikatsu!", 1892), - Tag("Air", 2163), - Tag("Aiura", 1885), - Tag("Aiyoku no Eustia", 1983), - Tag("Akame ga Kill!", 2042), - Tag("Akaneiro Ni Somaru Saka", 2150), - Tag("Akatsuki no Yona", 2295), - Tag("Aldnoah Zero", 2111), - Tag("Alice Gear Aegis", 2441), - Tag("Alice in the Country of Hearts", 1951), - Tag("Alice in Wonderland", 345), - Tag("Alps no Shoujo Heidi", 2463), - Tag("Amaama To Inazuma", 2216), - Tag("Amaeta Gari", 1876), - Tag("Amagami", 937), - Tag("Amagi Brilliant Park", 1989), - Tag("Amanchu!", 2276), - Tag("Amano Megumi Ha Sukidarake", 2326), - Tag("And Yet The Town Moves", 1916), - Tag("Ane Doki", 1699), - Tag("Angel Beats!", 1080), - Tag("Animal Crossing", 2364), - Tag("Anne Happy", 2317), - Tag("Ano Hana", 1090), - Tag("Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai", 1109), - Tag("Ano Natsu de Matteru", 1470), - Tag("Another", 1776), - Tag("Ansatsu Kyoushitsu", 2064), - Tag("Anyamaru Tantei Kiruminzoo", 2448), - Tag("Ao no Exorcist", 1562), - Tag("Aoi Hana", 1548), - Tag("Aoi Shiro", 2044), - Tag("Aquarion Evol", 1508), - Tag("Ar Tonelico 2", 1578), - Tag("Arakawa Under the Bridge", 1467), - Tag("Arc The Lad", 2250), - Tag("Arcana Heart", 1217), - Tag("Aria", 1110), - Tag("Arpeggio of Blue Steel", 1895), - Tag("Arslan Senki", 2269), - Tag("Asatte no Houkou", 2266), - Tag("Ashita No Nadja", 2483), - Tag("Ashitaba-san Chi no Muko Kurashi", 2299), - Tag("Asobi ni Ikuyo", 956), - Tag("Astarotte no Omocha!", 1705), - Tag("Astro Fighter Sunred", 2338), - Tag("Asu no Yoichi!", 967), - Tag("Atelier Series", 2023), - Tag("Atsumare! Fushigi Kenkyu-bu", 2501), - Tag("Axis Powers Hetalia", 2256), - Tag("Azazel-san.", 1773), - Tag("Azumanga Daioh", 1775), - Tag("Azur Lane", 2368), - Tag("Baccano!", 1980), - Tag("Baka to Test to Shoukanjuu", 940), - Tag("Bakemonogatari", 1024), - Tag("Bakuman", 985), - Tag("Bakunyu", 1244), - Tag("Bakuon", 2214), - Tag("Ballroom e Youkoso", 2528), - Tag("Bamboo Blade", 1654), - Tag("Bang Dream", 2333), - Tag("Banner of the Stars", 2199), - Tag("Basquash!", 1087), - Tag("Bastard!!", 1593), - Tag("Batman", 2416), - Tag("Battle Girl High School", 2221), - Tag("Battle Spirits", 1045), - Tag("Bayonetta", 1011), - Tag("Beat Angel Escalayer", 1701), - Tag("Beat Blades Haruka", 2287), - Tag("Beatmania", 1993), - Tag("Beelzebub", 2102), - Tag("Ben-To", 1785), - Tag("Berserk", 1735), - Tag("Bible Black", 2207), - Tag("Big Hero 6", 2247), - Tag("Bijin Onna Joushi Takizawa-san", 2455), - Tag("Bikini Warriors", 2198), - Tag("Binbougami ga!", 2419), - Tag("Birdy the Mighty", 2487), - Tag("Bishoujo Kamen Poitrine", 2162), - Tag("Black Bullet", 1999), - Tag("Black Butler", 2363), - Tag("Black Cat", 1103), - Tag("Black Desert Online", 2321), - Tag("Black Lagoon", 1249), - Tag("Black Rock Shooter", 1802), - Tag("Blast of Tempest", 2083), - Tag("Blazblue", 1523), - Tag("Bleach", 933), - Tag("Blend S", 2385), - Tag("Blood Plus", 2509), - Tag("Blue Exorcist", 2027), - Tag("Boku Dake Ga Inai Machi", 2383), - Tag("Boku Girl", 2397), - Tag("Boku wa Tomodachi ga Sukunai", 954), - Tag("Bokutachi wa Benkyou ga Dekinai", 2366), - Tag("Bomber Girl", 2524), - Tag("Boruto", 2351), - Tag("Brave Beats", 2206), - Tag("Brave Witches", 2320), - Tag("Bravely Default", 1902), - Tag("Brothers Conflict", 1947), - Tag("Brynhildr in the Darkness", 1944), - Tag("Btooom", 2026), - Tag("Buddy Complex", 1982), - Tag("Bungou Stray Dogs", 2392), - Tag("Burst Angel", 2393), - Tag("Busou Renkin", 1324), - Tag("Busou Shinki", 1801), - Tag("Busou Shoujo Machiavellianism", 2334), - Tag("Campione!", 1706), - Tag("CANAAN", 2037), - Tag("Canvas", 2318), - Tag("Capeta", 2511), - Tag("Captain Earth", 2146), - Tag("Captain Tsubasa", 2496), - Tag("Card Captor Sakura", 1919), - Tag("Cardfight!! Vanguard", 2020), - Tag("Catherine", 1692), - Tag("Charlotte", 2108), - Tag("Chihayafuru", 2072), - Tag("Chobits", 1899), - Tag("Chokotto Sister", 2405), - Tag("Chousoku Henkei Gyrozetter", 1767), - Tag("Chrome Shelled Regios", 1787), - Tag("Chrono Trigger", 1811), - Tag("Chu Berozu", 1399), - Tag("Chuunibyou demo Koi ga Shitai!", 1766), - Tag("Cinderella", 2464), - Tag("City Hunter", 1959), - Tag("Clannad", 955), - Tag("Claymore", 2030), - Tag("Code Geass", 1009), - Tag("Collar x Malice", 2343), - Tag("Corpse Party", 2124), - Tag("Cowboy Bebop", 1958), - Tag("Crayon Shin-chan", 2011), - Tag("Cutey Honey", 2314), - Tag("Cyborg 009", 1410), - Tag("D-Fragments", 1242), - Tag("Da Capo", 1723), - Tag("Dagashi Kashi", 2053), - Tag("Daily Life with a Monster Girl", 1781), - Tag("Daiya no A", 2522), - Tag("Dakara Boku wa H ga Dekinai", 1881), - Tag("Dame x Prince", 2485), - Tag("Danball Senki", 1954), - Tag("Danberu nan kiro moteru?", 2540), - Tag("Dance With Devils", 2209), - Tag("Danganronpa", 1896), - Tag("Danshi Koukousei No Nichijou", 2130), - Tag("Daphne in the Brilliant Blue", 1332), - Tag("Darker Than Black", 964), - Tag("Darkstalkers", 1857), - Tag("DARLING in the FRANXX", 2382), - Tag("Date A Live", 1794), - Tag("Dead by Daylight", 2510), - Tag("Dead Dead Demons Dededededestruction", 2077), - Tag("Dead or Alive", 1040), - Tag("DearS", 2039), - Tag("Death March kara Hajimaru Isekai Kyousoukyoku", 2471), - Tag("Death Note", 1188), - Tag("Defense Of The Ancients", 2526), - Tag("Demi-chan wa Kataritai", 2268), - Tag("Demons Souls", 1635), - Tag("Dennou Coil", 1018), - Tag("Denpa Onna to Seishun Otoko", 1091), - Tag("Detective Conan", 2051), - Tag("Devil Survivor 2", 1884), - Tag("Devilman", 2046), - Tag("Di Gi Charat", 2093), - Tag("Diabolik Lovers", 2117), - Tag("Digimon Savers", 2218), - Tag("Digimon Xros Wars", 1102), - Tag("Dirty Pair no Daibouken", 901), - Tag("Dirty Pair", 1260), - Tag("Discipline", 2358), - Tag("Disgaea 2", 1877), - Tag("Doctor Strange", 2340), - Tag("Dog Days", 1051), - Tag("Doki Doki Majo Shinpan", 1266), - Tag("Dokidoki! Precure", 1854), - Tag("Dokkiri Doctor", 2076), - Tag("Doraemon", 2147), - Tag("Dorei To No Seikatsu", 2277), - Tag("Dorohedoro", 1258), - Tag("Dororo", 2534), - Tag("Double Sensei Life", 2240), - Tag("Doutei", 1712), - Tag("Dr. Grayman", 1197), - Tag("Dragalia Lost", 2462), - Tag("Dragon Ball", 1122), - Tag("Dragon Quest", 936), - Tag("Dragonaut", 1059), - Tag("Dragons Crown", 1865), - Tag("Dragons Dogma", 2377), - Tag("DRAMAtical Murder", 2423), - Tag("Dream C Club", 939), - Tag("Drifters", 2022), - Tag("Dry Humping", 871), - Tag("Dungeon Meshi", 2104), - Tag("Dungeon ni Deai o Motomeru no wa Machigatteiru Darou ka", 2040), - Tag("Durarara!!", 1337), - Tag("Dynasty Warriors", 1610), - Tag("Dystopia", 810), - Tag("Eiken", 2424), - Tag("Elf-san wa Yaserarenai", 2521), - Tag("Elsword", 2200), - Tag("Emma", 1012), - Tag("Endless Frontier", 2238), - Tag("Ensemble Stars!", 2246), - Tag("Ero Manga Sensei", 2290), - Tag("Etotama", 2400), - Tag("Etrian Odyssey", 1977), - Tag("Euphoria", 2235), - Tag("Eureka Seven", 337), - Tag("Excel Saga", 2286), - Tag("Eyeshield 21", 1207), - Tag("Fairy Tail", 1113), - Tag("Fallout", 2357), - Tag("Fancy Lala", 1265), - Tag("Fatal Frame", 2242), - Tag("Fatal Fury", 2480), - Tag("Fate Apocrypha", 2203), - Tag("Fate Extella", 2329), - Tag("Fate Extra", 2459), - Tag("Fate Grand Order", 2148), - Tag("Fate Hollow Ataraxia", 915), - Tag("Fate Kaleid Liner Prisma Illya", 1964), - Tag("Fate-Extra CCC", 1790), - Tag("Fate-Stay Night", 1042), - Tag("Fate-Zero", 1638), - Tag("Final Dragon Chronicle", 1421), - Tag("Final Fantasy", 1201), - Tag("Final Fight", 1125), - Tag("Fire Emblem", 1205), - Tag("Flip Flappers", 2289), - Tag("Flower Knight Girl", 2252), - Tag("Flying Witch", 2308), - Tag("Frame Arms Girl", 2384), - Tag("Free!", 2100), - Tag("Freezing", 1847), - Tag("Fresh Precure!", 993), - Tag("Fruits Basket", 2119), - Tag("Fujiyama-san wa Shishunki", 1966), - Tag("Full Metal Daemon Muramasa", 1675), - Tag("Full Metal Panic", 1119), - Tag("Fullmetal Alchemist", 1107), - Tag("Fun Fun Pharmacy", 2129), - Tag("Fushigiboshi no Futagohime", 2071), - Tag("Fushoku No Machi", 2161), - Tag("Futari wa Precure Splash Star", 1698), - Tag("Futari wa Precure", 1707), - Tag("Futari wa Pretty Cure", 1903), - Tag("Future Card Buddyfight", 2355), - Tag("Gabriel Dropout", 2270), - Tag("Gad Guard", 2271), - Tag("Gakkou Gurashi!", 2145), - Tag("Gakusen Toshi Asterisk", 2156), - Tag("Galaxy Angel", 1416), - Tag("Galilei Donna", 2113), - Tag("Gantz", 2249), - Tag("Gatchaman Crowds", 1979), - Tag("Gate - Jietai Kano Chi Nite Kaku Tatakaeri", 2114), - Tag("Gegege No Kitarou", 2157), - Tag("Gekkan Shoujo Nozaki-kun", 1974), - Tag("Genmu Senki Leda", 2144), - Tag("Genshiken Nidaime", 1890), - Tag("Genshiken", 1245), - Tag("Getsuyoubi no Tawawa", 2241), - Tag("Getter Robo", 1883), - Tag("Ghost In The Shell", 1092), - Tag("Ghost Sweeper Mikami", 2239), - Tag("Giant Robo", 2488), - Tag("Gintama", 1952), - Tag("Girl Friend Beta", 2254), - Tag("Girls and Panzer", 1798), - Tag("Girls Frontline", 2362), - Tag("Girl’s High", 1998), - Tag("GJ Club", 1813), - Tag("GJ-bu", 2180), - Tag("Go! Princess PreCure", 2054), - Tag("Go-Toubun no Hanayome", 2470), - Tag("Goblin Slayer", 2452), - Tag("Gochuumon wa Usagi desu ka?", 1968), - Tag("God Eater", 961), - Tag("Golden Kamui", 2537), - Tag("Golden Time", 2408), - Tag("Gosick", 2092), - Tag("Granblue Fantasy", 2109), - Tag("Grisaia no Kajitsu", 2041), - Tag("Gugure! Kokkuri-san", 2179), - Tag("Guilty Crown", 1789), - Tag("Guilty Gear", 1254), - Tag("Guity Crown", 1786), - Tag("Gundam ZZ", 1957), - Tag("Gundam", 996), - Tag("Gunparade March", 2032), - Tag("Gunslinger Girl", 2138), - Tag("Gunsmith Cats", 2120), - Tag("Guzuguzu Shitetara Sodacchau yo?", 1984), - Tag("Hacka Doll", 2234), - Tag("Hai to Gensou no Grimgal", 2228), - Tag("Haikyu!!", 1996), - Tag("Haiyore! Nyaruko-san", 1938), - Tag("Hakuouki", 2294), - Tag("Hanamaru Youchien", 1319), - Tag("Hanasaku Iroha", 1026), - Tag("Hanayamata", 2297), - Tag("Hand Maid May", 2404), - Tag("Happinesscharge Precure!", 1946), - Tag("Harry Potter", 1291), - Tag("Harukanaru Jikuu no Naka de", 2006), - Tag("Harvest Moon", 2292), - Tag("Hataraku Maou-sama!", 1839), - Tag("Hatsukoi Limited", 969), - Tag("Hayate no Gotoku", 1065), - Tag("He is My Master", 1280), - Tag("Heartcatch Precure!", 1791), - Tag("Heartful Maman", 2531), - Tag("Heavy Object", 2457), - Tag("Hellsing", 2248), - Tag("Hentai Ouji to Warawanai Neko", 1868), - Tag("Heroic Age", 2346), - Tag("Heroman", 2442), - Tag("Hibike! Euphonium", 2088), - Tag("Hidamari Sketch", 1417), - Tag("High School DxD", 1833), - Tag("High School Fleet", 2477), - Tag("High Score Girl", 2352), - Tag("Highschool of the Dead", 991), - Tag("Higurashi no Naku Koro ni", 1096), - Tag("Hikaru No Go", 2255), - Tag("Himawari no Shoujo", 1192), - Tag("Himouto! Umaru-chan", 2078), - Tag("Hinabita", 2390), - Tag("Hinako Note", 2413), - Tag("Historys Strongest Disciple Kenichi", 1041), - Tag("Hitsugi no Chaika", 1976), - Tag("Hokenshitsu no Shinigami", 1561), - Tag("Honkai Gakuen", 2403), - Tag("Hoshi no Samidare", 2125), - Tag("Houkago Play", 999), - Tag("Hugtto Precure", 2450), - Tag("Hundred", 2262), - Tag("Hunter x Hunter", 1129), - Tag("Hyakka Ryouran Samurai Girls", 1693), - Tag("Hyouka", 1762), - Tag("Hyperdimension Neptunia", 1879), - Tag("Hypnosis Mic", 2535), - Tag("I''s", 1281), - Tag("Ichiban Ushiro no Daimaou", 1335), - Tag("Ichigo 100%", 1278), - Tag("Ichigo Mashimaro", 2201), - Tag("Idolish Seven", 2523), - Tag("IGPX", 2001), - Tag("Ijiranaide Nagatoro-san", 2437), - Tag("Ikki Tousen", 946), - Tag("Imouto ga Iru!", 1727), - Tag("Imouto Sae Ireba Ii", 2516), - Tag("Inazuma Eleven", 1912), - Tag("Inda no Himekishi Janne", 1718), - Tag("Infinite Ryvius", 2121), - Tag("Infinite Stratos", 920), - Tag("Inju Seisen Twin Angel", 1975), - Tag("Inou-Battle wa Nichijou-kei no Naka de", 2224), - Tag("Inu x Boku SS", 1950), - Tag("Inuyasha", 1985), - Tag("Irresponsible Captain Tylor", 2348), - Tag("Isekai Maou to Shoukan Shoujo Dorei Majutsu", 2491), - Tag("Isekai no Seikishi Monogatari", 1509), - Tag("Isekai Shokudou", 2402), - Tag("Isekai Wa Smartphone To Tomo Ni", 2467), - Tag("Ixion Saga DT", 2091), - Tag("Jewelpet Sunshine", 1874), - Tag("Jigoku Sensei Nube", 1765), - Tag("Jigoku Shoujo", 2381), - Tag("Jinrui Wa Suitai Shimashita", 2123), - Tag("Jitsu wa Watashi wa", 2310), - Tag("JoJos Bizarre Adventures", 1534), - Tag("Joker Game", 2274), - Tag("Jormungand", 1770), - Tag("Joshiraku", 1873), - Tag("Journey to the West", 786), - Tag("Jungle Guu", 2372), - Tag("Junketsu no Maria", 2440), - Tag("Juuza Engi", 2068), - Tag("K-ON!", 945), - Tag("Kaguya-sama wa Kokurasetai", 2465), - Tag("Kaiju Girls", 2401), - Tag("Kaiten Mutenmaru", 2210), - Tag("Kaitou Tenshi Twin Angel", 2126), - Tag("Kakegurui", 2324), - Tag("Kaleido Star", 1202), - Tag("Kamen Rider W", 1463), - Tag("Kami Nomi zo Shiru Sekai", 1069), - Tag("Kami-sama no Inai Nichiyoubi", 2127), - Tag("Kami-sama no Memochou", 1891), - Tag("Kamichu", 2135), - Tag("Kamikaze Kaitou Jeanne", 1788), - Tag("Kampfer", 1187), - Tag("Kangoku Senkan", 2411), - Tag("Kannagi", 1184), - Tag("Kannazuki No Miko", 2137), - Tag("Kanokon", 1546), - Tag("Kanon", 983), - Tag("Kantai Collection", 1870), - Tag("Kara no Kyoukai", 1900), - Tag("Karakai Jouzu no Takagi-san", 2371), - Tag("Kashimashi", 1757), - Tag("Katanagatari", 1806), - Tag("Katekyo Hitman Reborn", 2132), - Tag("Kemono Ekaki no Kousoku 2", 1864), - Tag("Kemono Friends", 2279), - Tag("Kemono of Magic - Foxy Rena", 2283), - Tag("Kenzen Robo Daimidaier", 2009), - Tag("Keroro Gunsou", 1336), - Tag("Key The Metal Idol", 2412), - Tag("Kid Icarus", 1721), - Tag("Kiddy Grade", 1869), - Tag("Kikou Shoujo Wa Kizutsukanai", 2475), - Tag("Kill la Kill", 1893), - Tag("Kimagure Orange Road", 1582), - Tag("Kimetsu no Yaiba", 2529), - Tag("Kimi ga Aruji de Shitsuji ga Ore de", 1259), - Tag("Kimi no Na wa", 2230), - Tag("Kimi no Suizou wo Tabetai", 2490), - Tag("KiMiKiSS", 934), - Tag("Kindaichi Shounen no Jikenbo", 2231), - Tag("King of Fighters", 1124), - Tag("Kingdom Hearts", 2466), - Tag("Kiniro Mosaic", 1855), - Tag("Kino No Tabi", 2347), - Tag("Kirakira Precure a la Mode", 2341), - Tag("Kiratto Pri Chan", 2541), - Tag("Kishuku Gakkou no Juliet", 2452), - Tag("Kiss x Sis", 1733), - Tag("Kizuato", 1497), - Tag("Knights and Magic", 2398), - Tag("Knights Of Sidonia", 2447), - Tag("Kobayashi-san Chi no Maid Dragon", 2261), - Tag("Kochikame", 1986), - Tag("Kodomo no Jikan", 2143), - Tag("Koe de Oshigoto!", 1780), - Tag("Koe no Katachi", 2021), - Tag("Koi Iroha", 2508), - Tag("Koi to Senkyo to Chocolate", 1749), - Tag("Koi wa Amaagari no You ni", 2378), - Tag("Koihime Musou", 1097), - Tag("Kokoro Connect", 1848), - Tag("Komi-san wa Komyushou Desu", 2495), - Tag("Kono Bijutsubu ni wa Mondai ga Aru!", 2336), - Tag("Kono Naka ni Hitori", 1726), - Tag("Kono Subarashii Sekai Ni Syukufuku O", 2139), - Tag("Kore wa Zombie desu ka?", 1844), - Tag("Koukou Kyuuji Zawa-san", 1764), - Tag("Koutetsujou No Kabaneri", 2169), - Tag("Kouyoku Senki ExS-Tia", 2533), - Tag("Ku-Neru Maruta", 2426), - Tag("Kuma Miko", 2155), - Tag("Kure-nai", 2014), - Tag("Kurogane no Liberals", 1671), - Tag("Kurokami", 2134), - Tag("Kuroko no Basuke", 1931), - Tag("Kuromukuro", 2302), - Tag("Kuttsukiboshi", 1820), - Tag("Kyo Kara Maoh!", 2128), - Tag("Kyonyuu Fantasy", 2305), - Tag("Kyoukai no Kanata", 2074), - Tag("Kyoukai Senjou no Horizon", 941), - Tag("Kyuujou Lovers", 2223), - Tag("Kyuushu Sentai Danjija", 2422), - Tag("La Blue Girl", 2353), - Tag("Last Period", 2311), - Tag("Le Fruit de la Grisaia", 2012), - Tag("League of Legends", 1720), - Tag("Leed Sha", 418), - Tag("Legend of Mana", 1949), - Tag("Legend Of Queen Opala", 2361), - Tag("Legend of the Mystical Ninja", 1926), - Tag("Lilim Kiss", 2476), - Tag("Linebarrels of Iron", 1672), - Tag("Little Busters", 2107), - Tag("Little Red Riding Hood", 2322), - Tag("Little Witch Academia", 2373), - Tag("Log Horizon", 1914), - Tag("Lost Universe", 2428), - Tag("Lotte no Omocha!", 1834), - Tag("Love Hina", 1533), - Tag("Love Lab", 1836), - Tag("Love Live Sunshine", 2213), - Tag("Love Live! - School Idol Project", 1917), - Tag("Love Plus", 931), - Tag("Lucky Star", 1000), - Tag("Luminous Arc", 1310), - Tag("Lupin III", 2116), - Tag("Mabinogi", 1909), - Tag("Macross Delta", 2244), - Tag("Macross Frontier", 1098), - Tag("Madan no Ou to Vanadis", 2090), - Tag("Made In Abyss", 2328), - Tag("Magi - Labyrinth of Magic", 1729), - Tag("Magic Knight Rayearth", 2285), - Tag("Magic the Gathering", 1816), - Tag("Magical Girl Lyrical Nanoha", 1469), - Tag("Magical Taluluto-kun", 1935), - Tag("Maho Girls Precure", 2165), - Tag("Mahou Sensei Negima", 1019), - Tag("Mahou Shoujo Ai", 2316), - Tag("Mahou Shoujo Ikusei Keikaku", 2253), - Tag("Mahou Shoujo Lyrical Nanoha StrikerS", 1431), - Tag("Mahou Shoujo Madoka Magica", 942), - Tag("Mahou Shoujo Nante Mou Ii Desu Kara", 2275), - Tag("Mahou Tsukai no Yome", 2394), - Tag("Mahou Tsukai no Yoru", 1596), - Tag("Mahouka Koukou no Rettousei", 1810), - Tag("Mai-Hime", 1247), - Tag("Maison Ikkoku", 2070), - Tag("Majestic Prince", 2429), - Tag("Maji de Watashi ni Koi Shinasai!", 2446), - Tag("Maji de Watashi ni Koishinasai", 1673), - Tag("Majin Tantei Nougami Neuro", 1583), - Tag("Major", 2080), - Tag("Makai Kishi Ingrid", 2115), - Tag("Makai Senki Disgaea", 1898), - Tag("Maken-Ki!", 1818), - Tag("Mamono Musume Zukan", 2345), - Tag("Mantradeva", 2512), - Tag("Manyuu Hikenchou", 2013), - Tag("Maoyuu Maou Yuusha", 1768), - Tag("Maria+Holic", 1005), - Tag("Maria-sama ga Miteru", 926), - Tag("Martian Successor Nadesico", 2245), - Tag("Marvel Universe", 1927), - Tag("Mashiroiro Symphony", 1563), - Tag("Mass Effect", 2219), - Tag("Matantei Loki Ragnarok", 2439), - Tag("Mawaru Penguindrum", 2435), - Tag("Mayo Chiki", 1088), - Tag("Maze", 2217), - Tag("Mazinger Z", 2225), - Tag("Medaka Box", 1121), - Tag("MegaMan", 2015), - Tag("Melancholy of Haruhi Suzumiya", 1297), - Tag("Melty Blood", 1419), - Tag("Metal Gear Solid", 2374), - Tag("Metal Slug", 2059), - Tag("Metroid", 1973), - Tag("Mikakunin de Shinkoukei", 1969), - Tag("Milk Crown", 2530), - Tag("Minami-ke", 2075), - Tag("Mirai Nikki", 1032), - Tag("Mitsudomoe", 1924), - Tag("Mobile Suit Gundam", 1948), - Tag("Moetan", 2154), - Tag("Mondaiji-tachi ga Isekai kara Kuru Sou Desu yo?", 2031), - Tag("Monster Girl Quest", 2029), - Tag("Monster Hunter", 921), - Tag("Moshidora", 2195), - Tag("Mouretsu Pirates", 1685), - Tag("Moyashimon", 1965), - Tag("Muchi Muchi Pork", 2478), - Tag("Muramasa", 1039), - Tag("Mushibugyo", 2065), - Tag("Mushishi", 2018), - Tag("Musou Orochi Z", 1797), - Tag("Muv-Luv Alternative", 1550), - Tag("Muv-Luv", 1547), - Tag("My Hero Academia", 2057), - Tag("My Little Pony", 2060), - Tag("Myriad Colors Phantom World", 2226), - Tag("Mysterious Girlfriend X", 1722), - Tag("Nadia", 930), - Tag("Nagi no Asukara", 1971), - Tag("Namiuchigiwa no Muromi-san", 2081), - Tag("Nanatsu no Taizai", 2033), - Tag("Naruto", 914), - Tag("Natsu no Kumo", 1379), - Tag("Natsume Yuujinchou", 2421), - Tag("Nausicaa", 1374), - Tag("Nazo no Kanojo X", 1007), - Tag("Negima", 1219), - Tag("Neko no Otera no Chion-san", 2433), - Tag("Nekomonogatari", 1866), - Tag("Neon Genesis Evangelion", 938), - Tag("Netoge no Yome wa Onnanoko ja Nai to Omotta?", 2170), - Tag("New Game", 2181), - Tag("Nichijou", 1887), - Tag("NieR RepliCant", 2097), - Tag("Ninja Gaiden Sigma", 1391), - Tag("Ninja Slayer", 2229), - Tag("Nisekoi", 1730), - Tag("Nisemonogatari", 1779), - Tag("No Game No Life", 1972), - Tag("No More Heroes", 2003), - Tag("No-Rin", 1908), - Tag("Nobunaga no Shinobi", 2438), - Tag("Nodame Cantabile", 2319), - Tag("Noein", 2233), - Tag("Noir", 2367), - Tag("Non Non Biyori", 1888), - Tag("Noragami", 1945), - Tag("Nurarihyon no Mago", 1400), - Tag("Nyan Koi", 1086), - Tag("Nyotaika! Paradise", 1889), - Tag("Oboro Muramasa", 1933), - Tag("Occult Academy", 1795), - Tag("Occultic Nine", 2265), - Tag("Oda Nobuna no Yabou", 1905), - Tag("Odin Sphere", 1001), - Tag("Ojamajo Doremi", 2008), - Tag("Okujou no Yurirei-san", 2136), - Tag("Okusan", 1731), - Tag("Omamori Himari", 2445), - Tag("One Off", 1851), - Tag("One Piece", 917), - Tag("One Punch Man", 1992), - Tag("One Week Friends", 2002), - Tag("Onegai My Melody", 1036), - Tag("Onegai Teacher", 1918), - Tag("Oni Imo", 1871), - Tag("Onii-chan Dakedo Ai Sae Areba Kankeinai yo ne", 2236), - Tag("Oniichan dakedo Ai Sae Areba Kankeinai yo ne", 1778), - Tag("Onmyou Taisenki", 2427), - Tag("Ookami Kodomo no Ame to Yuki", 2103), - Tag("Ookami to Koushinryou", 1055), - Tag("Ookami-san to Shichinin no Nakama-tachi", 1115), - Tag("Ookami-san", 976), - Tag("Ookiku Furikabutte", 1674), - Tag("Ooyasan Wa Shishunki", 2257), - Tag("Ore no Imouto ga Konna ni Kawaii Wake ga Nai", 1023), - Tag("Ore no Kanojo to Osananajimi ga Shuraba Sugiru", 1872), - Tag("Ore no Nounai", 2375), - Tag("Ore Twintail ni Narimasu", 2034), - Tag("OS-tan", 1067), - Tag("Oshiete! Gyaruko-chan", 2152), - Tag("Oshioki Sweetie", 2517), - Tag("Oshiro Project", 2498), - Tag("Osomatsu-san", 2388), - Tag("Otome wa Boku ni Koishiteru", 1963), - Tag("Otoyomegatari", 1937), - Tag("Ouran High School Host Club", 2284), - Tag("Outbreak Company", 1897), - Tag("Outlaw Star", 1133), - Tag("Overlord", 2176), - Tag("Overwatch", 2196), - Tag("Pandora Hearts", 1728), - Tag("Pani Poni Dash", 1678), - Tag("Panty and Stocking with Garterbelt", 1138), - Tag("Panty and Stocking", 1450), - Tag("Papa no Iu Koto o Kikinasai!", 1921), - Tag("Pastel Chime", 2472), - Tag("Patlabor", 2419), - Tag("Persona 3", 1111), - Tag("Persona 4", 977), - Tag("Persona 5", 2215), - Tag("Phantasy Star Online 2", 1882), - Tag("Photo Kano", 1878), - Tag("Pink Trash", 1516), - Tag("Planetes", 1907), - Tag("Playerunknowns Battlegrounds", 2432), - Tag("Plus Minus", 1048), - Tag("Pokemon", 1402), - Tag("Pop Team Epic", 2380), - Tag("Pretty Cure", 1035), - Tag("Pretty Rhythm", 2264), - Tag("Prince of Tennis", 2258), - Tag("Princess Connect", 2431), - Tag("Princess Lover!", 1827), - Tag("Princess Princess", 2544), - Tag("Princess Principal", 2386), - Tag("Princess Resurrection", 1647), - Tag("Princess Tears", 2474), - Tag("Prink Trash", 1755), - Tag("PriPara", 1994), - Tag("Prison School", 2106), - Tag("Prunus Girl", 2354), - Tag("Psycho-Pass", 2418), - Tag("Pumpkin Scissors", 1407), - Tag("Puzzle and Dragons", 1939), - Tag("Qualidea Code", 2436), - Tag("Queens Blade", 1120), - Tag("Quiz Magic Academy", 1915), - Tag("R.O.D the TV", 1859), - Tag("Rage of Bahamut", 2016), - Tag("Ragnarok Online", 1150), - Tag("Rail Wars", 2067), - Tag("Rainy Days", 819), - Tag("Rakudai Kishi no Eiyuutan", 2164), - Tag("Rakuen Tsuihou", 2028), - Tag("Ramen Daisuki Koizumi-san", 2520), - Tag("Rampo Kitan Game of Laplace", 2177), - Tag("Ran to Haiiro no Sekai", 2527), - Tag("Rance Quest", 1970), - Tag("Ranma 1-2", 986), - Tag("Rave", 2543), - Tag("Re:CREATORS", 2389), - Tag("Re:Zero kara Hajimeru Isekai Seikatsu", 2173), - Tag("Read or Die", 1106), - Tag("Real Drive", 2237), - Tag("Reco Love", 2296), - Tag("Record of Lodoss War", 1272), - Tag("Reika Wa Karei Na Boku No Maid", 2493), - Tag("Renkin 3 Kyuu Magical? Pokaan", 1549), - Tag("Resident Evil", 1089), - Tag("Rewrite", 2251), - Tag("Rinne no Lagrange", 1803), - Tag("Rio: Rainbow Gate", 1708), - Tag("Ro-Kyu-Bu! SS", 1925), - Tag("Ro-Kyu-Bu", 2131), - Tag("Robopon", 2167), - Tag("Robot Ponkottsu", 1825), - Tag("Robotics;Notes", 2502), - Tag("Rokka no Yuusha", 2079), - Tag("Rokudenashi Majutsu Koushi To Akashic Records", 2444), - Tag("Rokujouma no Shinryakusha!?", 2202), - Tag("Rosario to Vampire", 1308), - Tag("Rozen Maiden", 1828), - Tag("Rumble Roses", 2192), - Tag("Rune Factory", 2293), - Tag("Rurouni Kenshin", 1415), - Tag("RWBY", 2112), - Tag("Ryuugajou Nanana no Maizoukin", 2017), - Tag("Ryuuou No Oshigoto", 2399), - Tag("Sabagebu", 2098), - Tag("Saber Marionette", 2004), - Tag("Saenai Heroine no Sodatekata", 2043), - Tag("Saijaku Muhai No Bahamut", 2304), - Tag("Sailor Moon", 927), - Tag("Saint Seiya", 2063), - Tag("Saki Achiga-hen", 1934), - Tag("Saki", 1066), - Tag("Sakiko-san no Dansei Jijou", 1603), - Tag("Sakura Quest", 2406), - Tag("Sakura Taisen", 2160), - Tag("Sakura Trick", 2069), - Tag("Sakura War", 1683), - Tag("Sakurasou no Pet na Kanojo", 1930), - Tag("Samurai Flamenco", 2101), - Tag("Samurai Spirits", 1128), - Tag("Sangoku Rensenki", 1942), - Tag("Sankarea", 2110), - Tag("Sanoba Witch", 2453), - Tag("Satsuriku No Tenshi", 2473), - Tag("Saya no Uta", 1920), - Tag("Sayonara Zetsubou Sensei", 1886), - Tag("Scared Rider Xechs", 2212), - Tag("School Rumble", 981), - Tag("Schoolgirl Strikers", 2499), - Tag("Seiken Densetsu 3", 1302), - Tag("Seikon No Qwaser", 2118), - Tag("Seirei Tsukai no Kenbu", 2387), - Tag("Seishun Buta Yarou Wa Bunny Girl Senpai No Yume O Minai", 2460), - Tag("Seitokai Yakuindomo", 1901), - Tag("Sekai De Ichiban Tsuyoku Naritai", 2122), - Tag("Sekai Seifuku", 1955), - Tag("Sekirei", 1006), - Tag("Selector Infected WIXOSS", 2047), - Tag("Sengoku Basara", 1261), - Tag("Sengoku Collection", 1962), - Tag("Sengoku Musou", 1677), - Tag("Sengoku Night Blood", 2461), - Tag("Sengoku Otome", 1911), - Tag("Sengoku Rance", 1225), - Tag("Senki Zesshou Symphogear", 2049), - Tag("Sennen Sensou Aigis", 2227), - Tag("Senpai ga Urusai Kouhai no Hanashi", 2525), - Tag("Senran Kagura", 1443), - Tag("Senyuu", 2263), - Tag("Seraph of the End", 2232), - Tag("Servant x Service", 1846), - Tag("Seto no Hanayome", 1231), - Tag("Sewayaki Kitsune no Senko-san", 2532), - Tag("Shadow Hearts", 2025), - Tag("Shadowverse", 2449), - Tag("Shakugan no Shana", 2096), - Tag("Shakunetsu no Takkyu Musume", 2376), - Tag("Shaman King", 2048), - Tag("Shantae", 2151), - Tag("Sharin no Kuni", 1191), - Tag("Shimoneta to Iu Gainen ga Sonzai Shinai Taikutsu na Sekai", 2133), - Tag("Shin Koihime Musou", 1570), - Tag("Shin Megami Tensei: Devil Survivor 2", 1835), - Tag("Shingeki no Kyojin", 1832), - Tag("Shining Blade", 1734), - Tag("Shining Force", 2175), - Tag("Shinken", 2335), - Tag("Shinmai Fukei Kiruko-san", 1748), - Tag("Shinmai Maou no Keiyakusha", 2084), - Tag("Shinrabansho", 1842), - Tag("Shinryaku! Ika Musume", 1953), - Tag("Shinsekai Yori", 2220), - Tag("Shirobako", 2045), - Tag("Shirokuma Cafe", 2085), - Tag("Shokugeki no Soma", 1956), - Tag("Shota Eater", 1981), - Tag("Shoujo Kageki Revue Starlight", 2504), - Tag("Shoujo Shuumatsu Ryokou", 2414), - Tag("Shounen Maid", 2291), - Tag("Show by Rock!!", 2095), - Tag("Shugo Chara", 2019), - Tag("Shuriken Sentai Ninninger", 2178), - Tag("Shut Hell", 2073), - Tag("Silent Hill", 2211), - Tag("Simoun", 1936), - Tag("Sket Dance", 2089), - Tag("Skullgirls", 2159), - Tag("Slayers", 1214), - Tag("Smile Precure!", 1637), - Tag("So-Ra-No-Wo-To", 2260), - Tag("Sokkou Seitokai", 2409), - Tag("Solatorobo", 2052), - Tag("Solty Rei", 1094), - Tag("Sonic", 2055), - Tag("Sora no Manimani", 2082), - Tag("Sora no Method", 2094), - Tag("Sora no Otoshimono", 1025), - Tag("Sora o Kakeru Shoujo", 1193), - Tag("Sora Yori Mo Tooi Basho", 2482), - Tag("Soredemo Machi wa Mawatteiru", 1174), - Tag("Soreyuke Marin-chan", 1296), - Tag("Soul Calibur", 992), - Tag("Soul Eater", 1408), - Tag("Sound Voltex", 2066), - Tag("Sousei No Onmyouji", 2272), - Tag("Space Battleship Yamato 2199", 1792), - Tag("Space Dandy", 2062), - Tag("Space Patrol Luluco", 2365), - Tag("Space Pirate Mito", 2407), - Tag("Spice and Wolf", 1411), - Tag("Splatoon", 2099), - Tag("SSSS.Gridman", 2479), - Tag("Star Driver", 1172), - Tag("Star Fox", 2327), - Tag("Star Ocean 3", 1210), - Tag("Star Ocean", 1655), - Tag("Star Twinkle Precure", 2514), - Tag("Starry Sky", 2208), - Tag("Starting Gate!: Uma Musume Pretty Derby", 2410), - Tag("Steins;Gate", 1050), - Tag("Stellvia Of The Universe", 1298), - Tag("Steven Universe", 2434), - Tag("Stratos 4", 2036), - Tag("Strawberry Panic", 1279), - Tag("Street Fighters", 1081), - Tag("Strike The Blood", 2222), - Tag("Strike Witches", 1437), - Tag("Suberu", 419), - Tag("Suisei no Gargantia", 1814), - Tag("Suite PreCure", 1020), - Tag("Sukatto Golf Pangya", 1393), - Tag("Summer Wars", 1131), - Tag("Summon Night", 2323), - Tag("Sumomomo Momomo", 1697), - Tag("Sunohara-sou no Kanrinin-san", 2197), - Tag("Super Black Jack", 1208), - Tag("Super Danganronpa 2", 1988), - Tag("Super Dimensional Fortress Macross", 2182), - Tag("Super Mario", 1539), - Tag("Super Powers", 824), - Tag("Super Real Mahjong", 2513), - Tag("Super Robot Wars", 1700), - Tag("Super Sonico", 1774), - Tag("Suzumiya Haruhi no Yuuutsu", 918), - Tag("Sword Art Online", 1473), - Tag("Tactics Ogre: Wheel of Fate", 1747), - Tag("Taimanin Asagi", 2035), - Tag("Tales of Graces", 1753), - Tag("Tales of Symphonia", 1845), - Tag("Tales of the Abyss", 928), - Tag("Tales of Vesperia", 1743), - Tag("Tales of Xillia", 1756), - Tag("Tales of Zestiria", 2396), - Tag("Tamako Market", 1863), - Tag("Tanaka-kun wa Itsumo Kedaruge", 2415), - Tag("Tanken Driland", 2280), - Tag("Tantei Opera Milky Holmes", 2174), - Tag("Tari Tari", 1860), - Tag("Tasogare Otome x Amnesia", 1750), - Tag("Tate no Yuusha no Nariagari", 2492), - Tag("Teen Titans", 1594), - Tag("Tejina Senpai", 2205), - Tag("Tekken", 1203), - Tag("Tenchi Muyou", 1315), - Tag("Tengen Toppa Gurren Lagann", 1112), - Tag("Tenjo Tenge", 1108), - Tag("Tenkai Knights", 2370), - Tag("Tenki no Ko", 2538), - Tag("Tenkuu no Escaflowne", 2330), - Tag("Tensei Shitara Slime Datta Ken", 2469), - Tag("Tenshi no 3P!", 2500), - Tag("TERA Online", 1809), - Tag("TERA The Exiled Realm of Arborea", 1894), - Tag("Terminator", 2515), - Tag("Terra Formars", 2153), - Tag("The Elder Scrolls", 2468), - Tag("The Idolm@sters", 948), - Tag("The Legend of Heroes", 1987), - Tag("The Legend of Zelda", 1793), - Tag("The Lord of Elemental", 1689), - Tag("The Ring", 2332), - Tag("The Seven Deadly Sins", 2005), - Tag("The World God Only Knows", 1123), - Tag("They Are My Noble Masters", 1237), - Tag("Tiger and Bunny", 1176), - Tag("To Aru Kagaku no Railgun", 1557), - Tag("To Aru Majutsu no Index", 960), - Tag("To Heart 2", 987), - Tag("To Heart", 984), - Tag("To Love-Ru Darkness", 949), - Tag("To Love-Ru", 1056), - Tag("Tobaku Mokushiroku Kaiji", 1680), - Tag("Toji No Miko", 2420), - Tag("Toki o Kakeru Shoujo", 1913), - Tag("Tokidensho Angel Eyes", 1480), - Tag("Tokimeki Memorial 4", 1535), - Tag("Tokimeki Memorial", 1812), - Tag("Tokyo 7th Sisters", 2142), - Tag("Tokyo Ghoul", 2056), - Tag("Tokyo Ravens", 1961), - Tag("Tonari No Miko-san Wa Minna Warau", 2391), - Tag("Top wo Nerae 2!", 1372), - Tag("Toradora!", 1095), - Tag("Toriko", 2425), - Tag("Touhou", 916), - Tag("Touken Ranbu", 2166), - Tag("Trinity Seven", 2038), - Tag("Tsukihime", 1446), - Tag("Tsukumogami", 1077), - Tag("Tsuujou Kougeki ga Zentai Kougeki de Ni-kai Kougeki no Okaa-san wa Suki desu ka?", 2518), - Tag("Turn A Gundam", 1763), - Tag("Twin Angels", 2536), - Tag("Uchi No Maid Ga Uzasugiru", 2489), - Tag("Uchi No Musume Ni Te O Dasuna", 2204), - Tag("Uchuu Kaizoku Captain Harlock", 1189), - Tag("Uchuu no Stellvia", 1289), - Tag("Ultimate Girls", 2087), - Tag("Ultraman", 2451), - Tag("Umineko no Naku Koro ni", 1149), - Tag("Umineko", 1910), - Tag("UN-GO", 1995), - Tag("Unbalance x Unbalance", 1405), - Tag("Under Night In-Birth", 2301), - Tag("Under the Moon", 2193), - Tag("Undertale", 2356), - Tag("Unlight", 1862), - Tag("Upotte!!", 1592), - Tag("UQ Holder!", 1991), - Tag("Urusei Yatsura", 1466), - Tag("Usagi Drop", 2105), - Tag("Usavich", 1347), - Tag("Uta no Prince-sama", 1929), - Tag("Utawarerumono", 1062), - Tag("Uzaki-chan wa Asobitai!", 2456), - Tag("Valkyria Chronicles II", 1643), - Tag("Valkyria Chronicles", 925), - Tag("Valkyrie Drive", 2259), - Tag("Vampire Hunters", 1447), - Tag("Vampire Princess Miyu", 2061), - Tag("Vampire Savior", 1448), - Tag("Vandread", 1462), - Tag("Violated Heroine", 2172), - Tag("Viper", 2007), - Tag("Vividred Operation", 1838), - Tag("Vocaloid", 1013), - Tag("Voiceroid", 2306), - Tag("Wagaya no Oinari-sama", 2484), - Tag("Waiting in the Summer", 1906), - Tag("Walkure Romanze", 2000), - Tag("Warriors Orochi Z", 1684), - Tag("Warship Girls", 2349), - Tag("Watari-kun no xx ga Houkai Sunzen", 2497), - Tag("Watashi ga Motenai no wa Dou Kangaete mo Omaera ga Warui!", 1621), - Tag("Watashi ni Tenshi ga Maiorita!", 2481), - Tag("Wedding Peach", 2288), - Tag("Wild Arms 2", 982), - Tag("Wild Arms 3", 1804), - Tag("Witch Blade", 997), - Tag("Witch Craft Works", 924), - Tag("Working", 1130), - Tag("World Masterpiece Theater", 2050), - Tag("World Trigger", 2149), - Tag("Wrestle Angels", 1267), - Tag("X Change", 2486), - Tag("Xenoblade", 2307), - Tag("Xenogears", 1777), - Tag("Xenosaga", 1928), - Tag("xxxHoLic", 2417), - Tag("Yahari Ore no Seishun Rabukome wa Machigatte Iru.", 1796), - Tag("Yakitate!! Japan", 1499), - Tag("Yakushiji Ryouko no Kaiki Jikenbo", 2267), - Tag("Yama no Susume", 1967), - Tag("Yes! Precure 5", 1307), - Tag("Yokohama Kaidashi Kikou", 2443), - Tag("Yondemasu yo Azazeru-san", 1978), - Tag("Yoru no Yatterman", 2171), - Tag("Yosuga No Sora", 2140), - Tag("Yotsuba!", 1234), - Tag("Yotsubato!", 1943), - Tag("You Are Under Arrest!", 2369), - Tag("Youjo Senki", 2430), - Tag("Youkoso Jitsuryoku Shijou Shugi no Kyoushitsu e", 2337), - Tag("Yowamushi Pedal", 2086), - Tag("YS", 2494), - Tag("Yu-Gi-Oh!", 1084), - Tag("Yu-No", 2539), - Tag("Yumekui Merry", 2024), - Tag("Yuragi-sou no Yuuna-san", 2379), - Tag("Yuri On Ice", 2273), - Tag("Yuru Camp", 2395), - Tag("Yuru Yuri", 1861), - Tag("Yuuki Yuuna wa Yuusha de Aru", 2058), - Tag("Yuusha ni Narenakatta", 1932), - Tag("Yuyushiki", 2010), - Tag("Zegapain", 1530), - Tag("Zero In", 1858), - Tag("Zero no Tsukaima", 1148), - Tag("Zettai Junpaku Mahou Shoujo", 2194), - Tag("Zettai Karen Children", 1022), - Tag("Zoids Genesis", 1052), - Tag("Zombie Land Saga", 2458), - Tag("Zone of the Enders", 1997) - ) -} diff --git a/src/en/hentai2read/src/eu/kanade/tachiyomi/extension/en/hentai2read/Hentai2ReadActivity.kt b/src/en/hentai2read/src/eu/kanade/tachiyomi/extension/en/hentai2read/Hentai2ReadActivity.kt deleted file mode 100644 index 9a51ed8fe..000000000 --- a/src/en/hentai2read/src/eu/kanade/tachiyomi/extension/en/hentai2read/Hentai2ReadActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.hentai2read - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://hentai2read.com/xxxx intents - * and redirects them to the main Tachiyomi process. - */ -class Hentai2ReadActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null) { - // TODO: filter standard paths - val id = pathSegments[0] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${Hentai2Read.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("Hentai2ReadActivity", e.toString()) - } - } else { - Log.e("Hentai2ReadActivity", "Could not parse URI from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/en/hentaifox/AndroidManifest.xml b/src/en/hentaifox/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/hentaifox/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/hentaifox/build.gradle b/src/en/hentaifox/build.gradle deleted file mode 100644 index cfef6c48e..000000000 --- a/src/en/hentaifox/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HentaiFox' - pkgNameSuffix = 'en.hentaifox' - extClass = '.HentaiFox' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/hentaifox/res/mipmap-hdpi/ic_launcher.png b/src/en/hentaifox/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1514ae044..000000000 Binary files a/src/en/hentaifox/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentaifox/res/mipmap-mdpi/ic_launcher.png b/src/en/hentaifox/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 691dc5d19..000000000 Binary files a/src/en/hentaifox/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentaifox/res/mipmap-xhdpi/ic_launcher.png b/src/en/hentaifox/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e909daf35..000000000 Binary files a/src/en/hentaifox/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentaifox/res/mipmap-xxhdpi/ic_launcher.png b/src/en/hentaifox/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 95a9109be..000000000 Binary files a/src/en/hentaifox/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentaifox/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/hentaifox/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a17b2d047..000000000 Binary files a/src/en/hentaifox/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hentaifox/res/web_hi_res_512.png b/src/en/hentaifox/res/web_hi_res_512.png deleted file mode 100644 index 16b468882..000000000 Binary files a/src/en/hentaifox/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/hentaifox/src/eu/kanade/tachiyomi/extension/en/hentaifox/HentaiFox.kt b/src/en/hentaifox/src/eu/kanade/tachiyomi/extension/en/hentaifox/HentaiFox.kt deleted file mode 100644 index 1243869bb..000000000 --- a/src/en/hentaifox/src/eu/kanade/tachiyomi/extension/en/hentaifox/HentaiFox.kt +++ /dev/null @@ -1,212 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.hentaifox - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -@Nsfw -class HentaiFox : ParsedHttpSource() { - - override val name = "HentaiFox" - - override val baseUrl = "https://hentaifox.com" - - override val lang = "en" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/pag/$page/", headers) - } - - override fun popularMangaSelector() = "div.thumb" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("h2 a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("img").first().attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "li.page-item:last-of-type:not(.disabled)" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotEmpty()) { - GET("$baseUrl/search/?q=$query&page=$page", headers) - } else { - var url = "$baseUrl/tag/" - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - url += "${filter.toUriPart()}/pag/$page/" - } - } - } - GET(url, headers) - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return document.select("div.gallery_top").let { info -> - SManga.create().apply { - title = info.select("h1").text() - genre = info.select("ul.tags a").joinToString { it.ownText() } - artist = info.select("ul.artists a").joinToString { it.ownText() } - thumbnail_url = info.select("img").attr("abs:src") - description = info.select("ul.parodies a") - .let { e -> if (e.isNotEmpty()) "Parodies: ${e.joinToString { it.ownText() }}\n\n" else "" } - description += info.select("ul.characters a") - .let { e -> if (e.isNotEmpty()) "Characters: ${e.joinToString { it.ownText() }}\n\n" else "" } - description += info.select("ul.groups a") - .let { e -> if (e.isNotEmpty()) "Groups: ${e.joinToString { it.ownText() }}\n\n" else "" } - } - } - } - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - return listOf( - SChapter.create().apply { - name = "Chapter" - // page path with a marker at the end - url = "${response.request().url().toString().replace("/gallery/", "/g/")}#" - // number of pages - url += response.asJsoup().select("[id=load_pages]").attr("value") - } - ) - } - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - // Pages - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - // split the "url" to get the page path and number of pages - return chapter.url.split("#").let { list -> - Observable.just(listOf(1..list[1].toInt()).flatten().map { Page(it, list[0] + "$it/") }) - } - } - - override fun imageUrlParse(document: Document): String { - return document.select("img#gimg").attr("abs:src") - } - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - GenreFilter() - ) - - // Top 50 tags - private class GenreFilter : UriPartFilter( - "Category", - arrayOf( - Pair("<select>", "---"), - Pair("Big breasts", "big-breasts"), - Pair("Sole female", "sole-female"), - Pair("Sole male", "sole-male"), - Pair("Anal", "anal"), - Pair("Nakadashi", "nakadashi"), - Pair("Group", "group"), - Pair("Stockings", "stockings"), - Pair("Blowjob", "blowjob"), - Pair("Schoolgirl uniform", "schoolgirl-uniform"), - Pair("Rape", "rape"), - Pair("Lolicon", "lolicon"), - Pair("Glasses", "glasses"), - Pair("Defloration", "defloration"), - Pair("Ahegao", "ahegao"), - Pair("Incest", "incest"), - Pair("Shotacon", "shotacon"), - Pair("X-ray", "x-ray"), - Pair("Bondage", "bondage"), - Pair("Full color", "full-color"), - Pair("Double penetration", "double-penetration"), - Pair("Femdom", "femdom"), - Pair("Milf", "milf"), - Pair("Yaoi", "yaoi"), - Pair("Multi-work series", "multi-work-series"), - Pair("Schoolgirl", "schoolgirl"), - Pair("Mind break", "mind-break"), - Pair("Paizuri", "paizuri"), - Pair("Mosaic censorship", "mosaic-censorship"), - Pair("Impregnation", "impregnation"), - Pair("Males only", "males-only"), - Pair("Sex toys", "sex-toys"), - Pair("Sister", "sister"), - Pair("Dark skin", "dark-skin"), - Pair("Ffm threesome", "ffm-threesome"), - Pair("Hairy", "hairy"), - Pair("Cheating", "cheating"), - Pair("Sweating", "sweating"), - Pair("Yuri", "yuri"), - Pair("Netorare", "netorare"), - Pair("Full censorship", "full-censorship"), - Pair("Schoolboy uniform", "schoolboy-uniform"), - Pair("Dilf", "dilf"), - Pair("Big penis", "big-penis"), - Pair("Futanari", "futanari"), - Pair("Swimsuit", "swimsuit"), - Pair("Collar", "collar"), - Pair("Uncensored", "uncensored"), - Pair("Big ass", "big-ass"), - Pair("Story arc", "story-arc"), - Pair("Teacher", "teacher") - ) - ) - - private open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/en/hiveworks/AndroidManifest.xml b/src/en/hiveworks/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/hiveworks/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/hiveworks/build.gradle b/src/en/hiveworks/build.gradle deleted file mode 100644 index 3b6077e6a..000000000 --- a/src/en/hiveworks/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Hiveworks Comics' - pkgNameSuffix = 'en.hiveworks' - extClass = '.Hiveworks' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/hiveworks/res/mipmap-hdpi/ic_launcher.png b/src/en/hiveworks/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e11c95bfc..000000000 Binary files a/src/en/hiveworks/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hiveworks/res/mipmap-mdpi/ic_launcher.png b/src/en/hiveworks/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e73e88987..000000000 Binary files a/src/en/hiveworks/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hiveworks/res/mipmap-xhdpi/ic_launcher.png b/src/en/hiveworks/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a2392ccbc..000000000 Binary files a/src/en/hiveworks/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hiveworks/res/mipmap-xxhdpi/ic_launcher.png b/src/en/hiveworks/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7f753a986..000000000 Binary files a/src/en/hiveworks/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hiveworks/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/hiveworks/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 54438db74..000000000 Binary files a/src/en/hiveworks/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/hiveworks/res/web_hi_res_512.png b/src/en/hiveworks/res/web_hi_res_512.png deleted file mode 100644 index c7e797af8..000000000 Binary files a/src/en/hiveworks/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/hiveworks/src/eu/kanade/tachiyomi/extension/en/hiveworks/Hiveworks.kt b/src/en/hiveworks/src/eu/kanade/tachiyomi/extension/en/hiveworks/Hiveworks.kt deleted file mode 100644 index 4fc6917a1..000000000 --- a/src/en/hiveworks/src/eu/kanade/tachiyomi/extension/en/hiveworks/Hiveworks.kt +++ /dev/null @@ -1,440 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.hiveworks - -import android.net.Uri -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import java.util.concurrent.TimeUnit - -class Hiveworks : ParsedHttpSource() { - - // Info - - override val name = "Hiveworks Comics" - override val baseUrl = "https://hiveworkscomics.com" - override val lang = "en" - override val supportsLatest = true - - // Client - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build()!! - - // Popular - - override fun popularMangaRequest(page: Int) = GET(baseUrl, headers) - override fun popularMangaNextPageSelector(): String? = null - override fun popularMangaSelector() = "div.comicblock" - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).filterNot { - val url = it.select("a.comiclink").first().attr("abs:href") - url.contains("sparklermonthly.com") || url.contains("explosm.net") // Filter Unsupported Comics - }.map { element -> - popularMangaFromElement(element) - } - - val hasNextPage = popularMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - val day = SimpleDateFormat("EEEE", Locale.US).format(Date()).toLowerCase(Locale.US) - return GET("$baseUrl/home/update-day/$day", headers) - } - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun latestUpdatesSelector() = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) - - // Search - // Source's website doesn't appear to have a search function; so searching locally - - private lateinit var searchQuery: String - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse(baseUrl).buildUpon() - if (filters.isNotEmpty()) uri.appendPath("home") - // Append uri filters - filters.forEach { filter -> - when (filter) { - is UriFilter -> filter.addToUri(uri) - is OriginalsFilter -> if (filter.state) return GET("$baseUrl/originals", headers) - is KidsFilter -> if (filter.state) return GET("$baseUrl/kids", headers) - is CompletedFilter -> if (filter.state) return GET("$baseUrl/completed", headers) - } - } - if (query.isNotEmpty()) { - searchQuery = query - uri.fragment("localSearch") - } - return GET(uri.toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() + ", div.originalsblock" - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaParse(response: Response): MangasPage { - val url = response.request().url().toString() - val document = response.asJsoup() - - val selectManga = document.select(searchMangaSelector()) - val mangas = when { - url.endsWith("localSearch") -> { - selectManga.filter { it.text().contains(searchQuery, true) }.map { element -> searchMangaFromElement(element) } - } - url.contains("originals") -> { - selectManga.map { element -> searchOriginalMangaFromElement(element) } - } - else -> { - selectManga.map { element -> searchMangaFromElement(element) } - } - } - - val hasNextPage = searchMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - private fun searchOriginalMangaFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img")[1].attr("abs:src") - title = element.select("div.header").text().substringBefore("by").trim() - author = element.select("div.header").text().substringAfter("by").trim() - artist = author - description = element.select("div.description").text().trim() - url = element.select("a").first().attr("href") - } - - // Common - - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = element.select("a.comiclink").first().attr("abs:href") - manga.title = element.select("h1").text().trim() - manga.thumbnail_url = element.select("img").attr("abs:src") - manga.artist = element.select("h2").text().removePrefix("by").trim() - manga.author = manga.artist - manga.description = element.select("div.description").text().trim() - manga.genre = element.select("div.comicrating").text().trim() - return manga - } - - // Details - // Fetches details by calling home page again and using the existing url to find the correct comic - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - val url = manga.url - return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response, url).apply { initialized = true } - } - } - - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl, headers) - override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not Used") - private fun mangaDetailsParse(response: Response, url: String): SManga { - val document = response.asJsoup() - return document.select(popularMangaSelector()) - .firstOrNull { url == it.select("a.comiclink").first().attr("abs:href") } - ?.let { mangaFromElement(it) } ?: SManga.create() - } - - // Chapters - - // Included to call custom error codes - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return if (manga.status != SManga.LICENSED) { - client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response) - } - } else { - Observable.error(Exception("Licensed - No chapters to show")) - } - } - - override fun chapterListSelector() = "select[name=comic] option" - override fun chapterListRequest(manga: SManga): Request { - val uri = Uri.parse(manga.url).buildUpon() - .appendPath("comic") - .appendPath("archive") - return GET(uri.toString(), headers) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val url = response.request().url().toString() - val document = response.asJsoup() - val baseUrl = document.select("div script").html().substringAfter("href='").substringBefore("'") - val elements = document.select(chapterListSelector()) - if (elements.isNullOrEmpty()) throw Exception("This comic has a unsupported chapter list") - val chapters = mutableListOf<SChapter>() - for (i in 1 until elements.size) { - chapters.add(createChapter(elements[i], baseUrl)) - } - when { - "checkpleasecomic" in url -> chapters.retainAll { it.name.endsWith("01") || it.name.endsWith(" 1") } - } - chapters.reverse() - return chapters - } - - private fun createChapter(element: Element, baseUrl: String?) = SChapter.create().apply { - name = element.text().substringAfter("-").trim() - url = baseUrl + element.attr("value") - date_upload = parseDate(element.text().substringBefore("-").trim()) - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(date)?.time ?: 0 - } - - override fun chapterFromElement(element: Element) = throw Exception("Not Used") - - // Pages - - override fun pageListRequest(chapter: SChapter) = GET(chapter.url, headers) - override fun pageListParse(response: Response): List<Page> { - val url = response.request().url().toString() - val document = response.asJsoup() - val pages = mutableListOf<Page>() - - document.select("div#cc-comicbody img")?.forEach { - pages.add(Page(pages.size, "", it.attr("src"))) - } - - // Site specific pages can be added here - when { - "smbc-comics" in url -> { - pages.add(Page(pages.size, "", document.select("div#aftercomic img").attr("src"))) - pages.add(Page(pages.size, "", smbcTextHandler(document))) - } - } - - return pages - } - - override fun pageListParse(document: Document): List<Page> = throw Exception("Not used, see pageListParse(response)") - override fun imageUrlRequest(page: Page) = throw Exception("Not used") - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("Only one filter can be used at a time"), - Filter.Separator(), - UpdateDay(), - RatingFilter(), - GenreFilter(), - TitleFilter(), - SortFilter(), - Filter.Separator(), - Filter.Header("Extra Lists"), - OriginalsFilter(), - KidsFilter(), - CompletedFilter() - ) - - private class OriginalsFilter : Filter.CheckBox("Original Comics") - private class KidsFilter : Filter.CheckBox("Kids Comics") - private class CompletedFilter : Filter.CheckBox("Completed Comics") - - private open class UriSelectFilter( - displayName: String, - val uriParam: String, - val vals: Array<Pair<String, String>>, - val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) - uri.appendPath(uriParam) - .appendPath(vals[state].first) - } - } - - private interface UriFilter { - fun addToUri(uri: Uri.Builder) - } - - private class UpdateDay : UriSelectFilter( - "Update Day", - "update-day", - arrayOf( - Pair("all", "All"), - Pair("monday", "Monday"), - Pair("tuesday", "Tuesday"), - Pair("wednesday", "Wednesday"), - Pair("thursday", "Thursday"), - Pair("friday", "Friday"), - Pair("saturday", "Saturday"), - Pair("sunday", "Sunday") - ) - ) - - private class RatingFilter : UriSelectFilter( - "Rating", - "age", - arrayOf( - Pair("all", "All"), - Pair("everyone", "Everyone"), - Pair("teen", "Teen"), - Pair("young-adult", "Young Adult"), - Pair("mature", "Mature") - ) - ) - - private class GenreFilter : UriSelectFilter( - "Genre", - "genre", - arrayOf( - Pair("all", "All"), - Pair("action/adventure", "Action/Adventure"), - Pair("animated", "Animated"), - Pair("autobio", "Autobio"), - Pair("comedy", "Comedy"), - Pair("drama", "Drama"), - Pair("dystopian", "Dystopian"), - Pair("fairytale", "Fairytale"), - Pair("fantasy", "Fantasy"), - Pair("finished", "Finished"), - Pair("historical-fiction", "Historical Fiction"), - Pair("horror", "Horror"), - Pair("lgbt", "LGBT"), - Pair("mystery", "Mystery"), - Pair("romance", "Romance"), - Pair("sci-fi", "Science Fiction"), - Pair("slice-of-life", "Slice of Life"), - Pair("steampunk", "Steampunk"), - Pair("superhero", "Superhero"), - Pair("urban-fantasy", "Urban Fantasy") - ) - ) - - private class TitleFilter : UriSelectFilter( - "Title", - "alpha", - arrayOf( - Pair("all", "All"), - Pair("a", "A"), - Pair("b", "B"), - Pair("c", "C"), - Pair("d", "D"), - Pair("e", "E"), - Pair("f", "F"), - Pair("g", "G"), - Pair("h", "H"), - Pair("i", "I"), - Pair("j", "J"), - Pair("k", "K"), - Pair("l", "L"), - Pair("m", "M"), - Pair("n", "N"), - Pair("o", "O"), - Pair("p", "P"), - Pair("q", "Q"), - Pair("r", "R"), - Pair("s", "S"), - Pair("t", "T"), - Pair("u", "U"), - Pair("v", "V"), - Pair("w", "W"), - Pair("x", "X"), - Pair("y", "Y"), - Pair("z", "Z"), - Pair("numbers-symbols", "Numbers / Symbols") - ) - ) - - private class SortFilter : UriSelectFilter( - "Sort By", - "sortby", - arrayOf( - Pair("none", "None"), - Pair("a-z", "A-Z"), - Pair("z-a", "Z-A") - ) - ) - - // Other Code - - // Builds Image from mouse tooltip text - private fun smbcTextHandler(document: Document): String { - val title = document.select("title").text().trim() - val altText = document.select("div#cc-comicbody img").attr("title") - - val titleWords: Sequence<String> = title.splitToSequence(" ") - val altTextWords: Sequence<String> = altText.splitToSequence(" ") - - val builder = StringBuilder() - var count = 0 - - for (i in titleWords) { - if (count != 0 && count.rem(7) == 0) { - builder.append("%0A") - } - builder.append(i).append("+") - count++ - } - builder.append("%0A%0A") - - var charCount = 0 - - for (i in altTextWords) { - if (charCount > 25) { - builder.append("%0A") - charCount = 0 - } - builder.append(i).append("+") - charCount += i.length + 1 - } - - return "https://fakeimg.pl/1500x2126/ffffff/000000/?text=$builder&font_size=42&font=museo" - } - - // Used to throw custom error codes for http codes - private fun Call.asObservableSuccess(): Observable<Response> { - return asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - when (response.code()) { - 404 -> throw Exception("This comic has a unsupported chapter list") - else -> throw Exception("HiveWorks Comics HTTP Error ${response.code()}") - } - } - } - } -} diff --git a/src/en/honkaiimpact3/AndroidManifest.xml b/src/en/honkaiimpact3/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/honkaiimpact3/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/honkaiimpact3/build.gradle b/src/en/honkaiimpact3/build.gradle deleted file mode 100644 index bc356d0ef..000000000 --- a/src/en/honkaiimpact3/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HonkaiImpact3' - pkgNameSuffix = 'en.honkaiimpact' - extClass = '.Honkaiimpact' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/honkaiimpact3/res/mipmap-hdpi/ic_launcher.png b/src/en/honkaiimpact3/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index dc9187779..000000000 Binary files a/src/en/honkaiimpact3/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/honkaiimpact3/res/mipmap-mdpi/ic_launcher.png b/src/en/honkaiimpact3/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0f31bc0c8..000000000 Binary files a/src/en/honkaiimpact3/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/honkaiimpact3/res/mipmap-xhdpi/ic_launcher.png b/src/en/honkaiimpact3/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 850d770de..000000000 Binary files a/src/en/honkaiimpact3/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/honkaiimpact3/res/mipmap-xxhdpi/ic_launcher.png b/src/en/honkaiimpact3/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8d9005961..000000000 Binary files a/src/en/honkaiimpact3/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/honkaiimpact3/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/honkaiimpact3/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2a38eb48c..000000000 Binary files a/src/en/honkaiimpact3/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/honkaiimpact3/res/web_hi_res_512.png b/src/en/honkaiimpact3/res/web_hi_res_512.png deleted file mode 100644 index f460a939b..000000000 Binary files a/src/en/honkaiimpact3/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/honkaiimpact3/src/eu/kanade/tachiyomi/extension/en/honkaiimpact/Honkaiimpact.kt b/src/en/honkaiimpact3/src/eu/kanade/tachiyomi/extension/en/honkaiimpact/Honkaiimpact.kt deleted file mode 100644 index 0efdbdfb5..000000000 --- a/src/en/honkaiimpact3/src/eu/kanade/tachiyomi/extension/en/honkaiimpact/Honkaiimpact.kt +++ /dev/null @@ -1,113 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.honkaiimpact - -import com.github.salomonbrys.kotson.float -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class Honkaiimpact : ParsedHttpSource() { - - // Info - Based of BH3 - // This is the english version of the site - override val name = "Honkai Impact 3rd" - override val baseUrl = "https://manga.honkaiimpact3.com" - override val lang = "en" - override val supportsLatest = false - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build()!! - - // Popular - override fun popularMangaSelector() = "a[href*=book]" - - override fun popularMangaNextPageSelector(): String? = null - override fun popularMangaRequest(page: Int) = GET("$baseUrl/book", headers) - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - - // Latest - override fun latestUpdatesSelector() = throw Exception("Not Used") - - override fun latestUpdatesNextPageSelector(): String? = null - override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - - // Search - override fun searchMangaSelector() = throw Exception("Not Used") - - override fun searchMangaNextPageSelector(): String? = null - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("No search") - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").attr("abs:href")) - manga.title = element.select("div.container-title").text().trim() - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - - // Manga Details - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = document.select("img.cover").attr("abs:src") - manga.description = document.select("div.detail_info1").text().trim() - manga.title = document.select("div.title").text().trim() - return manga - } - - // Chapters - override fun chapterListSelector() = throw Exception("Not Used") - - override fun chapterFromElement(element: Element) = throw Exception("Not Used") - override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/get_chapter", headers) - override fun chapterListParse(response: Response): List<SChapter> { - val jsondata = response.body()!!.string() - val json = JsonParser().parse(jsondata).asJsonArray - val chapters = mutableListOf<SChapter>() - json.forEach { - chapters.add(createChapter(it)) - } - return chapters - } - - private fun createChapter(json: JsonElement) = SChapter.create().apply { - name = json["title"].string - url = "/book/${json["bookid"].int}/${json["chapterid"].int}" - date_upload = parseDate(json["timestamp"].string) - chapter_number = json["chapterid"].float - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0 - } - - // Manga Pages - override fun pageListParse(response: Response): List<Page> = mutableListOf<Page>().apply { - val body = response.asJsoup() - body.select("img.lazy.comic_img")?.forEach { - add(Page(size, "", it.attr("data-original"))) - } - } - - override fun pageListParse(document: Document) = throw Exception("Not Used") - override fun imageUrlParse(document: Document) = throw Exception("Not Used") -} diff --git a/src/en/keenspot/AndroidManifest.xml b/src/en/keenspot/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/keenspot/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/keenspot/build.gradle b/src/en/keenspot/build.gradle deleted file mode 100644 index afbd8b670..000000000 --- a/src/en/keenspot/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'keenspot' - pkgNameSuffix = 'en.keenspot' - extClass = '.KeenspotFactory' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/keenspot/res/mipmap-hdpi/ic_launcher.png b/src/en/keenspot/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 81fca9506..000000000 Binary files a/src/en/keenspot/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/keenspot/res/mipmap-mdpi/ic_launcher.png b/src/en/keenspot/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ef37699b6..000000000 Binary files a/src/en/keenspot/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/keenspot/res/mipmap-xhdpi/ic_launcher.png b/src/en/keenspot/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 14c9df990..000000000 Binary files a/src/en/keenspot/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/keenspot/res/mipmap-xxhdpi/ic_launcher.png b/src/en/keenspot/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c6154cf32..000000000 Binary files a/src/en/keenspot/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/keenspot/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/keenspot/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 46fd3a27f..000000000 Binary files a/src/en/keenspot/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/keenspot/res/web_hi_res_512.png b/src/en/keenspot/res/web_hi_res_512.png deleted file mode 100644 index 1f167d6cd..000000000 Binary files a/src/en/keenspot/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/keenspot/src/eu/kanade/tachiyomi/extension/en/keenspot/KeenspotFactory.kt b/src/en/keenspot/src/eu/kanade/tachiyomi/extension/en/keenspot/KeenspotFactory.kt deleted file mode 100644 index 62cdcf73f..000000000 --- a/src/en/keenspot/src/eu/kanade/tachiyomi/extension/en/keenspot/KeenspotFactory.kt +++ /dev/null @@ -1,8 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.keenspot - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class KeenspotFactory : SourceFactory { - override fun createSources(): List<Source> = listOf(TwoKinds()) -} diff --git a/src/en/keenspot/src/eu/kanade/tachiyomi/extension/en/keenspot/TwoKinds.kt b/src/en/keenspot/src/eu/kanade/tachiyomi/extension/en/keenspot/TwoKinds.kt deleted file mode 100644 index 9d97a3b32..000000000 --- a/src/en/keenspot/src/eu/kanade/tachiyomi/extension/en/keenspot/TwoKinds.kt +++ /dev/null @@ -1,157 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.keenspot - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import rx.Observable - -class TwoKinds : HttpSource() { - - override val name = "Keenspot TwoKinds" - - override val baseUrl = "https://twokinds.keenspot.com" - - override val lang = "en" - - override val supportsLatest: Boolean = false - - override val id: Long = 3133607736276627986 - - // the one and only manga entry - fun mangaSinglePages(): SManga { - return SManga.create().apply { - title = "TwoKinds (1 page per chapter)" - thumbnail_url = "https://dummyimage.com/768x994/000/ffffff.jpg&text=$title" - artist = "Tom Fischbach" - author = "Tom Fischbach" - status = SManga.UNKNOWN - url = "1" - } - } - - fun manga20Pages(): SManga { - return SManga.create().apply { - title = "TwoKinds (20 pages per chapter)" - thumbnail_url = "https://dummyimage.com/768x994/000/ffffff.jpg&text=$title" - artist = "Tom Fischbach" - author = "Tom Fischbach" - status = SManga.UNKNOWN - url = "20" - } - } - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return Observable.just(MangasPage(listOf(mangaSinglePages(), manga20Pages()), false)) - } - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun popularMangaParse(response: Response): MangasPage = throw Exception("Not used") - - // latest Updates not used - - override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - // the manga is one and only, but still write the data again to avoid bugs in backup restore - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - if (manga.url == "1") - return Observable.just(mangaSinglePages()) - else - return Observable.just(manga20Pages()) - } - - override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used") - - // chapter list - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga) - } - } - - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl, headers) - } - - override fun chapterListParse(response: Response): List<SChapter> = throw Exception("Not used") - - fun chapterListParse(response: Response, manga: SManga): List<SChapter> { - val document = response.asJsoup() - - val lastPage = document.select(".navprev").first().attr("href").split("/")[2].toInt() + 1 - - val chapters = mutableListOf<SChapter>() - - if (manga.url == "1") { - for (i in 1..lastPage) { - chapters.add( - SChapter.create().apply() { - url = "1-$i" - name = "Page $i" - } - ) - } - } else { - for (i in 1..lastPage step 20) { - chapters.add( - SChapter.create().apply() { - url = "20-$i" - if (i + 20 > lastPage) - name = "Pages $i-$lastPage" - else - name = "Pages $i-${i + 20}" - } - ) - } - } - - return chapters.reversed() - } - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - if (chapter.url.startsWith("1")) { - return Observable.just( - listOf( - Page(0, baseUrl + "/comic/${chapter.url.substringAfter("-")}/") - ) - ) - } else { - val pages = mutableListOf<Page>() - val firstPage = chapter.url.substringAfter("-").toInt() - - for (i in firstPage..firstPage + 19) { - pages.add( - Page(i - firstPage, baseUrl + "/comic/$i/") - ) - } - return Observable.just(pages) - } - } - - override fun pageListParse(response: Response): List<Page> = throw Exception("Not used") - - override fun imageUrlParse(response: Response): String { - val document = response.asJsoup() - - return document.select("#content article img").first().attr("src") - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = throw Exception("Search functionality is not available.") - - override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") -} diff --git a/src/en/killsixbilliondemons/AndroidManifest.xml b/src/en/killsixbilliondemons/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/killsixbilliondemons/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/killsixbilliondemons/build.gradle b/src/en/killsixbilliondemons/build.gradle deleted file mode 100644 index ccfaf1763..000000000 --- a/src/en/killsixbilliondemons/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Kill Six Billion Demons' - pkgNameSuffix = 'en.killsixbilliondemons' - extClass = '.KillSixBillionDemons' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/killsixbilliondemons/res/mipmap-hdpi/ic_launcher.png b/src/en/killsixbilliondemons/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 82499db30..000000000 Binary files a/src/en/killsixbilliondemons/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/killsixbilliondemons/res/mipmap-mdpi/ic_launcher.png b/src/en/killsixbilliondemons/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c43314817..000000000 Binary files a/src/en/killsixbilliondemons/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/killsixbilliondemons/res/mipmap-xhdpi/ic_launcher.png b/src/en/killsixbilliondemons/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 23bccac87..000000000 Binary files a/src/en/killsixbilliondemons/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/killsixbilliondemons/res/mipmap-xxhdpi/ic_launcher.png b/src/en/killsixbilliondemons/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 72d0da8ed..000000000 Binary files a/src/en/killsixbilliondemons/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/killsixbilliondemons/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/killsixbilliondemons/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index dcb9ab07a..000000000 Binary files a/src/en/killsixbilliondemons/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/killsixbilliondemons/res/web_hi_res_512.png b/src/en/killsixbilliondemons/res/web_hi_res_512.png deleted file mode 100644 index 412b9942f..000000000 Binary files a/src/en/killsixbilliondemons/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/killsixbilliondemons/src/eu/kanade/tachiyomi/extension/en/killsixbilliondemons/KillSixBillionDemons.kt b/src/en/killsixbilliondemons/src/eu/kanade/tachiyomi/extension/en/killsixbilliondemons/KillSixBillionDemons.kt deleted file mode 100644 index 3360e6b79..000000000 --- a/src/en/killsixbilliondemons/src/eu/kanade/tachiyomi/extension/en/killsixbilliondemons/KillSixBillionDemons.kt +++ /dev/null @@ -1,166 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.killsixbilliondemons - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -/** - * @author Aria Moradi <aria.moradi007@gmail.com> - */ - -class KillSixBillionDemons : HttpSource() { - - override val name = "KillSixBillionDemons" - - override val baseUrl = "https://killsixbilliondemons.com" - - override val lang = "en" - - override val supportsLatest: Boolean = false - - // list of books - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - - return MangasPage(mangas, false) - } - - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl) - } - - private fun popularMangaSelector(): String { - return "#chapter option:contains(book)" - } - - private fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.text().substringBefore(" (") - thumbnail_url = "https://dummyimage.com/768x994/000/ffffff.jpg&text=$title" - artist = "Abbadon" - author = "Abbadon" - status = SManga.UNKNOWN - url = title // this url is not useful at all but must set to something unique or the app breaks! - } - } - - // latest Updates not used - - override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - // books dont change around here, but still write the data again to avoid bugs in backup restore - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(popularMangaRequest(1)) - .asObservableSuccess() - .map { response -> - popularMangaParse(response).mangas.find { manga.title == it.title } - } - } - - override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used") - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga) - } - } - - override fun chapterListRequest(manga: SManga): Request = popularMangaRequest(1) - - override fun chapterListParse(response: Response): List<SChapter> = throw Exception("Not used") - - private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { - val document = response.asJsoup() - - val options = document.select(chapterListSelector()) - - val chapters = mutableListOf<SChapter>() - var bookNum = 0 - val targetBookNum = manga.title.split(":")[0].split(" ")[1].toInt() - - for (element in options) { - val text = element.text() - if (text.startsWith("Book")) { - bookNum += 1 - continue - } - if (bookNum > targetBookNum) - break - - if (bookNum == targetBookNum) { - chapters.add( - SChapter.create().apply { - url = element.attr("value") - - val textSplit = text.split(" ") - - name = "Chapter ${textSplit[0]}" - } - ) - } - } - - return chapters.reversed() - } - - private fun chapterListSelector(): String { - return "#chapter option" - } - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - val wordpressPages = mutableListOf<Document>() - // get the first page and add ir to the list - val firstPageURL = chapter.url + "?order=ASC" // change the url to ask Wordpress to reverse the posts - val firstPage = client.newCall(GET(firstPageURL)).execute().asJsoup() - wordpressPages.add(firstPage) - - val otherPages = firstPage.select("#paginav a") - - for (i in 0 until (otherPages.size - 1)) // ignore the last one (last page button) - wordpressPages.add(client.newCall(GET(otherPages[i].attr("href"))).execute().asJsoup()) - - val chapterPages = mutableListOf<Page>() - var pageNum = 1 - - wordpressPages.forEach { wordpressPage -> - wordpressPage.select(".post-content .entry a:has(img)").forEach { postImage -> - chapterPages.add( - Page(pageNum, postImage.attr("href"), postImage.select("img").attr("src")) - ) - pageNum++ - } - } - - return Observable.just(chapterPages) - } - - override fun imageUrlParse(response: Response): String = throw Exception("Not used") - - override fun pageListParse(response: Response): List<Page> = throw Exception("Not used") - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = throw Exception("Search functionality is not available.") - - override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") -} diff --git a/src/en/latisbooks/AndroidManifest.xml b/src/en/latisbooks/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/latisbooks/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/latisbooks/build.gradle b/src/en/latisbooks/build.gradle deleted file mode 100644 index 03e867927..000000000 --- a/src/en/latisbooks/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Latis Books' - pkgNameSuffix = 'en.latisbooks' - extClass = '.Latisbooks' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/latisbooks/res/mipmap-hdpi/ic_launcher.png b/src/en/latisbooks/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 37d95aac4..000000000 Binary files a/src/en/latisbooks/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/latisbooks/res/mipmap-mdpi/ic_launcher.png b/src/en/latisbooks/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index edd1522b7..000000000 Binary files a/src/en/latisbooks/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/latisbooks/res/mipmap-xhdpi/ic_launcher.png b/src/en/latisbooks/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a5d4f2602..000000000 Binary files a/src/en/latisbooks/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/latisbooks/res/mipmap-xxhdpi/ic_launcher.png b/src/en/latisbooks/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 66b559ff8..000000000 Binary files a/src/en/latisbooks/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/latisbooks/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/latisbooks/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 13120621e..000000000 Binary files a/src/en/latisbooks/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/latisbooks/res/web_hi_res_512.png b/src/en/latisbooks/res/web_hi_res_512.png deleted file mode 100644 index 5c35d319f..000000000 Binary files a/src/en/latisbooks/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/latisbooks/src/eu/kanade/tachiyomi/extension/en/latisbooks/Latisbooks.kt b/src/en/latisbooks/src/eu/kanade/tachiyomi/extension/en/latisbooks/Latisbooks.kt deleted file mode 100644 index b8c34c9f4..000000000 --- a/src/en/latisbooks/src/eu/kanade/tachiyomi/extension/en/latisbooks/Latisbooks.kt +++ /dev/null @@ -1,113 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.latisbooks - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import java.util.Calendar - -@Nsfw -class Latisbooks : HttpSource() { - - override val name = "Latis Books" - - override val baseUrl = "https://www.latisbooks.com" - - override val lang = "en" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - private fun createManga(response: Response): SManga { - return SManga.create().apply { - initialized = true - title = "Bodysuit 23" - url = "/archive/" - thumbnail_url = response.asJsoup().select("img.thumb-image").firstOrNull()?.attr("abs:data-src") - } - } - - // Popular - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - MangasPage(listOf(createManga(response)), false) - } - } - - override fun popularMangaRequest(page: Int): Request { - return (GET("$baseUrl/archive/", headers)) - } - - override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Details - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - createManga(response) - } - } - - override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException("Not used") - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - val cal: Calendar = Calendar.getInstance() - - return response.asJsoup().select("ul.archive-item-list li a").map { - val date: List<String> = it.attr("abs:href").split("/") - cal.set(date[4].toInt(), date[5].toInt() - 1, date[6].toInt()) - - SChapter.create().apply { - name = it.text() - url = it.attr("abs:href") - date_upload = cal.timeInMillis - } - } - } - - // Pages - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return Observable.just(listOf(Page(0, chapter.url))) - } - - override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(response: Response): String { - return response.asJsoup().select("div.content-wrapper img.thumb-image").attr("abs:data-src") - } - - override fun getFilterList() = FilterList() -} diff --git a/src/en/lemonfont/AndroidManifest.xml b/src/en/lemonfont/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/lemonfont/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/lemonfont/build.gradle b/src/en/lemonfont/build.gradle deleted file mode 100644 index f83170239..000000000 --- a/src/en/lemonfont/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'LemonFont' - pkgNameSuffix = 'en.lemonfont' - extClass = '.LemonFont' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/lemonfont/res/mipmap-hdpi/ic_launcher.png b/src/en/lemonfont/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4eb942c50..000000000 Binary files a/src/en/lemonfont/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lemonfont/res/mipmap-mdpi/ic_launcher.png b/src/en/lemonfont/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4eb942c50..000000000 Binary files a/src/en/lemonfont/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lemonfont/res/mipmap-xhdpi/ic_launcher.png b/src/en/lemonfont/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2a5ff3d68..000000000 Binary files a/src/en/lemonfont/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lemonfont/res/mipmap-xxhdpi/ic_launcher.png b/src/en/lemonfont/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e85927c6c..000000000 Binary files a/src/en/lemonfont/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lemonfont/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/lemonfont/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 42f6b8b26..000000000 Binary files a/src/en/lemonfont/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/lemonfont/res/web_hi_res_512.png b/src/en/lemonfont/res/web_hi_res_512.png deleted file mode 100644 index ea8db8f23..000000000 Binary files a/src/en/lemonfont/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/lemonfont/src/eu/kanade/tachiyomi/extension/en/lemonfont/LemonFont.kt b/src/en/lemonfont/src/eu/kanade/tachiyomi/extension/en/lemonfont/LemonFont.kt deleted file mode 100644 index 750f7a02d..000000000 --- a/src/en/lemonfont/src/eu/kanade/tachiyomi/extension/en/lemonfont/LemonFont.kt +++ /dev/null @@ -1,112 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.lemonfont - -import eu.kanade.tachiyomi.annotations.Nsfw -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -@Nsfw -class LemonFont : ParsedHttpSource() { - override val name = "LemonFont" - - override val baseUrl = "http://lemonfontcomics.com" - - override val lang = "en" - - override val supportsLatest = false - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics/", headers) - - override fun popularMangaSelector() = "div.comic-collection > a:not([href*=redbubble])" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - title = element.select("p").text().trim() - thumbnail_url = element.select("img").attr("abs:src") - - if (!element.attr("href").contains("http")) { - setUrlWithoutDomain(element.attr("abs:href")) - } else { - status = SManga.LICENSED - setUrlWithoutDomain(element.attr("href")) - } - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val seriesUrl: String = document.location() - val homePage: Document = client.newCall(GET("$baseUrl/comics/", headers)).execute().asJsoup() - val element: Element = homePage.select("div.comic-collection > a[abs:href=$seriesUrl]").first() - - thumbnail_url = element.select("img").attr("abs:src") - status = getStatus(element) - author = "LemonFont" - } - - private fun getStatus(element: Element) = when { - element.attr("href").contains("http") -> SManga.LICENSED - element.select("p").first().id() == "tag-ongoing" -> SManga.ONGOING - else -> SManga.COMPLETED - } - - override fun chapterListParse(response: Response): List<SChapter> { - val mangaInfo = response.asJsoup().select("div.container > div.content > script").toString() - - val ptotal: Int = Integer.parseInt(Regex("(?<=window\\.ptotal = )([0-9]*?)(?=;)").find(mangaInfo)?.value!!) - val part: String = Regex("(?<=window\\.part=')(.*?)(?=';)").find(mangaInfo)?.value ?: "" - val series: String = Regex("(?<=window\\.series=')(.*?)(?=';)").find(mangaInfo)?.value ?: "" - - val chapterList: MutableList<SChapter> = ArrayList() - for (i in 1..ptotal) { - chapterList.add( - SChapter.create().apply { - setUrlWithoutDomain("$baseUrl/assets/comics/$series/$part/${i.toString().padStart(3, '0')}.png") - chapter_number = i.toFloat() - name = "Chapter $i" - } - ) - } - return chapterList.reversed() - } - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return Observable.just(listOf(Page(0, "", baseUrl + chapter.url))) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return Observable.just(MangasPage(emptyList(), false)) - } - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not Used") - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not Used") - - override fun chapterListSelector() = throw UnsupportedOperationException("Not Used") - - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not Used") - - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not Used") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not Used") - - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not Used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not Used") - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not Used") - - override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not Used") - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not Used") -} diff --git a/src/en/madokami/AndroidManifest.xml b/src/en/madokami/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/madokami/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/madokami/build.gradle b/src/en/madokami/build.gradle deleted file mode 100644 index 19fa0941b..000000000 --- a/src/en/madokami/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Madokami' - pkgNameSuffix = 'en.madokami' - extClass = '.Madokami' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/madokami/res/mipmap-hdpi/ic_launcher.png b/src/en/madokami/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c6d7dd186..000000000 Binary files a/src/en/madokami/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/madokami/res/mipmap-mdpi/ic_launcher.png b/src/en/madokami/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b6f111e73..000000000 Binary files a/src/en/madokami/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/madokami/res/mipmap-xhdpi/ic_launcher.png b/src/en/madokami/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f6ab9312f..000000000 Binary files a/src/en/madokami/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/madokami/res/mipmap-xxhdpi/ic_launcher.png b/src/en/madokami/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a96a9b820..000000000 Binary files a/src/en/madokami/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/madokami/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/madokami/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 337dc7f34..000000000 Binary files a/src/en/madokami/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/madokami/res/web_hi_res_512.png b/src/en/madokami/res/web_hi_res_512.png deleted file mode 100644 index 3fdb5bbb7..000000000 Binary files a/src/en/madokami/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/madokami/src/eu/kanade/tachiyomi/extension/en/madokami/Madokami.kt b/src/en/madokami/src/eu/kanade/tachiyomi/extension/en/madokami/Madokami.kt deleted file mode 100644 index d992bf684..000000000 --- a/src/en/madokami/src/eu/kanade/tachiyomi/extension/en/madokami/Madokami.kt +++ /dev/null @@ -1,229 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.madokami - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.text.InputType -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.JsonArray -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Credentials -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.IOException -import java.net.URLDecoder -import java.net.URLEncoder -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Madokami : ConfigurableSource, ParsedHttpSource() { - override val name = "Madokami" - override val baseUrl = "https://manga.madokami.al" - override val lang = "en" - override val supportsLatest = false - - private val gson = Gson() - - private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH) - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private fun authenticate(request: Request): Request { - val credential = Credentials.basic(preferences.getString("username", "")!!, preferences.getString("password", "")!!) - return request.newBuilder().header("Authorization", credential).build() - } - - override val client: OkHttpClient = super.client.newBuilder().addInterceptor { chain -> - val response = chain.proceed(chain.request()) - if (response.code() == 401) throw IOException("You are currently logged out.\nGo to Extensions > Details to input your credentials.") - response - }.build() - - override fun latestUpdatesSelector() = "" - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Unsupported!") - override fun latestUpdatesNextPageSelector(): String? = null - override fun latestUpdatesRequest(page: Int) = throw Exception("Unsupported!") - - override fun popularMangaSelector(): String = "table.mobile-files-table tbody tr td:nth-child(1) a:nth-child(1)" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = URLDecoder.decode(element.attr("href").split("/").last(), "UTF-8").trimStart('!') - return manga - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun popularMangaRequest(page: Int): Request = authenticate(GET("$baseUrl/recent", headers)) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = authenticate(GET("$baseUrl/search?q=$query", headers)) - - override fun searchMangaSelector() = "div.container table tbody tr td:nth-child(1) a:nth-child(1)" - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsRequest(manga: SManga): Request { - val url = HttpUrl.parse(baseUrl + manga.url)!! - if (url.pathSize() > 5 && url.pathSegments()[0] == "Manga" && url.pathSegments()[1].length == 1) { - return authenticate(GET(url.newBuilder().removePathSegment(5).build().url().toExternalForm(), headers)) - } - if (url.pathSize() > 2 && url.pathSegments()[0] == "Raws") { - return authenticate(GET(url.newBuilder().removePathSegment(2).build().url().toExternalForm(), headers)) - } - return authenticate(GET(url.url().toExternalForm(), headers)) - } - - /** - * Returns the details of the manga from the given [document]. - * - * @param document the parsed document. - */ - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select("a[itemprop=\"author\"]").joinToString(", ") { it.text() } - manga.description = "Tags: " + document.select("div.genres[itemprop=\"keywords\"] a.tag.tag-category").joinToString(", ") { it.text() } - manga.genre = document.select("div.genres a.tag[itemprop=\"genre\"]").joinToString(", ") { it.text() } - manga.status = if (document.select("span.scanstatus").text() == "Yes") SManga.COMPLETED else SManga.UNKNOWN - manga.thumbnail_url = document.select("div.manga-info img[itemprop=\"image\"]").attr("src") - return manga - } - - override fun chapterListRequest(manga: SManga) = authenticate(GET("$baseUrl/" + manga.url, headers)) - - override fun chapterListParse(response: Response): List<SChapter> = super.chapterListParse(response).reversed() - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. - */ - override fun chapterListSelector() = "table#index-table > tbody > tr > td:nth-child(6) > a" - - /** - * Returns a chapter from the given element. - * - * @param element an element obtained from [chapterListSelector]. - */ - override fun chapterFromElement(element: Element): SChapter { - val el = element.parent().parent() - val chapter = SChapter.create() - chapter.url = el.select("td:nth-child(6) a").attr("href") - chapter.name = el.select("td:nth-child(1) a").text() - val date = el.select("td:nth-child(3)").text() - if (date.endsWith("ago")) { - val splitDate = date.split(" ") - val newDate = Calendar.getInstance() - val amount = splitDate[0].toInt() - when { - splitDate[1].startsWith("min") -> { - newDate.add(Calendar.MINUTE, -amount) - } - splitDate[1].startsWith("sec") -> { - newDate.add(Calendar.SECOND, -amount) - } - splitDate[1].startsWith("hour") -> { - newDate.add(Calendar.HOUR, -amount) - } - } - chapter.date_upload = newDate.time.time - } else { - chapter.date_upload = dateFormat.parse(date)?.time ?: 0L - } - return chapter - } - - override fun pageListRequest(chapter: SChapter) = authenticate(GET(chapter.url, headers)) - - override fun pageListParse(document: Document): List<Page> { - val element = document.select("div#reader") - val path = element.attr("data-path") - val files = gson.fromJson<JsonArray>(element.attr("data-files")) - val pages = mutableListOf<Page>() - for ((index, file) in files.withIndex()) { - val url = HttpUrl.Builder() - .scheme("https") - .host("manga.madokami.al") - .addPathSegments("reader/image") - .addEncodedQueryParameter("path", URLEncoder.encode(path, "UTF-8")) - .addEncodedQueryParameter("file", URLEncoder.encode(file.asString, "UTF-8")) - .build().url() - pages.add(Page(index, url.toExternalForm(), url.toExternalForm())) - } - return pages - } - - override fun imageRequest(page: Page) = authenticate(GET(page.url, headers)) - - /** - * Returns the absolute url to the source image from the document. - * - * @param document the parsed document. - */ - override fun imageUrlParse(document: Document) = "" - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val username = androidx.preference.EditTextPreference(screen.context).apply { - key = "username" - title = "Username" - - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putString(key, newValue as String).commit() - } - } - val password = androidx.preference.EditTextPreference(screen.context).apply { - key = "password" - title = "Password" - - setOnBindEditTextListener { - it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putString(key, newValue as String).commit() - } - } - - screen.addPreference(username) - screen.addPreference(password) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val username = EditTextPreference(screen.context).apply { - key = "username" - title = "Username" - - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putString(key, newValue as String).commit() - } - } - val password = EditTextPreference(screen.context).apply { - key = "password" - title = "Password" - - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putString(key, newValue as String).commit() - } - } - - screen.addPreference(username) - screen.addPreference(password) - } -} diff --git a/src/en/manga1s/AndroidManifest.xml b/src/en/manga1s/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/manga1s/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/manga1s/build.gradle b/src/en/manga1s/build.gradle deleted file mode 100644 index c83672cb2..000000000 --- a/src/en/manga1s/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manga1s' - pkgNameSuffix = 'en.manga1s' - extClass = '.manga1s' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/manga1s/res/mipmap-hdpi/ic_launcher.png b/src/en/manga1s/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 84f6a3d8e..000000000 Binary files a/src/en/manga1s/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manga1s/res/mipmap-mdpi/ic_launcher.png b/src/en/manga1s/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 34f14a113..000000000 Binary files a/src/en/manga1s/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manga1s/res/mipmap-xhdpi/ic_launcher.png b/src/en/manga1s/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 71fcd5cbd..000000000 Binary files a/src/en/manga1s/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manga1s/res/mipmap-xxhdpi/ic_launcher.png b/src/en/manga1s/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9f88596cc..000000000 Binary files a/src/en/manga1s/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manga1s/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/manga1s/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6d7f22740..000000000 Binary files a/src/en/manga1s/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manga1s/res/web_hi_res_512.png b/src/en/manga1s/res/web_hi_res_512.png deleted file mode 100644 index aa2bf15e6..000000000 Binary files a/src/en/manga1s/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/manga1s/src/eu/kanade/tachiyomi/extension/en/manga1s/manga1s.kt b/src/en/manga1s/src/eu/kanade/tachiyomi/extension/en/manga1s/manga1s.kt deleted file mode 100644 index 7d60afa67..000000000 --- a/src/en/manga1s/src/eu/kanade/tachiyomi/extension/en/manga1s/manga1s.kt +++ /dev/null @@ -1,211 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.manga1s - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class manga1s : ParsedHttpSource() { - - override val name = "Manga1s" - - override val baseUrl = "https://manga1s.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/search/?s=rank&compact=1&page=$page#paged", headers) - } - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.select("a").attr("href")) - manga.title = element.select("p.manga-list-4-item-title > a").text() - manga.thumbnail_url = element.select("a img").attr("data-src") - - return manga - } - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/search/?s=news&compact=1&page=$page#paged", headers) - } - - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga { - - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.select("a").attr("href")) - manga.title = element.select("p.manga-list-4-item-title > a").text() - manga.thumbnail_url = element.select("a img").attr("data-src") - - return manga - } - - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("text", query) - filters.forEach { filter -> - when (filter) { - is GenreList -> { - val genreInclude = filter.state.filter { it.isIncluded() }.joinToString { it.id } - val genreExclude = filter.state.filter { it.isExcluded() }.joinToString { it.id } - url.addQueryParameter("genres", genreInclude) - url.addQueryParameter("excludes", genreExclude) - } - is SortFilter -> url.addQueryParameter("s", filter.toUriPart()) - } - } - url.addQueryParameter("compact", "1") - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "ul > li:nth-child(n)" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.select("a").attr("href")) - manga.title = element.select("p.manga-list-4-item-title > a").text() - manga.thumbnail_url = element.select("a img").attr("data-src") - - return manga - } - - override fun searchMangaNextPageSelector() = "div.pager-list > div > a:nth-child(8)" - - // Details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select("div.detail-info-right > h1").text() - description = document.select("div.detail-info-right > p.fullcontent").text() - thumbnail_url = document.select("div.detail-info-cover > img").attr("data-src") - author = document.select("p.detail-info-right-say > a").text() - genre = document.select("p.detail-info-right-tag-list > a:nth-child(n)").joinToString { it.text() } - status = document.select("span.detail-info-right-title-tip").text().let { - when { - it.contains("Ongoing") -> SManga.ONGOING - it.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - } - - // Chapters - override fun chapterListSelector() = "div.chap-version.version-active #list-2 > ul > li:nth-child(n)" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - chapter.setUrlWithoutDomain(element.select("a").attr("href")) - chapter.name = element.select("a").attr("title") - - return chapter - } - - // Pages - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("div.reader-main > img:nth-child(n)").forEachIndexed { index, element -> - add(Page(index, "", element.attr("data-src"))) - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - // Filter - override fun getFilterList() = FilterList( - GenreList(genres()), - SortFilter() - ) - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first - } - - private class Genre(name: String, val id: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - private class SortFilter : UriPartFilter( - "Sort By", - arrayOf( - Pair("popularity", "Popularity"), - Pair("alphabetical", "A-Z"), - Pair("rating", "Rating"), - Pair("chapters", "Chapters"), - Pair("news", "Last Update"), - Pair("rank", "Rank"), - Pair("hourly", "Hourly View"), - Pair("daily", "Daily View") - ) - ) - - private fun genres(): List<Genre> = listOf( - Genre("4Koma", "4-Koma"), - Genre("Action", "Action"), - Genre("Adult", "Adult"), - Genre("Adventure", "Adventure"), - Genre("Comedy", "Comedy"), - Genre("Doujinshi", "Doujinshi"), - Genre("Drama", "Drama"), - Genre("Ecchi", "Ecchi"), - Genre("Fantasy", "Fantasy"), - Genre("Full Color", "full-color"), - Genre("Gender bender", "Gender-bender"), - Genre("Harem", "Harem"), - Genre("Hentai", "Hentai"), - Genre("Historical", "Historical"), - Genre("Horror", "Horror"), - Genre("Josei", "Josei"), - Genre("Loli", "Loli"), - Genre("Long Strip", "Long-strip"), - Genre("Magic", "Magic"), - Genre("Mature", "Mature"), - Genre("Martial arts", "Martial-arts"), - Genre("Mecha", "Mecha"), - Genre("Mystery", "Mystery"), - Genre("One shot", "One-Shot"), - Genre("Psychological", "Psychological"), - Genre("Romance", "Romance"), - Genre("School life", "School-life"), - Genre("Shoujo", "Shoujo"), - Genre("Slice of life", "Slice-of-life"), - Genre("Supernatural", "Supernatural"), - Genre("Shounen", "Shounen"), - Genre("Seinen", "Seinen"), - Genre("Sci-fi", "Sci-fi"), - Genre("Shounen ai", "Shounen-ai"), - Genre("Smut", "Smut"), - Genre("Shoujo ai", "Shoujo-ai"), - Genre("Sports", "Sports"), - Genre("Tragedy", "Tragedy"), - Genre("Web comic", "web-comic"), - Genre("Webtoon", "Webtoon"), - Genre("Yaoi", "Yaoi"), - Genre("Yuri", "Yuri") - ) -} diff --git a/src/en/mangadog/AndroidManifest.xml b/src/en/mangadog/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangadog/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangadog/build.gradle b/src/en/mangadog/build.gradle deleted file mode 100644 index a684883f5..000000000 --- a/src/en/mangadog/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaDog' - pkgNameSuffix = 'en.mangadog' - extClass = '.Mangadog' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangadog/res/mipmap-hdpi/ic_launcher.png b/src/en/mangadog/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f7e97b120..000000000 Binary files a/src/en/mangadog/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadog/res/mipmap-mdpi/ic_launcher.png b/src/en/mangadog/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7b00cb1b6..000000000 Binary files a/src/en/mangadog/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadog/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangadog/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index af39b2e1d..000000000 Binary files a/src/en/mangadog/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadog/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangadog/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b2e799868..000000000 Binary files a/src/en/mangadog/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadog/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangadog/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 77c429953..000000000 Binary files a/src/en/mangadog/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadog/res/web_hi_res_512.png b/src/en/mangadog/res/web_hi_res_512.png deleted file mode 100644 index 7aec497e9..000000000 Binary files a/src/en/mangadog/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangadog/src/eu/kanade/tachiyomi/extension/en/mangadog/Mangadog.kt b/src/en/mangadog/src/eu/kanade/tachiyomi/extension/en/mangadog/Mangadog.kt deleted file mode 100644 index d1de97901..000000000 --- a/src/en/mangadog/src/eu/kanade/tachiyomi/extension/en/mangadog/Mangadog.kt +++ /dev/null @@ -1,156 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangadog - -import android.net.Uri -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.text.SimpleDateFormat -import java.util.Locale - -class Mangadog : HttpSource() { - - override val name = "MangaDog" - override val baseUrl = "https://mangadog.club" - private val cdn = "https://cdn.mangadog.club" - override val lang = "en" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/index/classification/search_test?page=$page&state=all&demographic=all&genre=all", headers) - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/index/latestupdate/getUpdateResult?page=$page", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse("$baseUrl/index/keywordsearch/index").buildUpon() - .appendQueryParameter("query", query) - return GET(uri.toString(), headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - // val page = response.request().url().queryParameterValues("page").toString().toInt() - val jsonData = response.body()!!.string() - val results = JsonParser().parse(jsonData) - val data = results["data"]["data"] - val mangas = mutableListOf<SManga>() - for (i in 0 until data.asJsonArray.size()) { - mangas.add(popularMangaFromjson(data[i])) - } - - val hasNextPage = true // page < results["data"]["pageNum"].int - return MangasPage(mangas, hasNextPage) - } - - private fun popularMangaFromjson(json: JsonElement): SManga { - val manga = SManga.create() - manga.title = json["name"].string.trim() - manga.thumbnail_url = cdn + json["image"].string.replace("\\/", "/") - val searchname = json["search_name"].string - val id = json["id"].string - manga.url = "/detail/$searchname/$id.html" - return manga - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val jsonData = response.body()!!.string() - val results = JsonParser().parse(jsonData) - val data = results["data"] - val mangas = mutableListOf<SManga>() - for (i in 0 until data.asJsonArray.size()) { - mangas.add(popularMangaFromjson(data[i])) - } - - val hasNextPage = true // data.asJsonArray.size()>18 - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaParse(response: Response): MangasPage { - val jsonData = response.body()!!.string() - val results = JsonParser().parse(jsonData) - val data = results["suggestions"] - val mangas = mutableListOf<SManga>() - for (i in 0 until 1) { - mangas.add(searchMangaFromjson(data[i])) - } - - val hasNextPage = false - return MangasPage(mangas, hasNextPage) - } - - private fun searchMangaFromjson(json: JsonElement): SManga { - val manga = SManga.create() - manga.title = json["value"].string.trim() - val data = json["data"].string.replace("\\/", "/") - manga.url = "/detail/$data.html" - return manga - } - - override fun chapterListRequest(manga: SManga): Request { - val id = manga.url.substringAfterLast("/").substringBefore(".html") - return GET("$baseUrl/index/detail/getChapterList?comic_id=$id&page=1", headers) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val jsonData = response.body()!!.string() - val results = JsonParser().parse(jsonData) - val data = results["data"]["data"] - val chapters = mutableListOf<SChapter>() - for (i in 0 until data.asJsonArray.size()) { - chapters.add(chapterFromjson(data[i])) - } - return chapters - } - - private fun chapterFromjson(json: JsonElement): SChapter { - val chapter = SChapter.create() - val searchname = json["search_name"].string - val id = json["comic_id"].string - chapter.url = "/read/read/$searchname/$id.html" // The url should include the manga name but it doesn't seem to matter - chapter.name = json["name"].string.trim() - chapter.chapter_number = json["obj_id"].asFloat - chapter.date_upload = parseDate(json["create_date"].string) - return chapter - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(date)?.time ?: 0L - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - val manga = SManga.create() - manga.thumbnail_url = document.select("img.detail-post-img").attr("abs:src") - manga.description = document.select("h2.fs15 + p").text().trim() - manga.author = document.select("a[href*=artist]").text() - manga.artist = document.select("a[href*=artist]").text() - val glist = document.select("div.col-sm-10.col-xs-9.text-left.toe.mlr0.text-left-m a[href*=genre]").map { it.text().substringAfter(",").capitalize() } - manga.genre = glist.joinToString(", ") - manga.status = when (document.select("span.label.label-success").first().text()) { - "update" -> SManga.ONGOING - "finished" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - return manga - } - - override fun pageListParse(response: Response): List<Page> { - val body = response.asJsoup() - val pages = mutableListOf<Page>() - val elements = body.select("img[data-src]") - for (i in 0 until elements.size) { - pages.add(Page(i, "", elements[i].select("img").attr("data-src"))) - } - return pages - } - - override fun imageUrlParse(response: Response) = throw Exception("Not used") -} diff --git a/src/en/mangadoom/AndroidManifest.xml b/src/en/mangadoom/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangadoom/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangadoom/build.gradle b/src/en/mangadoom/build.gradle deleted file mode 100644 index 92ef9122d..000000000 --- a/src/en/mangadoom/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaDoom' - pkgNameSuffix = 'en.mangadoom' - extClass = '.MangaDoom' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangadoom/res/mipmap-hdpi/ic_launcher.png b/src/en/mangadoom/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ded04f2e8..000000000 Binary files a/src/en/mangadoom/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadoom/res/mipmap-mdpi/ic_launcher.png b/src/en/mangadoom/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b8ff4d498..000000000 Binary files a/src/en/mangadoom/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadoom/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangadoom/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3f24d8a5b..000000000 Binary files a/src/en/mangadoom/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadoom/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangadoom/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a885cb64a..000000000 Binary files a/src/en/mangadoom/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadoom/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangadoom/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2940c7c3f..000000000 Binary files a/src/en/mangadoom/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangadoom/res/web_hi_res_512.png b/src/en/mangadoom/res/web_hi_res_512.png deleted file mode 100644 index 21aae4e60..000000000 Binary files a/src/en/mangadoom/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangadoom/src/eu/kanade/tachiyomi/extension/en/mangadoom/MangaDoom.kt b/src/en/mangadoom/src/eu/kanade/tachiyomi/extension/en/mangadoom/MangaDoom.kt deleted file mode 100644 index 023dcf0cc..000000000 --- a/src/en/mangadoom/src/eu/kanade/tachiyomi/extension/en/mangadoom/MangaDoom.kt +++ /dev/null @@ -1,593 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangadoom - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.Call -import okhttp3.Callback -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.nodes.Node -import org.jsoup.nodes.TextNode -import java.io.IOException -import java.nio.charset.Charset -import java.util.Calendar - -class MangaDoom : HttpSource() { - - override val baseUrl = "https://www.mngdoom.com" - override val lang = "en" - override val name = "MangaDoom" - override val supportsLatest = true - - private val popularMangaPath = "/popular-manga/" - - private val popularMangaSelector = "div.row.manga-list-style" - - // popular - override fun popularMangaRequest(page: Int) = GET(baseUrl + popularMangaPath + page) - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - return MangasPage( - document.select(popularMangaSelector).map { - mangaFromMangaListElement(it) - }, - paginationHasNext(document) - ) - } - - // latest - private val latestMangaPath = "/latest-chapters" - - /** - * The website has a pagination problem for the latest-chapters list. - * latest-chapters/ without a page number is the first page, latest-chapters/1 is the - * second page, latest-chapters/2 is the third page, .... - */ - override fun latestUpdatesRequest(page: Int): Request { - var url = baseUrl + latestMangaPath - - if (page != 1) { - url += "/${page - 1}" - } - - return GET(url) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangaUpdates = document.select("div.manga_updates > dl > div.manga-cover > a") - - return MangasPage( - mangaUpdates.map { mangaFromMangaTitleElement(it) }, - paginationHasNext(document) - ) - } - - /** - * Checks on a page that has pagination (e.g. popular-manga and latest-chapters) - * whether or not a next page exists. - */ - private fun paginationHasNext(document: Document) = !document - .select("ul.pagination > li:contains(»)").isEmpty() - - // individual manga - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - val innerContentElement = document.select("div.content-inner.inner-page").first() - val dlElement = innerContentElement.select("div.col-md-8 > dl").first() - - return SManga.create().apply { - this.url = response.request().url().toString() - - this.title = innerContentElement - .select("h5.widget-heading:matchText").first().text() - this.thumbnail_url = innerContentElement - .select("div.col-md-4 > img").first()?.attr("src") - - this.genre = dlElement.select("dt:containsOwn(Categories:) + dd > a") - .joinToString { e -> e.attr("title") } - - this.description = innerContentElement.select("div.note").first()?.let { - descriptionProcessor(it) - } - - this.author = dlElement.select("dt:containsOwn(Author:) + dd > a") - .first()?.ownText().takeIf { it != "-" } - - this.artist = dlElement.select("dt:containsOwn(Artist:) + dd > a") - .first()?.ownText().takeIf { it != "-" } - - this.status = when ( - dlElement.select("dt:containsOwn(Status:) + dd") - .first().ownText() - ) { - "Ongoing" -> SManga.ONGOING - "Completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - } - - /** - * Manga descriptions are composed of a multitude of (sometimes nested) html-elements + free - * text and seemingly follow no common structure. - * This function is used for parsing the html manga description into a String - */ - private fun descriptionProcessor(descriptionRootNode: Node): String? { - - val descriptionStringBuilder = StringBuilder() - - /** - * Determines which String best represents a single html node. - * Does not care about any hierarchy (neither siblings nor children) - */ - fun descriptionElementProcessor(descriptionNode: Node): String? { - if (descriptionNode is Element) { - if (descriptionNode.tagName() == "br") { - return "\n" - } - } else if (descriptionNode is TextNode) { - return descriptionNode.text() - } - - return null - } - - /** - * Responsible for the flow of the description. - * Manages the description hierarchy. - */ - fun descriptionHierarchyProcessor(currentNode: Node) { - descriptionElementProcessor(currentNode)?.let { - descriptionStringBuilder.append(it) - } - - val childNodesIterator = currentNode.childNodes().iterator() - - while (childNodesIterator.hasNext()) { - descriptionHierarchyProcessor(childNodesIterator.next()) - } - - if (currentNode is Element && currentNode.tagName() == "p") { - descriptionStringBuilder.append("\n\n") - } - } - - descriptionHierarchyProcessor(descriptionRootNode) - - return if (descriptionStringBuilder.isNotEmpty()) { - descriptionStringBuilder.toString().trimEnd() - } else { - null - } - } - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = response.asJsoup().select("ul.chapter-list > li") - - return chapters.map { - SChapter.create().apply { - this.name = it.select("span.val").first().ownText() - this.url = it.select("a").first().attr("href") - this.chapter_number = this.url.split("/").last().toFloat() - - val calculatedDate = it.select("span.date").first().ownText()?.let { - parseDate(it) - } - - if (calculatedDate != null) { - this.date_upload = calculatedDate - } - } - } - } - - /** - * Extension function for Calendar, that allows for an easy manipulation of a calendar instance - */ - private fun Calendar.setWithDefaults( - year: Int = this.get(Calendar.YEAR), - month: Int = this.get(Calendar.MONTH), - date: Int = this.get(Calendar.DATE), - hourOfDay: Int = this.get(Calendar.HOUR_OF_DAY), - minute: Int = this.get(Calendar.MINUTE), - second: Int = this.get(Calendar.SECOND) - ) { - this.set(Calendar.MILLISECOND, 0) - this.set(year, month, date, hourOfDay, minute, second) - } - - private val regexFirstNumberPattern = Regex("^\\d*") - private val regexLastWordPattern = Regex("\\w*\$") - - /** - * Chapter "dates" are given by the website not as a date, but as how many seconds, minutes, - * days, months, years ago. This leads to a lot of inaccuracy, but it's the best we have. - */ - private fun parseDate(inputString: String): Long? { - - val timeDifference = regexFirstNumberPattern.find(inputString)?.let { - it.value.toInt() * (-1) - } - - val lastWord = regexLastWordPattern.find(inputString)?.value - - if (lastWord != null && timeDifference != null) { - val calculatedTime = Calendar.getInstance() - - when (lastWord) { - "Years", "Year" -> { - calculatedTime - .setWithDefaults(month = 0, date = 1, hourOfDay = 0, minute = 0, second = 0) - calculatedTime.add(Calendar.YEAR, timeDifference) - } - - "Months", "Month" -> { - calculatedTime.setWithDefaults(date = 1, hourOfDay = 0, minute = 0, second = 0) - calculatedTime.add(Calendar.MONTH, timeDifference) - } - - "Weeks", "Week" -> { - calculatedTime.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) - calculatedTime.setWithDefaults(hourOfDay = 0, minute = 0, second = 0) - calculatedTime.add(Calendar.WEEK_OF_YEAR, timeDifference) - } - "Days", "Day" -> { - calculatedTime.setWithDefaults(hourOfDay = 0, minute = 0, second = 0) - calculatedTime.add(Calendar.DATE, timeDifference) - } - "Hours", "Hour" -> { - calculatedTime.setWithDefaults(minute = 0, second = 0) - calculatedTime.add(Calendar.HOUR_OF_DAY, timeDifference) - } - "Minutes", "Minute" -> { - calculatedTime.setWithDefaults(second = 0) - calculatedTime.add(Calendar.MINUTE, timeDifference) - } - "Seconds", "Second" -> { - calculatedTime.set(Calendar.MILLISECOND, 0) - calculatedTime.add(Calendar.SECOND, timeDifference) - } - } - - return calculatedTime.time.time - } else { - return null - } - } - - private val allPagesURLPart = "/all-pages" - - override fun pageListRequest(chapter: SChapter): Request { - return GET(chapter.url + allPagesURLPart) - } - - private val imgSelector = "div.content-inner.inner-page > div > img.img-responsive" - - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - - var pageIndex = 0 - - return document.select(imgSelector) - .map { Page(pageIndex++, it.attr("src"), it.attr("src")) } - } - - override fun fetchImageUrl(page: Page) = throw UnsupportedOperationException("Not used.") - - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("Not used.") - - // search - /** - * The search functionality of the website is uses javascript to talk to an underlying API. - * The here implemented search function skips the javascript and talks directly with the API. - */ - private val underlyingSearchMangaPath = "/service/advanced_search" - - /** - * The search API won't respond properly unless a certain header field is added to each request. - * This function prepares the searchHeader by appending the header field to the default headers. - */ - private val searchHeaders: Headers = headers.newBuilder() - .set("X-Requested-With", "XMLHttpRequest") - .build() - - /** - * All search payload parameters must be sent with each request. This ensures that even if - * filters don't want to provide a payload parameter, no parameter will be missed. - */ - private val defaultSearchParameter = linkedMapOf( - Pair("type", "all"), - Pair("manga-name", ""), - Pair("author-name", ""), - Pair("artist-name", ""), - Pair("status", "both") - ) - - /** - * Search requests are made with POST requests to the search API of the website. - * Filters are first given the opportunity to overwrite the default search payload values, - * before the request body is constructed. - * GenreFilter form an exception, since they don't have default values, instead they are just - * added if they exist, or ignored if they don't exist. - */ - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val currentSearchParameter = LinkedHashMap(defaultSearchParameter) - - var potentialGenreGroupFilter: GenreGroupFilterManager.GenreGroupFilter? = null - - filters.forEach { - if (it is FormBodyFilter) it.addToFormParameters(currentSearchParameter) - if (it is GenreGroupFilterManager.GenreGroupFilter) potentialGenreGroupFilter = it - } - - if (query.isNotEmpty()) { - currentSearchParameter["manga-name"] = query - } - - val requestBodyBuilder = FormBody.Builder(Charset.forName("utf8")) - - currentSearchParameter.entries.forEach { - requestBodyBuilder.add(it.key, it.value) - if (it.key == "artist-name") { - potentialGenreGroupFilter?.run { - addToRequestPayload(requestBodyBuilder) - } - } - } - - return POST( - baseUrl + underlyingSearchMangaPath, - searchHeaders, - requestBodyBuilder.build() - ) - } - - private val searchResultSelector = "div.row" - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - return MangasPage( - document.select(searchResultSelector).map { - mangaFromMangaListElement(it) - }, - false - ) - } - - // filters - private val genreManager = GenreGroupFilterManager(client, baseUrl) - - override fun getFilterList() = FilterList( - TypeFilter(), - AuthorTextFilter(), - ArtistTextFilter(), - StatusFilter(), - genreManager.getGenreGroupFilterOrPlaceholder() - ) - - private class TypeFilter : FormBodySelectFilter( - "Type", - "type", - arrayOf( - Pair("japanese", "Japanese Manga"), - Pair("korean", "Korean Manhwa"), - Pair("chinese", "Chinese Manhua"), - Pair("all", "All") - ), - 3 - ) - - private class AuthorTextFilter : Filter.Text("Author"), FormBodyFilter { - override fun addToFormParameters(formParameters: MutableMap<String, String>) { - formParameters["author-name"] = state - } - } - - private class ArtistTextFilter : Filter.Text("Artist"), FormBodyFilter { - override fun addToFormParameters(formParameters: MutableMap<String, String>) { - formParameters["artist-name"] = state - } - } - - private class StatusFilter : FormBodySelectFilter( - "Status", - "status", - arrayOf( - Pair("ongoing", "Ongoing"), - Pair("completed", "Completed"), - Pair("both", "Both") - ), - 2 - ) - - /** - * GenreFilter aren't hard coded into this extension, instead it relies on asynchronous-fetching - * of Genre information from the advanced search page of the MangaDoom website. - * GenreFilter have to be fetched asynchronous, otherwise it would lead to a - * NetworkOnMainThreadException. In case Genre information isn't available at the time where - * the filters are created, a substitute Filter object is returned and a new website request is - * made. - */ - private class GenreGroupFilterManager(val client: OkHttpClient, val baseUrl: String) { - - fun getGenreGroupFilterOrPlaceholder(): Filter<*> { - return when (val potentialGenreGroup = callForGenreGroup()) { - null -> GenreNotAvailable() - else -> potentialGenreGroup - } - } - - private class GenreNotAvailable : - Filter.Header("Reset for genre filter") - - private class GenreFilter(val payloadParam: String, displayName: String) : - Filter.CheckBox(displayName) - - class GenreGroupFilter(generatedGenreList: List<GenreFilter>) : - Filter.Group<GenreFilter>("Genres", generatedGenreList) { - fun addToRequestPayload(formBodyBuilder: FormBody.Builder) { - state.filter { it.state } - .forEach { formBodyBuilder.add("include[]", it.payloadParam) } - } - } - - private var genreFiltersContent: List<Pair<String, String>>? = null - private var genreFilterContentFrom: Long? = null - - /** - * Checks if an object (e.g. cached response) isn't older than 15 minutes, by comparing its - * timestamp with the current time - */ - private fun contentUpToDate(compareTimestamp: Long?): Boolean = - ( - compareTimestamp != null && - (System.currentTimeMillis() - compareTimestamp < 15 * 60 * 1000) - ) - - /** - * Used to generate a GenreGroupFilter from cached Pair objects or (if the cached pairs are - * unavailable) resorts a fetch approach. - */ - private fun callForGenreGroup(): GenreGroupFilter? { - fun genreContentListToGenreGroup(genreFiltersContent: List<Pair<String, String>>) = - GenreGroupFilter( - genreFiltersContent.map { singleGenreContent -> - GenreFilter(singleGenreContent.first, singleGenreContent.second) - } - ) - - val genreGroupFromVar = genreFiltersContent?.let { genreList -> - genreContentListToGenreGroup(genreList) - } - - return if (genreGroupFromVar != null && contentUpToDate(genreFilterContentFrom)) { - genreGroupFromVar - } else { - generateFilterContent()?.let { - genreContentListToGenreGroup(it) - } - } - } - - private val advancedSearchPagePath = "/advanced-search" - - /** - * The fetch approach. Attempts to construct genre pairs from a cached response or starts a - * new asynchronous web request. - */ - private fun generateFilterContent(): List<Pair<String, String>>? { - fun responseToGenreFilterContentPair(genreResponse: Response): - List<Pair<String, String>> { - val document = genreResponse.asJsoup() - - return document.select("ul.manga-cat > li").map { - Pair( - it.select("span.fa").first().attr("data-id"), - it.ownText() - ) - } - } - - val genreResponse = client - .newCall( - GET( - url = baseUrl + advancedSearchPagePath, - cache = CacheControl.FORCE_CACHE - ) - ).execute() - - return if (genreResponse.code() == 200 && - contentUpToDate(genreResponse.receivedResponseAtMillis()) - ) { - responseToGenreFilterContentPair(genreResponse) - } else { - client.newCall( - GET( - url = baseUrl + advancedSearchPagePath, - cache = CacheControl.FORCE_NETWORK - ) - ).enqueue( - object : Callback { - override fun onFailure(call: Call, e: IOException) { - throw e - } - - override fun onResponse(call: Call, response: Response) { - genreFilterContentFrom = response.receivedResponseAtMillis() - genreFiltersContent = responseToGenreFilterContentPair(response) - } - } - ) - null - } - } - } - - /** - * Used to create a select filter. Each entry has a name and a display name. - */ - private open class FormBodySelectFilter( - displayName: String, - val payloadParam: String, - val vals: Array<Pair<String, String>>, - defaultValue: Int = 0 - ) : - Filter.Select<String>( - displayName, - vals.map { it.second }.toTypedArray(), - defaultValue - ), - FormBodyFilter { - override fun addToFormParameters(formParameters: MutableMap<String, String>) { - formParameters[payloadParam] = vals[state].first - } - } - - /** - * Implemented by filters that are capable of to modifying a payload parameter. - */ - private interface FormBodyFilter { - fun addToFormParameters(formParameters: MutableMap<String, String>) - } - - // common - /** - * The last step for parsing popular manga and search results (from jsoup element to [SManga] - */ - private fun mangaFromMangaListElement(mangaListElement: Element): SManga { - val titleElement = mangaListElement.select("div.col-md-4 > a").first() - return mangaFromMangaTitleElement(titleElement) - } - - /** - * Used for latest, popular and search manga parsing to create [SManga] objects - */ - private fun mangaFromMangaTitleElement(mangaTitleElement: Element): SManga = SManga.create() - .apply { - this.title = mangaTitleElement.attr("title") - this.setUrlWithoutDomain(mangaTitleElement.attr("href")) - this.thumbnail_url = mangaTitleElement.select("img").first() - .attr("src") - } -} diff --git a/src/en/mangaeden/AndroidManifest.xml b/src/en/mangaeden/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangaeden/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangaeden/build.gradle b/src/en/mangaeden/build.gradle deleted file mode 100644 index 28eda20d9..000000000 --- a/src/en/mangaeden/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangaeden' - pkgNameSuffix = 'en.mangaeden' - extClass = '.Mangaeden' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangaeden/res/mipmap-hdpi/ic_launcher.png b/src/en/mangaeden/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d5fe0354d..000000000 Binary files a/src/en/mangaeden/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaeden/res/mipmap-mdpi/ic_launcher.png b/src/en/mangaeden/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fba82aa13..000000000 Binary files a/src/en/mangaeden/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaeden/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangaeden/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bc5643ede..000000000 Binary files a/src/en/mangaeden/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaeden/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangaeden/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 4b3eb907e..000000000 Binary files a/src/en/mangaeden/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaeden/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangaeden/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 58d5afdc0..000000000 Binary files a/src/en/mangaeden/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaeden/res/web_hi_res_512.png b/src/en/mangaeden/res/web_hi_res_512.png deleted file mode 100644 index a4d44efcd..000000000 Binary files a/src/en/mangaeden/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangaeden/src/eu/kanade/tachiyomi/extension/en/mangaeden/Mangaeden.kt b/src/en/mangaeden/src/eu/kanade/tachiyomi/extension/en/mangaeden/Mangaeden.kt deleted file mode 100644 index fa3a960cc..000000000 --- a/src/en/mangaeden/src/eu/kanade/tachiyomi/extension/en/mangaeden/Mangaeden.kt +++ /dev/null @@ -1,222 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangaeden - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Mangaeden : ParsedHttpSource() { - - override val name = "Manga Eden" - - override val baseUrl = "https://www2.mangaeden.com" - - override val lang = "en" - - override val supportsLatest = true - - // so hcaptcha won't be triggered on images - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/en/en-directory/?order=3&page=$page", headers) - - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/en/en-directory/?order=1&page=$page", headers) - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/en/en-directory/")?.newBuilder()!!.addQueryParameter("title", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is StatusList -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("status", it) } - is Types -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("type", it) } - is GenreList -> - filter.state - .filter { !it.isIgnored() } - .forEach { genre -> url.addQueryParameter(if (genre.isIncluded()) "categoriesInc" else "categoriesExcl", genre.id) } - is TextField -> url.addQueryParameter(filter.key, filter.state) - is OrderBy -> filter.state?.let { - val sortId = it.index - url.addQueryParameter("order", if (it.ascending) "-$sortId" else "$sortId") - } - } - } - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "table#mangaList tbody tr td:first-child a" - - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.text() - } - - override fun searchMangaNextPageSelector() = "a:has(span.next)" - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val infos = document.select("div.rightbox") - - author = infos.select("a[href^=/en/en-directory/?author]").first()?.text() - artist = infos.select("a[href^=/en/en-directory/?artist]").first()?.text() - genre = infos.select("a[href^=/en/en-directory/?categoriesInc]").joinToString { it.text() } - description = document.select("h2#mangaDescription").text() - status = parseStatus(infos.select("h4:containsOwn(Status)").first()?.nextSibling().toString()) - thumbnail_url = document.select("div.mangaImage2 > img").attr("abs:src") - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing", true) -> SManga.ONGOING - status.contains("Completed", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div#leftContent > table > tbody > tr" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val a = element.select("a[href^=/en/en-manga/]").first() - - setUrlWithoutDomain(a.attr("href")) - name = a?.select("b")?.first()?.text().orEmpty() - date_upload = element.select("td.chapterDate").first()?.text()?.let { parseChapterDate(it.trim()) } ?: 0L - } - - private fun parseChapterDate(date: String): Long = - when { - "Today" in date -> { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - "Yesterday" in date -> { - Calendar.getInstance().apply { - add(Calendar.DATE, -1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - else -> - try { - SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("select#pageSelect option").mapIndexed { i, element -> - Page(i, element.attr("abs:value")) - } - } - - override fun imageUrlParse(document: Document): String = document.select("a.next img").attr("abs:src") - - private class NamedId(name: String, val id: Int) : Filter.CheckBox(name) - private class Genre(name: String, val id: String) : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class OrderBy : Filter.Sort( - "Order by", - arrayOf("Manga title", "Views", "Chapters", "Latest chapter"), - Selection(1, false) - ) - - private class StatusList(statuses: List<NamedId>) : Filter.Group<NamedId>("Stato", statuses) - private class Types(types: List<NamedId>) : Filter.Group<NamedId>("Tipo", types) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - override fun getFilterList() = FilterList( - TextField("Author", "author"), - TextField("Artist", "artist"), - OrderBy(), - Types(types()), - StatusList(statuses()), - GenreList(genres()) - ) - - private fun types() = listOf( - NamedId("Japanese Manga", 0), - NamedId("Korean Manhwa", 1), - NamedId("Chinese Manhua", 2), - NamedId("Comic", 3), - NamedId("Doujinshi", 4) - ) - - private fun statuses() = listOf( - NamedId("Ongoing", 1), - NamedId("Completed", 2), - NamedId("Suspended", 0) - ) - - private fun genres() = listOf( - Genre("Action", "4e70e91bc092255ef70016f8"), - Genre("Adult", "4e70e92fc092255ef7001b94"), - Genre("Adventure", "4e70e918c092255ef700168e"), - Genre("Comedy", "4e70e918c092255ef7001675"), - Genre("Doujinshi", "4e70e928c092255ef7001a0a"), - Genre("Drama", "4e70e918c092255ef7001693"), - Genre("Ecchi", "4e70e91ec092255ef700175e"), - Genre("Fantasy", "4e70e918c092255ef7001676"), - Genre("Gender Bender", "4e70e921c092255ef700184b"), - Genre("Harem", "4e70e91fc092255ef7001783"), - Genre("Historical", "4e70e91ac092255ef70016d8"), - Genre("Horror", "4e70e919c092255ef70016a8"), - Genre("Josei", "4e70e920c092255ef70017de"), - Genre("Martial Arts", "4e70e923c092255ef70018d0"), - Genre("Mature", "4e70e91bc092255ef7001705"), - Genre("Mecha", "4e70e922c092255ef7001877"), - Genre("Mystery", "4e70e918c092255ef7001681"), - Genre("One Shot", "4e70e91dc092255ef7001747"), - Genre("Psychological", "4e70e919c092255ef70016a9"), - Genre("Romance", "4e70e918c092255ef7001677"), - Genre("School Life", "4e70e918c092255ef7001688"), - Genre("Sci-fi", "4e70e91bc092255ef7001706"), - Genre("Seinen", "4e70e918c092255ef700168b"), - Genre("Shoujo", "4e70e918c092255ef7001667"), - Genre("Shounen", "4e70e918c092255ef700166f"), - Genre("Slice of Life", "4e70e918c092255ef700167e"), - Genre("Smut", "4e70e922c092255ef700185a"), - Genre("Sports", "4e70e91dc092255ef700172e"), - Genre("Supernatural", "4e70e918c092255ef700166a"), - Genre("Tragedy", "4e70e918c092255ef7001672"), - Genre("Webtoons", "4e70ea70c092255ef7006d9c"), - Genre("Yaoi", "4e70e91ac092255ef70016e5"), - Genre("Yuri", "4e70e92ac092255ef7001a57") - ) -} diff --git a/src/en/mangafast/AndroidManifest.xml b/src/en/mangafast/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangafast/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangafast/build.gradle b/src/en/mangafast/build.gradle deleted file mode 100644 index 85b06e487..000000000 --- a/src/en/mangafast/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaFast' - pkgNameSuffix = 'en.mangafast' - extClass = '.MangaFast' - extVersionCode = 8 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangafast/res/mipmap-hdpi/ic_launcher.png b/src/en/mangafast/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index c6171bf6c..000000000 Binary files a/src/en/mangafast/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafast/res/mipmap-mdpi/ic_launcher.png b/src/en/mangafast/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index e9e1ca91e..000000000 Binary files a/src/en/mangafast/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafast/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangafast/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 6077690fc..000000000 Binary files a/src/en/mangafast/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafast/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangafast/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 188decf85..000000000 Binary files a/src/en/mangafast/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafast/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangafast/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index b368d915c..000000000 Binary files a/src/en/mangafast/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafast/res/web_hi_res_512.png b/src/en/mangafast/res/web_hi_res_512.png deleted file mode 100644 index d27c2c668..000000000 Binary files a/src/en/mangafast/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangafast/src/eu/kanade/tachiyomi/extension/en/mangafast/MangaFast.kt b/src/en/mangafast/src/eu/kanade/tachiyomi/extension/en/mangafast/MangaFast.kt deleted file mode 100644 index 7c17d075f..000000000 --- a/src/en/mangafast/src/eu/kanade/tachiyomi/extension/en/mangafast/MangaFast.kt +++ /dev/null @@ -1,127 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangafast - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaFast : ParsedHttpSource() { - override val name = "MangaFast" - - override val baseUrl = "https://mangafast.net" - - override val lang = "en" - - override val supportsLatest = true - - // popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/list-manga" + if (page > 1) "/page/$page" else "", headers) - } - - override fun popularMangaSelector() = ".list-content .ls4 .ls4v" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - val a = element.select("a") - setUrlWithoutDomain(a.attr("href")) - title = a.attr("title") - thumbnail_url = a.select("img").attr("src") - } - - override fun popularMangaNextPageSelector() = ".btn-w a:contains(Next »)" - - // latest - override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) - - override fun latestUpdatesSelector() = ".ls8w div.ls8 .ls8v" - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String? = null - - // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/page/$page/?s=$query", headers) - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // manga details - override fun mangaDetailsParse(document: Document): SManga { - val articleTitle = document.select("article header[id=article-title]") - val articleInfo = document.select("article section[id=article-info]") - - val manga = SManga.create().apply { - title = articleTitle.select("h1[itemprop=name]").text().trim() - description = articleTitle.select("p.desc").text().trim() - thumbnail_url = articleInfo.select("img.shadow").attr("src") - } - articleInfo.select("table.inftable tbody tr").forEach { - val row = it.select("td") - when (row[0].text()) { - "Comic Title" -> manga.title = row[1].text().trim() - "Genre" -> manga.genre = row[1].text().trim().removeSuffix(",") - "Author" -> manga.author = row[1].text().trim() - "Status" -> manga.status = parseStatus(row[1].text()) - } - } - - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapter list - override fun chapterListSelector() = ".chapter-link:not([title=\"Spoiler Manga\"])" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.select(".left").text() - date_upload = parseDate(element.select(".right").text()) - } - - private fun parseDate(text: String): Long { - return try { - dateFormat.parse(text.trim())?.time ?: 0L - } catch (pe: ParseException) { // this can happen for spoiler & release date entries - 0L - } - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("yyyy-MM-dd", Locale.US) - } - } - - // pages - override fun pageListParse(document: Document): List<Page> { - return document.select(".content-comic > img").mapIndexed { i, element -> - var url = element.attr("abs:data-src") - - if (url.isEmpty()) { - url = element.attr("abs:src") - } - - Page(i, "", url) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/mangafreak/AndroidManifest.xml b/src/en/mangafreak/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangafreak/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangafreak/build.gradle b/src/en/mangafreak/build.gradle deleted file mode 100644 index 20f475ad3..000000000 --- a/src/en/mangafreak/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangafreak' - pkgNameSuffix = 'en.mangafreak' - extClass = '.Mangafreak' - extVersionCode = 2 - libVersion = '1.2' -} - - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangafreak/res/mipmap-hdpi/ic_launcher.png b/src/en/mangafreak/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 96a1745fd..000000000 Binary files a/src/en/mangafreak/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafreak/res/mipmap-mdpi/ic_launcher.png b/src/en/mangafreak/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b9f4f9569..000000000 Binary files a/src/en/mangafreak/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafreak/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangafreak/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index cf2f50992..000000000 Binary files a/src/en/mangafreak/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafreak/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangafreak/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c6ad3bb6d..000000000 Binary files a/src/en/mangafreak/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafreak/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangafreak/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1d68bf882..000000000 Binary files a/src/en/mangafreak/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangafreak/res/web_hi_res_512.png b/src/en/mangafreak/res/web_hi_res_512.png deleted file mode 100644 index 5bc1cd71c..000000000 Binary files a/src/en/mangafreak/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangafreak/src/eu/kanade/tachiyomi/extension/en/mangafreak/Mangafreak.kt b/src/en/mangafreak/src/eu/kanade/tachiyomi/extension/en/mangafreak/Mangafreak.kt deleted file mode 100644 index 4cbec0d1d..000000000 --- a/src/en/mangafreak/src/eu/kanade/tachiyomi/extension/en/mangafreak/Mangafreak.kt +++ /dev/null @@ -1,192 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangafreak - -import android.net.Uri -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class Mangafreak : ParsedHttpSource() { - override val name: String = "Mangafreak" - override val lang: String = "en" - override val baseUrl: String = "https://w11.mangafreak.net" - override val supportsLatest: Boolean = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build()!! - - private fun mangaFromElement(element: Element, urlSelector: String): SManga { - return SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:src") - element.select(urlSelector).apply { - title = text() - url = attr("href") - } - } - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/Genre/All/$page", headers) - } - override fun popularMangaNextPageSelector(): String? = "a.next_p" - override fun popularMangaSelector(): String = "div.ranking_item" - override fun popularMangaFromElement(element: Element): SManga = mangaFromElement(element, "a") - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/Latest_Releases/$page", headers) - } - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = "div.latest_releases_item" - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:src").replace("mini", "manga").substringBeforeLast("/") + ".jpg" - element.select("a").apply { - title = text() - url = attr("href") - } - } - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse(baseUrl).buildUpon() - if (!query.isBlank()) { - uri.appendPath("Search") - .appendPath(query) - } - filters.forEach { filter -> - uri.appendPath("Genre") - when (filter) { - is GenreList -> { - uri.appendPath( - filter.state.joinToString("") { - when (it.state) { - Filter.TriState.STATE_IGNORE -> "0" - Filter.TriState.STATE_INCLUDE -> "1" - Filter.TriState.STATE_EXCLUDE -> "2" - else -> "0" - } - } - ) - } - } - uri.appendEncodedPath("Status/0/Type/0") - } - return GET(uri.toString(), headers) - } - override fun searchMangaNextPageSelector(): String? = null - override fun searchMangaSelector(): String = "div.manga_search_item , div.mangaka_search_item" - override fun searchMangaFromElement(element: Element): SManga = mangaFromElement(element, "h3 a") - - // Details - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - thumbnail_url = document.select("div.manga_series_image img").attr("abs:src") - title = document.select("div.manga_series_data h5").text() - status = when (document.select("div.manga_series_data > div:eq(3)").text()) { - "ON-GOING" -> SManga.ONGOING - "COMPLETED" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - author = document.select("div.manga_series_data > div:eq(4)").text() - artist = document.select("div.manga_series_data > div:eq(5)").text() - genre = document.select("div.series_sub_genre_list a").joinToString { it.text() } - description = document.select("div.manga_series_description p").text() - } - - // Chapter - - override fun chapterListSelector(): String = "div.manga_series_list tbody tr" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.select(" td:eq(0)").text() - chapter_number = name.substringAfter("Chapter ").substringBefore(" -").toFloat() - url = element.select("a").attr("href") - date_upload = parseDate(element.select(" td:eq(1)").text()) - } - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy/MM/dd", Locale.US).parse(date)?.time ?: 0L - } - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - // Pages - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("img#gohere").forEachIndexed { index, element -> - add(Page(index, "", element.attr("abs:src"))) - } - } - - override fun imageUrlParse(document: Document): String { - throw Exception("Not Used") - } - - // Filter - - private class Genre(name: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - override fun getFilterList() = FilterList( - GenreList(getGenreList()) - ) - private fun getGenreList() = listOf( - Genre("Act"), - Genre("Adult"), - Genre("Adventure"), - Genre("Ancients"), - Genre("Animated"), - Genre("Comedy"), - Genre("Demons"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Horror"), - Genre("Josei"), - Genre("Magic"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Military"), - Genre("Mystery"), - Genre("One Shot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci Fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujoai"), - Genre("Shounen"), - Genre("Shounenai"), - Genre("Slice Of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Super Power"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Vampire"), - Genre("Yaoi"), - Genre("Yuri") - ) -} diff --git a/src/en/mangahasu/AndroidManifest.xml b/src/en/mangahasu/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangahasu/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangahasu/build.gradle b/src/en/mangahasu/build.gradle deleted file mode 100644 index 6fdafce74..000000000 --- a/src/en/mangahasu/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangahasu' - pkgNameSuffix = 'en.mangahasu' - extClass = '.Mangahasu' - extVersionCode = 10 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangahasu/res/mipmap-hdpi/ic_launcher.png b/src/en/mangahasu/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cc434ca65..000000000 Binary files a/src/en/mangahasu/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahasu/res/mipmap-mdpi/ic_launcher.png b/src/en/mangahasu/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e4d5147b3..000000000 Binary files a/src/en/mangahasu/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahasu/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangahasu/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8ba900d5b..000000000 Binary files a/src/en/mangahasu/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahasu/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangahasu/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d740a6fa9..000000000 Binary files a/src/en/mangahasu/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahasu/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangahasu/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c68f6c974..000000000 Binary files a/src/en/mangahasu/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahasu/res/web_hi_res_512.png b/src/en/mangahasu/res/web_hi_res_512.png deleted file mode 100644 index dc4573fc6..000000000 Binary files a/src/en/mangahasu/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangahasu/src/eu/kanade/tachiyomi/extension/en/mangahasu/Mangahasu.kt b/src/en/mangahasu/src/eu/kanade/tachiyomi/extension/en/mangahasu/Mangahasu.kt deleted file mode 100644 index 4cc744e68..000000000 --- a/src/en/mangahasu/src/eu/kanade/tachiyomi/extension/en/mangahasu/Mangahasu.kt +++ /dev/null @@ -1,324 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangahasu - -import android.util.Base64 -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Mangahasu : ParsedHttpSource() { - - override val name = "Mangahasu" - - override val baseUrl = "https://mangahasu.se" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/directory.html?page=$page", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/latest-releases.html?page=$page", headers) - - override fun popularMangaSelector() = "div.div_item" - - override fun latestUpdatesSelector() = "div.div_item" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").first().attr("src") - element.select("a.name-manga").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a[title = Tiếp]" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/advanced-search.html")!!.newBuilder() - url.addQueryParameter("keyword", query) - url.addQueryParameter("page", page.toString()) - - filters.forEach { filter -> - when (filter) { - is AuthorFilter -> url.addQueryParameter("author", filter.state) - is ArtistFilter -> url.addQueryParameter("artist", filter.state) - is StatusFilter -> url.addQueryParameter("status", filter.toUriPart()) - is TypeFilter -> url.addQueryParameter("typeid", filter.toUriPart()) - is GenreFilter -> { - filter.state.forEach { - when (it.state) { - Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("g_i[]", it.id) - Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("g_e[]", it.id) - } - } - } - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = - popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = - popularMangaFromElement(element) - - // max 200 results - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select(".info-c").first() - - val manga = SManga.create() - manga.author = infoElement.select(".info")[0].text() - manga.artist = infoElement.select(".info")[1].text() - manga.genre = infoElement.select(".info")[3].text() - manga.status = parseStatus(infoElement.select(".info")[4].text()) - manga.description = document.select("div.content-info:has(h3:contains(summary)) div").first()?.text() - manga.thumbnail_url = document.select("div.info-img img").attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("Ongoing") -> SManga.ONGOING - element.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "tbody tr" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - - chapter.date_upload = element.select(".date-updated").last()?.text()?.let { - SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(it)?.time ?: 0 - } ?: 0 - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""Chapter\s([0-9]+)""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[1]?.value!!.toFloat() - } - } - } - } - - override fun pageListParse(document: Document): List<Page> { - - // Grab All Pages from site - // Some images are place holders on new chapters. - - val pages = mutableListOf<Page>().apply { - document.select("div.img img").forEach { - val pageNumber = it.attr("class").substringAfter("page").toInt() - add(Page(pageNumber, "", it.attr("src"))) - } - } - - // Some images are not yet loaded onto Mangahasu's image server. - // Decode temporary URLs and replace placeholder images. - - val lstDUrls = - document.select("script:containsData(lstDUrls)").html().substringAfter("lstDUrls") - .substringAfter("\"").substringBefore("\"") - if (lstDUrls != "W10=") { // Base64 = [] or empty file - val decoded = String(Base64.decode(lstDUrls, Base64.DEFAULT)) - val json = JsonParser().parse(decoded).array - json.forEach { - val pageNumber = it["page"].int - pages.removeAll { page -> page.index == pageNumber } - pages.add(Page(pageNumber, "", it["url"].string)) - } - } - pages.sortBy { page -> page.index } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - // Filters - - override fun getFilterList() = FilterList( - AuthorFilter(), - ArtistFilter(), - StatusFilter(), - TypeFilter(), - GenreFilter(getGenreList()) - ) - - private class AuthorFilter : Filter.Text("Author") - - private class ArtistFilter : Filter.Text("Artist") - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private class TypeFilter : UriPartFilter( - "Type", - arrayOf( - Pair("Any", ""), - Pair("Manga", "10"), - Pair("Manhwa", "12"), - Pair("Manhua", "19") - ) - ) - - private class StatusFilter : UriPartFilter( - "Status", - arrayOf( - Pair("Any", ""), - Pair("Completed", "1"), - Pair("Ongoing", "2") - ) - ) - - private class Genre(name: String, val id: String) : Filter.TriState(name) - private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - private fun getGenreList() = listOf( - Genre("4-koma", "46"), - Genre("Action", "1"), - Genre("Adaptation", "101"), - Genre("Adult", "2"), - Genre("Adventure", "3"), - Genre("Aliens", "103"), - Genre("Animals", "73"), - Genre("Anime", "57"), - Genre("Anthology", "99"), - Genre("Award Winning", "48"), - Genre("Bara", "60"), - Genre("Comedy", "4"), - Genre("Comic", "5"), - Genre("Cooking", "6"), - Genre("Crime", "92"), - Genre("Crossdressing", "86"), - Genre("Delinquents", "83"), - Genre("Demons", "51"), - Genre("Doujinshi", "7"), - Genre("Drama", "8"), - Genre("Ecchi", "9"), - Genre("Fan Colored", "107"), - Genre("Fantasy", "10"), - Genre("Full Color", "95"), - Genre("Game", "68"), - Genre("Gender Bender", "11"), - Genre("Genderswap", "81"), - Genre("Ghosts", "90"), - Genre("Gore", "100"), - Genre("Gyaru", "97"), - Genre("Harem", "12"), - Genre("Historical", "13"), - Genre("Horror", "14"), - Genre("Incest", "84"), - Genre("Isekai", "67"), - Genre("Josei", "15"), - Genre("Live Action", "59"), - Genre("Loli", "91"), - Genre("Lolicon", "16"), - Genre("Long Strip", "93"), - Genre("Mafia", "113"), - Genre("Magic", "55"), - Genre("Magical Girls", "89"), - Genre("Manga Reviews", "64"), - Genre("Martial Arts", "20"), - Genre("Mature", "21"), - Genre("Mecha", "22"), - Genre("Medical", "23"), - Genre("Military", "62"), - Genre("Monster Girls", "87"), - Genre("Monsters", "72"), - Genre("Music", "24"), - Genre("Mystery", "25"), - Genre("Ninja", "112"), - Genre("Office Workers", "80"), - Genre("Official Colored", "96"), - Genre("One shot", "26"), - Genre("Others", "114"), - Genre("Philosophical", "110"), - Genre("Police", "105"), - Genre("Post-Apocalyptic", "76"), - Genre("Psychological", "27"), - Genre("Reincarnation", "74"), - Genre("Reverse harem", "69"), - Genre("Romance", "28"), - Genre("Samurai", "108"), - Genre("School Life", "29"), - Genre("Sci-fi", "30"), - Genre("Seinen", "31"), - Genre("Seinen Supernatural", "66"), - Genre("Sexual Violence", "98"), - Genre("Shota", "104"), - Genre("Shotacon", "32"), - Genre("Shoujo", "33"), - Genre("Shoujo Ai", "34"), - Genre("Shoujoai", "63"), - Genre("Shounen", "35"), - Genre("Shounen Ai", "36"), - Genre("Shounenai", "61"), - Genre("Slice of Life", "37"), - Genre("Smut", "38"), - Genre("Sports", "39"), - Genre("Super power", "70"), - Genre("Superhero", "88"), - Genre("Supernatural", "40"), - Genre("Survival", "77"), - Genre("Thriller", "75"), - Genre("Time Travel", "78"), - Genre("Traditional Games", "111"), - Genre("Tragedy", "41"), - Genre("Uncategorized", "65"), - Genre("User Created", "102"), - Genre("Vampire", "58"), - Genre("Vampires", "82"), - Genre("Video Games", "85"), - Genre("Virtual Reality", "109"), - Genre("Web Comic", "94"), - Genre("Webtoon", "42"), - Genre("Webtoons", "56"), - Genre("Wuxia", "71"), - Genre("Yaoi", "43"), - Genre("Youkai", "106"), - Genre("Yuri", "44"), - Genre("Zombies", "79") - ) -} diff --git a/src/en/mangahere/AndroidManifest.xml b/src/en/mangahere/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangahere/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangahere/build.gradle b/src/en/mangahere/build.gradle deleted file mode 100644 index 68be23ceb..000000000 --- a/src/en/mangahere/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangahere' - pkgNameSuffix = 'en.mangahere' - extClass = '.Mangahere' - extVersionCode = 15 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangahere/res/mipmap-hdpi/ic_launcher.png b/src/en/mangahere/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 46ae5585f..000000000 Binary files a/src/en/mangahere/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahere/res/mipmap-mdpi/ic_launcher.png b/src/en/mangahere/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0a3a976b3..000000000 Binary files a/src/en/mangahere/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahere/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangahere/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e28376d0c..000000000 Binary files a/src/en/mangahere/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahere/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangahere/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b1b7ba6a0..000000000 Binary files a/src/en/mangahere/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahere/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangahere/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3e248a7c6..000000000 Binary files a/src/en/mangahere/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahere/res/web_hi_res_512.png b/src/en/mangahere/res/web_hi_res_512.png deleted file mode 100644 index b1ea1fd7a..000000000 Binary files a/src/en/mangahere/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangahere/src/eu/kanade/tachiyomi/extension/en/mangahere/Mangahere.kt b/src/en/mangahere/src/eu/kanade/tachiyomi/extension/en/mangahere/Mangahere.kt deleted file mode 100644 index d0cfaa8d8..000000000 --- a/src/en/mangahere/src/eu/kanade/tachiyomi/extension/en/mangahere/Mangahere.kt +++ /dev/null @@ -1,375 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangahere - -import com.squareup.duktape.Duktape -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Cookie -import okhttp3.CookieJar -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Mangahere : ParsedHttpSource() { - - override val id: Long = 2 - - override val name = "Mangahere" - - override val baseUrl = "https://www.mangahere.cc" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = super.client.newBuilder() - .cookieJar( - object : CookieJar { - override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {} - override fun loadForRequest(url: HttpUrl): MutableList<Cookie> { - return ArrayList<Cookie>().apply { - add( - Cookie.Builder() - .domain("www.mangahere.cc") - .path("/") - .name("isAdult") - .value("1") - .build() - ) - } - } - } - ) - .build() - - override fun popularMangaSelector() = ".manga-list-1-list li" - - override fun latestUpdatesSelector() = ".manga-list-1-list li" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/directory/$page.htm", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/directory/$page.htm?latest", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - val titleElement = element.select("a").first() - manga.title = titleElement.attr("title") - manga.setUrlWithoutDomain(titleElement.attr("href")) - manga.thumbnail_url = element.select("img.manga-list-1-cover") - ?.first()?.attr("src") - - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "div.pager-list-left a:last-child" - - override fun latestUpdatesNextPageSelector() = "div.pager-list-left a:last-child" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - - filters.forEach { filter -> - when (filter) { - is TypeList -> url.addEncodedQueryParameter("type", types[filter.values[filter.state]].toString()) - is CompletionList -> url.addEncodedQueryParameter("st", filter.state.toString()) - is GenreList -> { - val includeGenres = mutableSetOf<Int>() - val excludeGenres = mutableSetOf<Int>() - filter.state.forEach { genre -> - if (genre.isIncluded()) includeGenres.add(genre.id) - if (genre.isExcluded()) excludeGenres.add(genre.id) - } - url.apply { - addEncodedQueryParameter("genres", includeGenres.joinToString(",")) - addEncodedQueryParameter("nogenres", excludeGenres.joinToString(",")) - } - } - } - } - - url.apply { - addEncodedQueryParameter("page", page.toString()) - addEncodedQueryParameter("title", query) - addEncodedQueryParameter("sort", null) - addEncodedQueryParameter("stype", 1.toString()) - addEncodedQueryParameter("name", null) - addEncodedQueryParameter("author_method", "cw") - addEncodedQueryParameter("author", null) - addEncodedQueryParameter("artist_method", "cw") - addEncodedQueryParameter("artist", null) - addEncodedQueryParameter("rating_method", "eq") - addEncodedQueryParameter("rating", null) - addEncodedQueryParameter("released_method", "eq") - addEncodedQueryParameter("released", null) - } - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = ".manga-list-4-list > li" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - val titleEl = element.select(".manga-list-4-item-title > a").first() - manga.setUrlWithoutDomain(titleEl?.attr("href") ?: "") - manga.title = titleEl?.attr("title") ?: "" - return manga - } - - override fun searchMangaNextPageSelector() = "div.pager-list-left a:last-child" - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select(".detail-info-right-say > a")?.first()?.text() - manga.artist = "" - manga.genre = document.select(".detail-info-right-tag-list > a")?.joinToString { it.text() } - manga.description = document.select(".fullcontent")?.first()?.text() - manga.thumbnail_url = document.select("img.detail-info-cover-img")?.first() - ?.attr("src") - - document.select("span.detail-info-right-title-tip")?.first()?.text()?.also { statusText -> - when { - statusText.contains("ongoing", true) -> manga.status = SManga.ONGOING - statusText.contains("completed", true) -> manga.status = SManga.COMPLETED - else -> manga.status = SManga.UNKNOWN - } - } - - // Get a chapter, check if the manga is licensed. - val aChapterURL = chapterFromElement(document.select(chapterListSelector()).first()).url - val aChapterDocument = client.newCall(GET("$baseUrl$aChapterURL", headers)).execute().asJsoup() - if (aChapterDocument.select("p.detail-block-content").hasText()) manga.status = SManga.LICENSED - - return manga - } - - override fun chapterListSelector() = "ul.detail-main-list > li" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.select("a").first().attr("href")) - chapter.name = element.select("a p.title3").first().text() - chapter.date_upload = element.select("a p.title2").first()?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - return if ("Today" in date || " ago" in date) { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } else if ("Yesterday" in date) { - Calendar.getInstance().apply { - add(Calendar.DATE, -1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } else { - try { - SimpleDateFormat("MMM dd,yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - } - - override fun pageListParse(document: Document): List<Page> { - val bar = document.select("script[src*=chapter_bar]") - val duktape = Duktape.create() - - /* - function to drop last imageUrl if it's broken/unneccesary, working imageUrls are incremental (e.g. t001, t002, etc); if the difference between - the last two isn't 1 or doesn't have an Int at the end of the last imageUrl's filename, drop last Page - */ - fun List<Page>.dropLastIfBroken(): List<Page> { - val list = this.takeLast(2).map { page -> - try { - page.imageUrl!!.substringBeforeLast(".").substringAfterLast("/").takeLast(2).toInt() - } catch (_: NumberFormatException) { - return this.dropLast(1) - } - } - return when { - list[0] == 0 && 100 - list[1] == 1 -> this - list[1] - list[0] == 1 -> this - else -> this.dropLast(1) - } - } - - // if-branch is for webtoon reader, else is for page-by-page - return if (bar.isNotEmpty()) { - val script = document.select("script:containsData(function(p,a,c,k,e,d))").html().removePrefix("eval") - val deobfuscatedScript = duktape.evaluate(script).toString() - val urls = deobfuscatedScript.substringAfter("newImgs=['").substringBefore("'];").split("','") - duktape.close() - - urls.mapIndexed { index, s -> Page(index, "", "https:$s") } - } else { - val html = document.html() - val link = document.location() - - var secretKey = extractSecretKey(html, duktape) - - val chapterIdStartLoc = html.indexOf("chapterid") - val chapterId = html.substring( - chapterIdStartLoc + 11, - html.indexOf(";", chapterIdStartLoc) - ).trim() - - val chapterPagesElement = document.select(".pager-list-left > span").first() - val pagesLinksElements = chapterPagesElement.select("a") - val pagesNumber = pagesLinksElements[pagesLinksElements.size - 2].attr("data-page").toInt() - - val pageBase = link.substring(0, link.lastIndexOf("/")) - - IntRange(1, pagesNumber).map { i -> - - val pageLink = "$pageBase/chapterfun.ashx?cid=$chapterId&page=$i&key=$secretKey" - - var responseText = "" - - for (tr in 1..3) { - - val request = Request.Builder() - .url(pageLink) - .addHeader("Referer", link) - .addHeader("Accept", "*/*") - .addHeader("Accept-Language", "en-US,en;q=0.9") - .addHeader("Connection", "keep-alive") - .addHeader("Host", "www.mangahere.cc") - .addHeader("User-Agent", System.getProperty("http.agent") ?: "") - .addHeader("X-Requested-With", "XMLHttpRequest") - .build() - - val response = client.newCall(request).execute() - responseText = response.body()!!.string() - - if (responseText.isNotEmpty()) - break - else - secretKey = "" - } - - val deobfuscatedScript = duktape.evaluate(responseText.removePrefix("eval")).toString() - - val baseLinkStartPos = deobfuscatedScript.indexOf("pix=") + 5 - val baseLinkEndPos = deobfuscatedScript.indexOf(";", baseLinkStartPos) - 1 - val baseLink = deobfuscatedScript.substring(baseLinkStartPos, baseLinkEndPos) - - val imageLinkStartPos = deobfuscatedScript.indexOf("pvalue=") + 9 - val imageLinkEndPos = deobfuscatedScript.indexOf("\"", imageLinkStartPos) - val imageLink = deobfuscatedScript.substring(imageLinkStartPos, imageLinkEndPos) - - Page(i - 1, "", "https:$baseLink$imageLink") - } - } - .dropLastIfBroken() - .also { duktape.close() } - } - - private fun extractSecretKey(html: String, duktape: Duktape): String { - - val secretKeyScriptLocation = html.indexOf("eval(function(p,a,c,k,e,d)") - val secretKeyScriptEndLocation = html.indexOf("</script>", secretKeyScriptLocation) - val secretKeyScript = html.substring(secretKeyScriptLocation, secretKeyScriptEndLocation).removePrefix("eval") - - val secretKeyDeobfuscatedScript = duktape.evaluate(secretKeyScript).toString() - - val secretKeyStartLoc = secretKeyDeobfuscatedScript.indexOf("'") - val secretKeyEndLoc = secretKeyDeobfuscatedScript.indexOf(";") - - val secretKeyResultScript = secretKeyDeobfuscatedScript.substring( - secretKeyStartLoc, - secretKeyEndLoc - ) - - return duktape.evaluate(secretKeyResultScript).toString() - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - private class Genre(title: String, val id: Int) : Filter.TriState(title) - - private class TypeList(types: Array<String>) : Filter.Select<String>("Type", types, 0) - private class CompletionList(completions: Array<String>) : Filter.Select<String>("Completed series", completions, 0) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - override fun getFilterList() = FilterList( - TypeList(types.keys.toList().sorted().toTypedArray()), - CompletionList(completions), - GenreList(genres()) - ) - - private val types = hashMapOf( - "Japanese Manga" to 1, - "Korean Manhwa" to 2, - "Other Manga" to 4, - "Any" to 0 - ) - - private val completions = arrayOf("Either", "No", "Yes") - - private fun genres() = arrayListOf( - Genre("Action", 1), - Genre("Adventure", 2), - Genre("Comedy", 3), - Genre("Fantasy", 4), - Genre("Historical", 5), - Genre("Horror", 6), - Genre("Martial Arts", 7), - Genre("Mystery", 8), - Genre("Romance", 9), - Genre("Shounen Ai", 10), - Genre("Supernatural", 11), - Genre("Drama", 12), - Genre("Shounen", 13), - Genre("School Life", 14), - Genre("Shoujo", 15), - Genre("Gender Bender", 16), - Genre("Josei", 17), - Genre("Psychological", 18), - Genre("Seinen", 19), - Genre("Slice of Life", 20), - Genre("Sci-fi", 21), - Genre("Ecchi", 22), - Genre("Harem", 23), - Genre("Shoujo Ai", 24), - Genre("Yuri", 25), - Genre("Mature", 26), - Genre("Tragedy", 27), - Genre("Yaoi", 28), - Genre("Doujinshi", 29), - Genre("Sports", 30), - Genre("Adult", 31), - Genre("One Shot", 32), - Genre("Smut", 33), - Genre("Mecha", 34), - Genre("Shotacon", 35), - Genre("Lolicon", 36) - ) -} diff --git a/src/en/mangahub/AndroidManifest.xml b/src/en/mangahub/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangahub/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangahub/build.gradle b/src/en/mangahub/build.gradle deleted file mode 100644 index 1bed04b7e..000000000 --- a/src/en/mangahub/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangahub' - pkgNameSuffix = 'en.mangahub' - extClass = '.Mangahub' - extVersionCode = 7 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangahub/res/mipmap-hdpi/ic_launcher.png b/src/en/mangahub/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 354b23ee7..000000000 Binary files a/src/en/mangahub/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahub/res/mipmap-mdpi/ic_launcher.png b/src/en/mangahub/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d63b07893..000000000 Binary files a/src/en/mangahub/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahub/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangahub/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 31611e381..000000000 Binary files a/src/en/mangahub/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahub/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangahub/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1ebaf9c50..000000000 Binary files a/src/en/mangahub/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahub/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangahub/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index aa1b41604..000000000 Binary files a/src/en/mangahub/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangahub/res/web_hi_res_512.png b/src/en/mangahub/res/web_hi_res_512.png deleted file mode 100644 index 30eb3d47e..000000000 Binary files a/src/en/mangahub/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangahub/src/eu/kanade/tachiyomi/extension/en/mangahub/Mangahub.kt b/src/en/mangahub/src/eu/kanade/tachiyomi/extension/en/mangahub/Mangahub.kt deleted file mode 100644 index 8c2fe7591..000000000 --- a/src/en/mangahub/src/eu/kanade/tachiyomi/extension/en/mangahub/Mangahub.kt +++ /dev/null @@ -1,291 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangahub - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.keys -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.net.URL -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Mangahub : ParsedHttpSource() { - - override val name = "Mangahub" - - override val baseUrl = "https://www.mangahub.io" - - override val lang = "en" - - override val supportsLatest = true - - override fun popularMangaSelector() = "#mangalist div.media-manga.media" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/popular/page/$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/updates/page/$page", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - val titleElement = element.select(".media-heading > a").first() - manga.title = titleElement.text() - manga.setUrlWithoutDomain(URL(titleElement.attr("href")).path) - manga.thumbnail_url = element.select("img.manga-thumb.list-item-thumb") - ?.first()?.attr("src") - - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "ul.pager li.next > a" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.title = document.select("h1._3xnDj").first().ownText() - manga.author = document.select("._3QCtP > div:nth-child(2) > div:nth-child(1) > span:nth-child(2)")?.first()?.text() - manga.artist = document.select("._3QCtP > div:nth-child(2) > div:nth-child(2) > span:nth-child(2)")?.first()?.text() - manga.genre = document.select("._3Czbn a")?.joinToString { it.text() } - manga.description = document.select("div#noanim-content-tab-pane-99 p.ZyMp7")?.first()?.text() - manga.thumbnail_url = document.select("img.img-responsive")?.first() - ?.attr("src") - - document.select("._3QCtP > div:nth-child(2) > div:nth-child(3) > span:nth-child(2)")?.first()?.text()?.also { statusText -> - when { - statusText.contains("ongoing", true) -> manga.status = SManga.ONGOING - statusText.contains("completed", true) -> manga.status = SManga.COMPLETED - else -> manga.status = SManga.UNKNOWN - } - } - - return manga - } - - override fun chapterListSelector() = ".tab-content .tab-pane li.list-group-item > a" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - chapter.setUrlWithoutDomain(element.attr("href")) - chapter.name = element.select("span._8Qtbo").text() - chapter.date_upload = element.select("small.UovLc").first()?.text()?.let { parseChapterDate(it) } ?: 0 - - return chapter - } - - private fun parseChapterDate(date: String): Long { - val now = Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - } - var parsedDate = 0L - when { - "just now" in date || "less than an hour" in date -> { - parsedDate = now.timeInMillis - } - // parses: "1 hour ago" and "2 hours ago" - "hour" in date -> { - val hours = date.replaceAfter(" ", "").trim().toInt() - parsedDate = now.apply { add(Calendar.HOUR, -hours) }.timeInMillis - } - // parses: "Yesterday" and "2 days ago" - "day" in date -> { - val days = date.replace("days ago", "").trim().toIntOrNull() ?: 1 - parsedDate = now.apply { add(Calendar.DAY_OF_YEAR, -days) }.timeInMillis - } - // parses: "2 weeks ago" - "weeks" in date -> { - val weeks = date.replace("weeks ago", "").trim().toInt() - parsedDate = now.apply { add(Calendar.WEEK_OF_YEAR, -weeks) }.timeInMillis - } - // parses: "12-20-2019" and defaults everything that wasn't taken into account to 0 - else -> { - try { - parsedDate = SimpleDateFormat("MM-dd-yyyy", Locale.US).parse(date)?.time ?: 0L - } catch (e: ParseException) { /*nothing to do, parsedDate is initialized with 0L*/ } - } - } - return parsedDate - } - - override fun pageListRequest(chapter: SChapter): Request { - val jsonHeaders = headers.newBuilder().add("Content-Type", "application/json").build() - - val slug = chapter.url.substringAfter("chapter/").substringBefore("/") - val number = chapter.url.substringAfter("chapter-").removeSuffix("/") - val body = RequestBody.create(null, "{\"query\":\"{chapter(x:m01,slug:\\\"$slug\\\",number:$number){id,title,mangaID,number,slug,date,pages,noAd,manga{id,title,slug,mainSlug,author,isWebtoon,isYaoi,isPorn,isSoftPorn,unauthFile,isLicensed}}}\"}") - - return POST("https://api.mghubcdn.com/graphql", jsonHeaders, body) - } - - private val gson = Gson() - - override fun pageListParse(response: Response): List<Page> { - val cdn = "https://img.mghubcdn.com/file/imghub" - - return gson.fromJson<JsonObject>(response.body()!!.string())["data"]["chapter"]["pages"].string - .removeSurrounding("\"").replace("\\", "") - .let { cleaned -> - val jsonObject = gson.fromJson<JsonObject>(cleaned) - jsonObject.keys().map { key -> jsonObject[key].string } - } - .mapIndexed { i, tail -> Page(i, "", "$cdn/$tail") } - } - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // https://mangahub.io/search/page/1?q=a&order=POPULAR&genre=all - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search/page/$page")?.newBuilder()!!.addQueryParameter("q", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is OrderBy -> { - val order = filter.values[filter.state] - url.addQueryParameter("order", order.key) - } - is GenreList -> { - val genre = filter.values[filter.state] - url.addQueryParameter("genre", genre.key) - } - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "div#mangalist div.media-manga.media" - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - private class Genre(title: String, val key: String) : Filter.TriState(title) { - override fun toString(): String { - return name - } - } - - private class Order(title: String, val key: String) : Filter.TriState(title) { - override fun toString(): String { - return name - } - } - - private class OrderBy(orders: Array<Order>) : Filter.Select<Order>("Order", orders, 0) - private class GenreList(genres: Array<Genre>) : Filter.Select<Genre>("Genres", genres, 0) - - override fun getFilterList() = FilterList( - OrderBy(orderBy), - GenreList(genres) - ) - - private val orderBy = arrayOf( - Order("Popular", "POPULAR"), - Order("Updates", "LATEST"), - Order("A-Z", "ALPHABET"), - Order("New", "NEW"), - Order("Completed", "COMPLETED") - ) - - private val genres = arrayOf( - Genre("All Genres", "all"), - Genre("[no chapters]", "no-chapters"), - Genre("4-Koma", "4-koma"), - Genre("Action", "action"), - Genre("Adult", "adult"), - Genre("Adventure", "adventure"), - Genre("Award Winning", "award-winning"), - Genre("Comedy", "comedy"), - Genre("Cooking", "cooking"), - Genre("Crime", "crime"), - Genre("Demons", "demons"), - Genre("Doujinshi", "doujinshi"), - Genre("Drama", "drama"), - Genre("Ecchi", "ecchi"), - Genre("Fantasy", "fantasy"), - Genre("Food", "food"), - Genre("Game", "game"), - Genre("Gender bender", "gender-bender"), - Genre("Harem", "harem"), - Genre("Historical", "historical"), - Genre("Horror", "horror"), - Genre("Isekai", "isekai"), - Genre("Josei", "josei"), - Genre("Kids", "kids"), - Genre("Magic", "magic"), - Genre("Magical Girls", "magical-girls"), - Genre("Manhua", "manhua"), - Genre("Manhwa", "manhwa"), - Genre("Martial arts", "martial-arts"), - Genre("Mature", "mature"), - Genre("Mecha", "mecha"), - Genre("Medical", "medical"), - Genre("Military", "military"), - Genre("Music", "music"), - Genre("Mystery", "mystery"), - Genre("One shot", "one-shot"), - Genre("Oneshot", "oneshot"), - Genre("Parody", "parody"), - Genre("Police", "police"), - Genre("Psychological", "psychological"), - Genre("Romance", "romance"), - Genre("School life", "school-life"), - Genre("Sci fi", "sci-fi"), - Genre("Seinen", "seinen"), - Genre("Shotacon", "shotacon"), - Genre("Shoujo", "shoujo"), - Genre("Shoujo ai", "shoujo-ai"), - Genre("Shoujoai", "shoujoai"), - Genre("Shounen", "shounen"), - Genre("Shounen ai", "shounen-ai"), - Genre("Shounenai", "shounenai"), - Genre("Slice of life", "slice-of-life"), - Genre("Smut", "smut"), - Genre("Space", "space"), - Genre("Sports", "sports"), - Genre("Super Power", "super-power"), - Genre("Superhero", "superhero"), - Genre("Supernatural", "supernatural"), - Genre("Thriller", "thriller"), - Genre("Tragedy", "tragedy"), - Genre("Vampire", "vampire"), - Genre("Webtoon", "webtoon"), - Genre("Webtoons", "webtoons"), - Genre("Wuxia", "wuxia"), - Genre("Yaoi", "yaoi"), - Genre("Yuri", "yuri") - ) -} diff --git a/src/en/mangajar/AndroidManifest.xml b/src/en/mangajar/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangajar/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangajar/build.gradle b/src/en/mangajar/build.gradle deleted file mode 100644 index da59b954c..000000000 --- a/src/en/mangajar/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaJar' - pkgNameSuffix = 'en.mangajar' - extClass = '.MangaJar' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangajar/res/mipmap-hdpi/ic_launcher.png b/src/en/mangajar/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e4347dfd3..000000000 Binary files a/src/en/mangajar/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangajar/res/mipmap-mdpi/ic_launcher.png b/src/en/mangajar/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d3b152730..000000000 Binary files a/src/en/mangajar/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangajar/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangajar/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index de3f3ef87..000000000 Binary files a/src/en/mangajar/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangajar/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangajar/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 23785eb1b..000000000 Binary files a/src/en/mangajar/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangajar/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangajar/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 365349fd7..000000000 Binary files a/src/en/mangajar/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangajar/res/web_hi_res_512.png b/src/en/mangajar/res/web_hi_res_512.png deleted file mode 100644 index a75666bf7..000000000 Binary files a/src/en/mangajar/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangajar/src/eu/kanade/tachiyomi/extension/en/mangajar/MangaJar.kt b/src/en/mangajar/src/eu/kanade/tachiyomi/extension/en/mangajar/MangaJar.kt deleted file mode 100644 index b0a8ccf90..000000000 --- a/src/en/mangajar/src/eu/kanade/tachiyomi/extension/en/mangajar/MangaJar.kt +++ /dev/null @@ -1,237 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangajar - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class MangaJar : ParsedHttpSource() { - - override val name = "MangaJar" - - override val baseUrl = "https://mangajar.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaSelector() = "article[class*=flex-item]" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/manga?sortBy=popular&page=$page") - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/manga?sortBy=last_chapter_at&page=$page") - } - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - title = element.select("img").attr("title") - thumbnail_url = element.select("img").let { - if (it.hasAttr("data-src")) - it.attr("data-src") else it.attr("src") - } - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "[rel=next]" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filters = if (filters.isEmpty()) getFilterList() else filters - val genreFilter = filters.findInstance<GenreList>() - val genre = genreFilter?.let { f -> f.values[f.state] } - - val url = HttpUrl.parse(if (genre!!.isEmpty()) "$baseUrl/search" else "$baseUrl/genre/$genre")!!.newBuilder() - - url.addQueryParameter("q", query) - url.addQueryParameter("page", page.toString()) - - ( - filters.forEach { filter -> - when (filter) { - is OrderBy -> { - url.addQueryParameter("sortBy", filter.toUriPart()) - } - is SortBy -> { - url.addQueryParameter("sortAscending", filter.toUriPart()) - } - } - } - ) - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - description = document.select("div.manga-description.entry").text() - thumbnail_url = document.select("div.row > div > img").attr("src") - genre = document.select("div.post-info > span > a[href*=genre]").joinToString { it.text() } - status = parseStatus(document.select("span:has(b)")[1].text()) - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/chaptersList") - - override fun chapterListSelector() = "li.list-group-item.chapter-item" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("span.chapter-title").text().trim() - date_upload = parseChapterDate(element.select("span.chapter-date").text().trim()) ?: 0 - } - - // The following date related code is taken directly from Genkan.kt - companion object { - val dateFormat by lazy { - SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH) - } - } - - private fun parseChapterDate(string: String): Long? { - return if ("ago" in string) { - parseRelativeDate(string) ?: 0 - } else { - dateFormat.parse(string)?.time ?: 0L - } - } - - private fun parseRelativeDate(date: String): Long? { - val trimmedDate = date.substringBefore(" ago").removeSuffix("s").split(" ") - - val calendar = Calendar.getInstance() - when (trimmedDate[1]) { - "month" -> calendar.apply { add(Calendar.MONTH, -trimmedDate[0].toInt()) } - "week" -> calendar.apply { add(Calendar.WEEK_OF_MONTH, -trimmedDate[0].toInt()) } - "day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) } - "hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) } - "minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) } - "second" -> calendar.apply { add(Calendar.SECOND, 0) } - } - - return calendar.timeInMillis - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("img[data-page]").mapIndexed { i, element -> - Page(i, "", element.attr("data-alternative")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun getFilterList() = FilterList( - OrderBy(), - SortBy(), - GenreList() - ) - - private class SortBy : UriPartFilter( - "Sort By", - arrayOf( - Pair("Descending", "0"), - Pair("Ascending", "1") - ) - ) - - private class OrderBy : UriPartFilter( - "Order By", - arrayOf( - Pair("Popularity", "popular"), - Pair("Year", "year"), - Pair("Alphabet", "name"), - Pair("Date added", "published_at"), - Pair("Date updated", "last_chapter_at") - ) - ) - - private class GenreList : Filter.Select<String>( - "Select Genre", - arrayOf( - "", - "Fantasy", - "Adventure", - "Martial Arts", - "Action", - "Demons", - "Shounen", - "Drama", - "Isekai", - "School Life", - "Harem", - "Horror", - "Supernatural", - "Mystery", - "Sci-Fi", - "Webtoons", - "Romance", - "Magic", - "Slice of Life", - "Seinen", - "Historical", - "Ecchi", - "Comedy", - "Sports", - "Tragedy", - "Shounen Ai", - "Yaoi", - "Shoujo", - "Super Power", - "Food", - "Psychological", - "Gender Bender", - "Smut", - "Shoujo Ai", - "Yuri", - "4-koma", - "Mecha", - "Adult", - "Mature", - "Military", - "Vampire", - "Kids", - "Space", - "Police", - "Music", - "One Shot", - "Parody", - "Josei" - ) - ) - - private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/en/mangakatana/AndroidManifest.xml b/src/en/mangakatana/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangakatana/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangakatana/build.gradle b/src/en/mangakatana/build.gradle deleted file mode 100644 index 1bb1f3d87..000000000 --- a/src/en/mangakatana/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaKatana' - pkgNameSuffix = 'en.mangakatana' - extClass = '.MangaKatana' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangakatana/res/mipmap-hdpi/ic_launcher.png b/src/en/mangakatana/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 05d520ce4..000000000 Binary files a/src/en/mangakatana/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangakatana/res/mipmap-mdpi/ic_launcher.png b/src/en/mangakatana/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ab63541ab..000000000 Binary files a/src/en/mangakatana/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangakatana/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangakatana/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 95675163c..000000000 Binary files a/src/en/mangakatana/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangakatana/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangakatana/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e19b7a256..000000000 Binary files a/src/en/mangakatana/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangakatana/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangakatana/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ca314a89e..000000000 Binary files a/src/en/mangakatana/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangakatana/res/web_hi_res_512.png b/src/en/mangakatana/res/web_hi_res_512.png deleted file mode 100644 index ba5383b90..000000000 Binary files a/src/en/mangakatana/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangakatana/src/eu/kanade/tachiyomi/extension/en/mangakatana/MangaKatana.kt b/src/en/mangakatana/src/eu/kanade/tachiyomi/extension/en/mangakatana/MangaKatana.kt deleted file mode 100644 index 32e30815e..000000000 --- a/src/en/mangakatana/src/eu/kanade/tachiyomi/extension/en/mangakatana/MangaKatana.kt +++ /dev/null @@ -1,128 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangakatana - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Response -import okhttp3.ResponseBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaKatana : ParsedHttpSource() { - override val name = "MangaKatana" - - override val baseUrl = "https://mangakatana.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder().addNetworkInterceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - if (originalResponse.headers("Content-Type").contains("application/octet-stream")) { - val orgBody = originalResponse.body()!!.bytes() - val extension = chain.request().url().toString().substringAfterLast(".") - val newBody = ResponseBody.create(MediaType.parse("image/$extension"), orgBody) - originalResponse.newBuilder() - .body(newBody) - .build() - } else { - originalResponse - } - }.build() - - override fun latestUpdatesSelector() = "div#book_list > div.item" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/$page", headers) - - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("div.text > h3 > a").attr("href")) - title = element.select("div.text > h3 > a").text() - thumbnail_url = element.select("img").attr("abs:src") - } - - override fun latestUpdatesNextPageSelector() = ".next.page-numbers" - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/page/$page", headers) - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun searchMangaSelector() = latestUpdatesSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/page/$page?search=$query&search_by=book_name", headers) - - override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun searchMangaParse(response: Response): MangasPage { - return if (response.request().url().toString().contains("/manga/")) { - val document = response.asJsoup() - val manga = SManga.create().apply { - thumbnail_url = parseThumbnail(document) - title = document.select("h1.heading").first().text() - } - manga.setUrlWithoutDomain(response.request().url().toString()) - MangasPage(listOf(manga), false) - } else { - super.searchMangaParse(response) - } - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - author = document.select(".author").text() - description = document.select(".summary > p").text() - status = parseStatus(document.select(".value.status").text()) - genre = document.select(".genres > a").joinToString { it.text() } - thumbnail_url = parseThumbnail(document) - } - - private fun parseThumbnail(document: Document) = document.select("div.media div.cover img").attr("abs:src") - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "tr:has(.chapter)" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("a").text() - date_upload = dateFormat.parse(element.select(".update_time").text())?.time ?: 0 - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("MMM-dd-yyyy", Locale.US) - } - } - - private val imageArrayRegex = Regex("""var ytaw=\[([^\[]*)]""") - private val imageUrlRegex = Regex("""'([^']*)'""") - - override fun pageListParse(document: Document): List<Page> { - val imageArray = document.select("script:containsData(var ytaw)").firstOrNull()?.data() - ?.let { imageArrayRegex.find(it)?.groupValues?.get(1) } - ?: throw Exception("Image array not found") - return imageUrlRegex.findAll(imageArray).asIterable().mapIndexed { i, mr -> - Page(i, "", mr.groupValues[1]) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") -} diff --git a/src/en/mangalinkz/AndroidManifest.xml b/src/en/mangalinkz/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangalinkz/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangalinkz/build.gradle b/src/en/mangalinkz/build.gradle deleted file mode 100644 index d359f39e4..000000000 --- a/src/en/mangalinkz/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manga Linkz' - pkgNameSuffix = 'en.mangalinkz' - extClass = '.MangaLinkz' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangalinkz/res/mipmap-hdpi/ic_launcher.png b/src/en/mangalinkz/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 33b421908..000000000 Binary files a/src/en/mangalinkz/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangalinkz/res/mipmap-mdpi/ic_launcher.png b/src/en/mangalinkz/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3760f478f..000000000 Binary files a/src/en/mangalinkz/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangalinkz/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangalinkz/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ac40cf3ab..000000000 Binary files a/src/en/mangalinkz/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangalinkz/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangalinkz/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0cab8c2c7..000000000 Binary files a/src/en/mangalinkz/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangalinkz/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangalinkz/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6348ee058..000000000 Binary files a/src/en/mangalinkz/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangalinkz/res/web_hi_res_512.png b/src/en/mangalinkz/res/web_hi_res_512.png deleted file mode 100644 index 537f0b1c5..000000000 Binary files a/src/en/mangalinkz/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangalinkz/src/eu/kanade/tachiyomi/extension/en/mangalinkz/MangaLinkz.kt b/src/en/mangalinkz/src/eu/kanade/tachiyomi/extension/en/mangalinkz/MangaLinkz.kt deleted file mode 100644 index 205f36718..000000000 --- a/src/en/mangalinkz/src/eu/kanade/tachiyomi/extension/en/mangalinkz/MangaLinkz.kt +++ /dev/null @@ -1,153 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangalinkz - -import android.util.Base64 -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class MangaLinkz : ParsedHttpSource() { - - override val name = "Manga Linkz" - - override val baseUrl = "https://mangalinkz.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/popular", headers) - } - - override fun popularMangaSelector() = "div#content div.card" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.mb-1 a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return (GET("$baseUrl/latest", headers)) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return document.select("div#series div.container").let { info -> - SManga.create().apply { - title = info.select("h1").text() - author = info.select("p:containsOwn(Authors:) a").text() - status = info.select("p:containsOwn(Status:)").text().substringAfter(": ").toStatus() - genre = info.select("p:containsOwn(Genre) a").joinToString { it.text() } - description = info.select("p").last().text() - thumbnail_url = info.select("img").attr("abs:src") - } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("Ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("Completed", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "div.px-sm-0 > table tbody tr" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - date_upload = element.select("td.text-nowrap").text().toChapterDate() - } - } - - val calendar by lazy { Calendar.getInstance() } - - private fun String?.toChapterDate(): Long { - return when { - this == null -> 0L - this.contains("just now", ignoreCase = true) -> calendar.timeInMillis - this.endsWith("ago", ignoreCase = true) -> { - this.split(" ").let { - when (it[1].removeSuffix("s")) { - "hr" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -it[0].toInt()) }.timeInMillis - "day" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -it[0].toInt()) }.timeInMillis - else -> 0L - } - } - } - else -> - try { - SimpleDateFormat("MMM d, yyyy", Locale.US).parse(this)?.time ?: 0L - } catch (_: Exception) { - 0L - } - } - } - - // Pages - - private val gson by lazy { Gson() } - - override fun pageListParse(document: Document): List<Page> { - val encoded = document.select("script:containsData(atob)").first().data() - .substringAfter("atob(\"").substringBefore("\"") - val decoded = Base64.decode(encoded, Base64.DEFAULT).toString(Charsets.UTF_8).removeSurrounding("[", "]") - - return gson.fromJson<JsonObject>(decoded)["pages"].asJsonArray.mapIndexed { i, jsonElement -> Page(i, "", jsonElement.string) } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/mangamutiny/AndroidManifest.xml b/src/en/mangamutiny/AndroidManifest.xml deleted file mode 100644 index dd8605dc3..000000000 --- a/src/en/mangamutiny/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - <application> - <activity - android:name=".en.mangamutiny.MangaMutinyUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="mangamutiny.org" - android:pathPattern="/title/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> - -</manifest> diff --git a/src/en/mangamutiny/build.gradle b/src/en/mangamutiny/build.gradle deleted file mode 100644 index 94f3225f3..000000000 --- a/src/en/mangamutiny/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manga Mutiny' - pkgNameSuffix = "en.mangamutiny" - extClass = '.MangaMutiny' - extVersionCode = 7 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangamutiny/res/mipmap-hdpi/ic_launcher.png b/src/en/mangamutiny/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 994f93478..000000000 Binary files a/src/en/mangamutiny/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangamutiny/res/mipmap-mdpi/ic_launcher.png b/src/en/mangamutiny/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 257699262..000000000 Binary files a/src/en/mangamutiny/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangamutiny/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangamutiny/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d17be79a4..000000000 Binary files a/src/en/mangamutiny/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangamutiny/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangamutiny/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9e7f7e2e8..000000000 Binary files a/src/en/mangamutiny/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangamutiny/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangamutiny/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index edf0fe931..000000000 Binary files a/src/en/mangamutiny/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangamutiny/res/web_hi_res_512.png b/src/en/mangamutiny/res/web_hi_res_512.png deleted file mode 100644 index c160cb691..000000000 Binary files a/src/en/mangamutiny/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangamutiny/src/eu/kanade/tachiyomi/extension/en/mangamutiny/MangaMutiny.kt b/src/en/mangamutiny/src/eu/kanade/tachiyomi/extension/en/mangamutiny/MangaMutiny.kt deleted file mode 100644 index 6d7f57e5c..000000000 --- a/src/en/mangamutiny/src/eu/kanade/tachiyomi/extension/en/mangamutiny/MangaMutiny.kt +++ /dev/null @@ -1,574 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangamutiny - -import android.net.Uri -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import java.lang.Exception -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone -import kotlin.collections.ArrayList - -fun JsonObject.getNullable(key: String): JsonElement? { - val value: JsonElement = this.get(key) ?: return null - - if (value.isJsonNull) { - return null - } - - return value -} - -fun Float.toStringWithoutDotZero(): String = when (this % 1) { - 0F -> this.toInt().toString() - else -> this.toString() -} - -class MangaMutiny : HttpSource() { - - override val name = "Manga Mutiny" - override val baseUrl = "https://mangamutiny.org" - - override val supportsLatest = true - - override val lang = "en" - - private val parser = JsonParser() - - private val baseUrlAPI = "https://api.mangamutiny.org" - - private val webViewSingleMangaPath = "title/" - private val webViewMultipleMangaPath = "titles/" - - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().apply { - add("Accept", "application/json") - add("Origin", "https://mangamutiny.org") - } - } - - private val apiMangaUrlPath = "v1/public/manga" - private val apiChapterUrlPath = "v1/public/chapter" - - private val fetchAmount = 21 - - companion object { - const val PREFIX_ID_SEARCH = "slug:" - } - - // Popular manga - override fun popularMangaRequest(page: Int): Request = mangaRequest(page) - - override fun popularMangaParse(response: Response): MangasPage = mangaParse(response) - - // Chapters - override fun chapterListRequest(manga: SManga): Request = - mangaDetailsRequestCommon(manga, false) - - override fun chapterListParse(response: Response): List<SChapter> { - val chapterList = mutableListOf<SChapter>() - val responseBody = response.body() - - if (responseBody != null) { - val jsonChapters = JsonParser().parse(responseBody.charStream()).asJsonObject - .get("chapters").asJsonArray - for (singleChapterJsonElement in jsonChapters) { - val singleChapterJsonObject = singleChapterJsonElement.asJsonObject - - chapterList.add( - SChapter.create().apply { - name = chapterTitleBuilder(singleChapterJsonObject) - url = singleChapterJsonObject.get("slug").asString - date_upload = parseDate(singleChapterJsonObject.get("releasedAt").asString) - - chapterNumberBuilder(singleChapterJsonObject)?.let { chapterNumber -> - chapter_number = chapterNumber - } - } - ) - } - - responseBody.close() - } - - return chapterList - } - - private fun chapterNumberBuilder(rootNode: JsonObject): Float? = - rootNode.getNullable("chapter")?.asFloat - - private fun chapterTitleBuilder(rootNode: JsonObject): String { - val volume = rootNode.getNullable("volume")?.asInt - - val chapter = rootNode.getNullable("chapter")?.asFloat?.toStringWithoutDotZero() - - val textTitle = rootNode.getNullable("title")?.asString - - val chapterTitle = StringBuilder() - if (volume != null) chapterTitle.append("Vol. $volume") - if (chapter != null) { - if (volume != null) chapterTitle.append(" ") - chapterTitle.append("Chapter $chapter") - } - if (textTitle != null && textTitle != "") { - if (volume != null || chapter != null) chapterTitle.append(": ") - chapterTitle.append(textTitle) - } - - return chapterTitle.toString() - } - - private val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH) - .apply { timeZone = TimeZone.getTimeZone("UTC") } - - private fun parseDate(dateAsString: String): Long = - dateFormatter.parse(dateAsString)?.time ?: 0 - - // latest - override fun latestUpdatesRequest(page: Int): Request = - mangaRequest(page, filters = FilterList(SortFilter().apply { this.state = 1 })) - - override fun latestUpdatesParse(response: Response): MangasPage = mangaParse(response) - - // browse + latest + search - override fun mangaDetailsRequest(manga: SManga): Request = mangaDetailsRequestCommon(manga) - - private fun mangaDetailsRequestCommon(manga: SManga, lite: Boolean = true): Request { - val uri = if (isForWebView()) { - Uri.parse(baseUrl).buildUpon() - .appendEncodedPath(webViewSingleMangaPath) - .appendPath(manga.url) - } else { - Uri.parse(baseUrlAPI).buildUpon() - .appendEncodedPath(apiMangaUrlPath) - .appendPath(manga.url).let { - if (lite) it.appendQueryParameter("lite", "1") else it - } - } - - return GET(uri.build().toString(), headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val manga = SManga.create() - val responseBody = response.body() - - if (responseBody != null) { - val rootNode = parser.parse(responseBody.charStream()).asJsonObject - manga.apply { - status = when (rootNode.get("status").asString) { - "ongoing" -> SManga.ONGOING - "completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - description = rootNode.getNullable("summary")?.asString - thumbnail_url = rootNode.getNullable("thumbnail")?.asString - title = rootNode.get("title").asString - url = rootNode.get("slug").asString - artist = rootNode.getNullable("artists")?.asString - author = rootNode.get("authors").asString - - genre = rootNode.get("tags").asJsonArray - .joinToString { singleGenre -> singleGenre.asString } - } - - responseBody.close() - } - - return manga - } - - override fun pageListRequest(chapter: SChapter): Request { - val uri = Uri.parse(baseUrlAPI).buildUpon() - .appendEncodedPath(apiChapterUrlPath) - .appendEncodedPath(chapter.url) - - return GET(uri.build().toString(), headers) - } - - override fun pageListParse(response: Response): List<Page> { - val pageList = ArrayList<Page>() - - val responseBody = response.body() - - if (responseBody != null) { - val rootNode = parser.parse(responseBody.charStream()).asJsonObject - - // Build chapter url for every image of this chapter - val storageLocation = rootNode.get("storage").asString - val manga = rootNode.get("manga").asString - val chapterId = rootNode.get("id").asString - - val chapterUrl = "$storageLocation/$manga/$chapterId/" - - // Process every image of this chapter - val images = rootNode.get("images").asJsonArray - - for (i in 0 until images.size()) { - pageList.add(Page(i, "", chapterUrl + images[i].asString)) - } - - responseBody.close() - } - - return pageList - } - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - mangaRequest(page, query, filters) - - override fun searchMangaParse(response: Response): MangasPage = mangaParse(response) - - // commonly used functions - private fun mangaParse(response: Response): MangasPage { - val mangasPage = ArrayList<SManga>() - val responseBody = response.body() - - var totalObjects = 0 - - if (responseBody != null) { - val rootNode = parser.parse(responseBody.charStream()) - - if (rootNode.isJsonObject) { - val rootObject = rootNode.asJsonObject - val itemsArray = rootObject.get("items").asJsonArray - - for (singleItem in itemsArray) { - val mangaObject = singleItem.asJsonObject - mangasPage.add( - SManga.create().apply { - this.title = mangaObject.get("title").asString - this.thumbnail_url = mangaObject.getNullable("thumbnail")?.asString - this.url = mangaObject.get("slug").asString - } - ) - } - - // total number of manga the server found in its database - // and is returning paginated page by page: - totalObjects = rootObject.getNullable("total")?.asInt ?: 0 - } - - responseBody.close() - } - - val skipped = response.request().url().queryParameter("skip")?.toInt() ?: 0 - - val moreElementsToSkip = skipped + fetchAmount < totalObjects - - val pageSizeEqualsFetchAmount = mangasPage.size == fetchAmount - - val hasMorePages = pageSizeEqualsFetchAmount && moreElementsToSkip - - return MangasPage(mangasPage, hasMorePages) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - - val tempManga = SManga.create().apply { - url = realQuery - } - - client.newCall(mangaDetailsRequestCommon(tempManga, true)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - MangasPage(listOf(details), false) - } - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - private fun mangaRequest(page: Int, query: String? = null, filters: FilterList? = null): Request { - val forWebView = isForWebView() - - val uri = if (forWebView) { - Uri.parse(baseUrl).buildUpon().apply { - appendEncodedPath(webViewMultipleMangaPath) - } - } else { - Uri.parse(baseUrlAPI).buildUpon().apply { - appendEncodedPath(apiMangaUrlPath) - } - } - - if (query?.isNotBlank() == true) { - uri.appendQueryParameter("text", query) - } - - val applicableFilters = if (filters != null && filters.isNotEmpty()) { - filters - } else { - FilterList(SortFilter()) - } - - val uriParameterMap = mutableMapOf<String, String>() - - for (singleFilter in applicableFilters) { - if (singleFilter is UriFilter) { - singleFilter.addParameter(uriParameterMap) - } - } - - for (uriParameter in uriParameterMap) { - uri.appendQueryParameter(uriParameter.key, uriParameter.value) - } - - if (!forWebView) { - uri.appendQueryParameter("limit", fetchAmount.toString()) - if (page != 1) { - uri.appendQueryParameter("skip", ((page - 1) * fetchAmount).toString()) - } - } - - return GET(uri.build().toString(), headers) - } - - // Filter - override fun getFilterList(): FilterList { - return FilterList( - StatusFilter(), - CategoryFilter(), - GenresFilter(), - FormatsFilter(), - SortFilter(), - AuthorFilter() - // ScanlatorFilter() - ) - } - - override fun imageUrlParse(response: Response): String { - throw Exception("Not used") - } - - private interface UriFilter { - val uriParam: () -> String - val shouldAdd: () -> Boolean - val getParameter: () -> String - - fun addParameter(parameterMap: MutableMap<String, String>) { - if (shouldAdd()) { - val newParameterValueBuilder = StringBuilder() - if (parameterMap[uriParam()] != null) { - newParameterValueBuilder.append(parameterMap[uriParam()] + " ") - } - newParameterValueBuilder.append(getParameter()) - - parameterMap[uriParam()] = newParameterValueBuilder.toString() - } - } - } - - private abstract class UriSelectFilter( - displayName: String, - override val uriParam: () -> String, - val vals: Array<Pair<String, String>>, - val defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { - - override val shouldAdd = fun() = - this.state != defaultValue - - override val getParameter = fun() = vals[state].first - } - - private class StatusFilter : UriSelectFilter( - "Status", - fun() = "status", - arrayOf( - Pair("", "All"), - Pair("completed", "Completed"), - Pair("ongoing", "Ongoing") - ) - ) - - private class CategoryFilter : UriSelectFilter( - "Category", - fun() = "tags", - arrayOf( - Pair("", "All"), - Pair("josei", "Josei"), - Pair("seinen", "Seinen"), - Pair("shoujo", "Shoujo"), - Pair("shounen", "Shounen") - ) - ) - - // A single filter: either a genre or a format filter - private class GenreOrFormatFilter(val uriParam: String, displayName: String) : - Filter.CheckBox(displayName) - - // A collection of genre or format filters - private abstract class GenreOrFormatFilterList(name: String, specificUriParam: String, elementList: List<GenreOrFormatFilter>) : Filter.Group<GenreOrFormatFilter>(name, elementList), UriFilter { - - override val shouldAdd = fun() = state.any { it.state } - - override val getParameter = fun() = - state.filter { it.state }.joinToString(" ") { it.uriParam } - - override val uriParam = fun() = if (isForWebView()) specificUriParam else "tags" - } - - // Generes filter list - private class GenresFilter : GenreOrFormatFilterList( - "Genres", - "genres", - listOf( - GenreOrFormatFilter("action", "action"), - GenreOrFormatFilter("adult", "adult"), - GenreOrFormatFilter("adventure", "adventure"), - GenreOrFormatFilter("aliens", "aliens"), - GenreOrFormatFilter("animals", "animals"), - GenreOrFormatFilter("comedy", "comedy"), - GenreOrFormatFilter("cooking", "cooking"), - GenreOrFormatFilter("crossdressing", "crossdressing"), - GenreOrFormatFilter("delinquents", "delinquents"), - GenreOrFormatFilter("demons", "demons"), - GenreOrFormatFilter("drama", "drama"), - GenreOrFormatFilter("ecchi", "ecchi"), - GenreOrFormatFilter("fantasy", "fantasy"), - GenreOrFormatFilter("gender_bender", "gender bender"), - GenreOrFormatFilter("genderswap", "genderswap"), - GenreOrFormatFilter("ghosts", "ghosts"), - GenreOrFormatFilter("gore", "gore"), - GenreOrFormatFilter("gyaru", "gyaru"), - GenreOrFormatFilter("harem", "harem"), - GenreOrFormatFilter("historical", "historical"), - GenreOrFormatFilter("horror", "horror"), - GenreOrFormatFilter("incest", "incest"), - GenreOrFormatFilter("isekai", "isekai"), - GenreOrFormatFilter("loli", "loli"), - GenreOrFormatFilter("magic", "magic"), - GenreOrFormatFilter("magical_girls", "magical girls"), - GenreOrFormatFilter("mangamutiny", "mangamutiny"), - GenreOrFormatFilter("martial_arts", "martial arts"), - GenreOrFormatFilter("mature", "mature"), - GenreOrFormatFilter("mecha", "mecha"), - GenreOrFormatFilter("medical", "medical"), - GenreOrFormatFilter("military", "military"), - GenreOrFormatFilter("monster_girls", "monster girls"), - GenreOrFormatFilter("monsters", "monsters"), - GenreOrFormatFilter("mystery", "mystery"), - GenreOrFormatFilter("ninja", "ninja"), - GenreOrFormatFilter("office_workers", "office workers"), - GenreOrFormatFilter("philosophical", "philosophical"), - GenreOrFormatFilter("psychological", "psychological"), - GenreOrFormatFilter("reincarnation", "reincarnation"), - GenreOrFormatFilter("reverse_harem", "reverse harem"), - GenreOrFormatFilter("romance", "romance"), - GenreOrFormatFilter("school_life", "school life"), - GenreOrFormatFilter("sci_fi", "sci fi"), - GenreOrFormatFilter("sci-fi", "sci-fi"), - GenreOrFormatFilter("sexual_violence", "sexual violence"), - GenreOrFormatFilter("shota", "shota"), - GenreOrFormatFilter("shoujo_ai", "shoujo ai"), - GenreOrFormatFilter("shounen_ai", "shounen ai"), - GenreOrFormatFilter("slice_of_life", "slice of life"), - GenreOrFormatFilter("smut", "smut"), - GenreOrFormatFilter("sports", "sports"), - GenreOrFormatFilter("superhero", "superhero"), - GenreOrFormatFilter("supernatural", "supernatural"), - GenreOrFormatFilter("survival", "survival"), - GenreOrFormatFilter("time_travel", "time travel"), - GenreOrFormatFilter("tragedy", "tragedy"), - GenreOrFormatFilter("video_games", "video games"), - GenreOrFormatFilter("virtual_reality", "virtual reality"), - GenreOrFormatFilter("webtoons", "webtoons"), - GenreOrFormatFilter("wuxia", "wuxia"), - GenreOrFormatFilter("zombies", "zombies") - ) - ) - - // Actual format filter List - private class FormatsFilter : GenreOrFormatFilterList( - "Formats", - "formats", - listOf( - GenreOrFormatFilter("4-koma", "4-koma"), - GenreOrFormatFilter("adaptation", "adaptation"), - GenreOrFormatFilter("anthology", "anthology"), - GenreOrFormatFilter("award_winning", "award winning"), - GenreOrFormatFilter("doujinshi", "doujinshi"), - GenreOrFormatFilter("fan_colored", "fan colored"), - GenreOrFormatFilter("full_color", "full color"), - GenreOrFormatFilter("long_strip", "long strip"), - GenreOrFormatFilter("official_colored", "official colored"), - GenreOrFormatFilter("oneshot", "oneshot"), - GenreOrFormatFilter("web_comic", "web comic") - ), - - ) - - private class SortFilter : UriSelectFilter( - "Sort", - fun() = "sort", - arrayOf( - Pair("title", "Name"), - Pair("-lastReleasedAt", "Last update"), - Pair("-createdAt", "Newest"), - Pair("-rating -ratingCount", "Popular") - ), - defaultValue = 3 - ) { - override val shouldAdd = fun() = if (isForWebView()) state != defaultValue else true - - override val getParameter = fun(): String { - return if (isForWebView()) { - this.state.toString() - } else { - this.vals[this.state].first - } - } - } - - private class AuthorFilter : Filter.Text("Manga Author & Artist"), UriFilter { - override val uriParam = fun() = "creator" - - override val shouldAdd = fun() = state.isNotEmpty() - - override val getParameter = fun(): String = state - } - - /**The scanlator filter exists on the mangamutiny website, however it doesn't work. - This should stay disabled in the extension until it's properly implemented on the website, - otherwise users may be confused by searches that return no results.**/ - /* - private class ScanlatorFilter : Filter.Text("Scanlator Name"), UriFilter { - override val uriParam = fun() = "scanlator" - - override val shouldAdd = fun() = state.isNotEmpty() - - override val getParameter = fun(): String = state - } - */ -} - -private fun isForWebView(): Boolean = - Thread.currentThread().stackTrace.map { it.methodName } - .firstOrNull { - it.contains("WebView", true) && !it.contains("isForWebView") - } != null diff --git a/src/en/mangamutiny/src/eu/kanade/tachiyomi/extension/en/mangamutiny/MangaMutinyUrlActivity.kt b/src/en/mangamutiny/src/eu/kanade/tachiyomi/extension/en/mangamutiny/MangaMutinyUrlActivity.kt deleted file mode 100644 index cc358b29d..000000000 --- a/src/en/mangamutiny/src/eu/kanade/tachiyomi/extension/en/mangamutiny/MangaMutinyUrlActivity.kt +++ /dev/null @@ -1,41 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangamutiny - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://mangamutiny.org/title/xxx intents and redirects them to - * the main tachiyomi process. The idea is to not install the intent filter unless - * you have this extension installed, but still let the main tachiyomi app control - * things. - */ -class MangaMutinyUrlActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val slug = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${MangaMutiny.PREFIX_ID_SEARCH}$slug") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("MangaMutinyUrlActivity", e.toString()) - } - } else { - Log.e("MangaMutinyUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/en/mangaowl/AndroidManifest.xml b/src/en/mangaowl/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangaowl/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangaowl/build.gradle b/src/en/mangaowl/build.gradle deleted file mode 100644 index d6cca5acb..000000000 --- a/src/en/mangaowl/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaOwl' - pkgNameSuffix = 'en.mangaowl' - extClass = '.MangaOwl' - extVersionCode = 9 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangaowl/res/mipmap-hdpi/ic_launcher.png b/src/en/mangaowl/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2d63f0ab2..000000000 Binary files a/src/en/mangaowl/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaowl/res/mipmap-mdpi/ic_launcher.png b/src/en/mangaowl/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 13b650f39..000000000 Binary files a/src/en/mangaowl/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaowl/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangaowl/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e412e079f..000000000 Binary files a/src/en/mangaowl/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaowl/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangaowl/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 644ee754b..000000000 Binary files a/src/en/mangaowl/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaowl/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangaowl/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3ad68eaa0..000000000 Binary files a/src/en/mangaowl/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangaowl/res/web_hi_res_512.png b/src/en/mangaowl/res/web_hi_res_512.png deleted file mode 100644 index 1e8a6282b..000000000 Binary files a/src/en/mangaowl/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangaowl/src/eu/kanade/tachiyomi/extension/en/mangaowl/MangaOwl.kt b/src/en/mangaowl/src/eu/kanade/tachiyomi/extension/en/mangaowl/MangaOwl.kt deleted file mode 100644 index 076892290..000000000 --- a/src/en/mangaowl/src/eu/kanade/tachiyomi/extension/en/mangaowl/MangaOwl.kt +++ /dev/null @@ -1,145 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangaowl - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MangaOwl : ParsedHttpSource() { - - override val name = "MangaOwl" - - override val baseUrl = "https://mangaowls.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/popular/$page", headers) - } - - override fun popularMangaSelector() = "div.col-md-2" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h6 a").let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = element.select("div.img-responsive").attr("abs:data-background-image") - - return manga - } - - override fun popularMangaNextPageSelector() = "div.blog-pagenat-wthree li a:contains(>>)" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/lastest/$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/$page?search=$query&search_field=110&sort=4&completed=2", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = "div.navigation li a:contains(next)" - - // Manga summary page - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.single_detail").first() - - return SManga.create().apply { - title = infoElement.select("h2").first().ownText() - author = infoElement.select("p.fexi_header_para a.author_link").text() - artist = author - status = parseStatus(infoElement.select("p.fexi_header_para:contains(status)").first().ownText()) - genre = infoElement.select(".owl-tags a").joinToString { it.text() } - description = infoElement.select(".description").first().ownText() - thumbnail_url = infoElement.select("img").first()?.let { img -> - if (img.hasAttr("data-src")) img.attr("abs:data-src") else img.attr("abs:src") - } - } - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "div.table-chapter-list ul li" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - element.select("a").let { - // They replace some URLs with a different host getting a path of domain.com/reader/reader/, fix to make usable on baseUrl - chapter.setUrlWithoutDomain(it.attr("href").replace("/reader/reader/", "/reader/")) - chapter.name = it.select("label")[0].text() - } - chapter.date_upload = parseChapterDate(element.select("small:last-of-type").text()) - - return chapter - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("MM/dd/yyyy", Locale.US) - } - } - - private fun parseChapterDate(string: String): Long { - return try { - dateFormat.parse(string)?.time ?: 0 - } catch (_: ParseException) { - 0 - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.item img.owl-lazy").mapIndexed { i, img -> - Page(i, "", img.attr("abs:data-src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/mangapark/AndroidManifest.xml b/src/en/mangapark/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangapark/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangapark/build.gradle b/src/en/mangapark/build.gradle deleted file mode 100644 index 5025c3ce3..000000000 --- a/src/en/mangapark/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaPark' - pkgNameSuffix = 'en.mangapark' - extClass = '.MangaPark' - extVersionCode = 18 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangapark/res/mipmap-hdpi/ic_launcher.png b/src/en/mangapark/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 74307452b..000000000 Binary files a/src/en/mangapark/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapark/res/mipmap-mdpi/ic_launcher.png b/src/en/mangapark/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 317550192..000000000 Binary files a/src/en/mangapark/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapark/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangapark/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8e63f732a..000000000 Binary files a/src/en/mangapark/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapark/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangapark/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 934da61ca..000000000 Binary files a/src/en/mangapark/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapark/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangapark/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index dfbb1479a..000000000 Binary files a/src/en/mangapark/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapark/res/web_hi_res_512.png b/src/en/mangapark/res/web_hi_res_512.png deleted file mode 100644 index eb894066e..000000000 Binary files a/src/en/mangapark/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangapark/src/eu/kanade/tachiyomi/extension/en/mangapark/MangaPark.kt b/src/en/mangapark/src/eu/kanade/tachiyomi/extension/en/mangapark/MangaPark.kt deleted file mode 100644 index d11cd0df8..000000000 --- a/src/en/mangapark/src/eu/kanade/tachiyomi/extension/en/mangapark/MangaPark.kt +++ /dev/null @@ -1,626 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangapark - -import android.annotation.SuppressLint -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class MangaPark : ConfigurableSource, ParsedHttpSource() { - - override val lang = "en" - - override val client = network.cloudflareClient - - override val supportsLatest = true - override val name = "MangaPark" - override val baseUrl = "https://mangapark.net" - - private val nextPageSelector = ".paging:not(.order) > li:last-child > a" - - private val dateFormat = SimpleDateFormat("MMM d, yyyy, HH:mm a", Locale.ENGLISH) - private val dateFormatTimeOnly = SimpleDateFormat("HH:mm a", Locale.ENGLISH) - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/search?orderby=views_a&page=$page") - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - - override fun popularMangaNextPageSelector() = nextPageSelector - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/latest" + if (page > 1) "/$page" else "") - - override fun latestUpdatesSelector() = ".ls1 .item" - - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = nextPageSelector - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse("$baseUrl/search").buildUpon() - if (query.isNotEmpty()) { - uri.appendQueryParameter("q", query) - } - filters.forEach { - if (it is UriFilter) - it.addToUri(uri) - } - if (page != 1) { - uri.appendQueryParameter("page", page.toString()) - } - return GET(uri.toString()) - } - - override fun searchMangaSelector() = ".item" - - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - - override fun searchMangaNextPageSelector() = nextPageSelector - - private fun mangaFromElement(element: Element) = SManga.create().apply { - val coverElement = element.getElementsByClass("cover").first() - url = coverElement.attr("href") - title = coverElement.attr("title") - thumbnail_url = coverElement.select("img").attr("abs:src") - } - - @SuppressLint("DefaultLocale") - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - document.select(".cover > img").first().let { coverElement -> - title = coverElement.attr("title") - thumbnail_url = coverElement.attr("abs:src") - } - - document.select(".attr > tbody > tr").forEach { - when (it.getElementsByTag("th").first().text().trim().toLowerCase()) { - "author(s)" -> { - author = it.getElementsByTag("a").joinToString(transform = Element::text) - } - "artist(s)" -> { - artist = it.getElementsByTag("a").joinToString(transform = Element::text) - } - "genre(s)" -> { - genre = it.getElementsByTag("a").joinToString(transform = Element::text) - } - "status" -> { - status = when (it.getElementsByTag("td").text().trim().toLowerCase()) { - "ongoing" -> SManga.ONGOING - "completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - } - } - - description = document.getElementsByClass("summary").text().trim() - - // add alternative name to manga description - val altName = "Alternative Name: " - document.select(".attr > tbody > tr:contains(Alter) td").firstOrNull()?.ownText()?.let { - if (it.isEmpty().not()) { - description += when { - description!!.isEmpty() -> altName + it - else -> "\n\n$altName" + it - } - } - } - - } - - // force network to make sure chapter prefs take effect - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + manga.url, headers, CacheControl.FORCE_NETWORK) - } - - override fun chapterListParse(response: Response): List<SChapter> { - fun List<SChapter>.getMissingChapters(allChapters: List<SChapter>): List<SChapter> { - val chapterNums = this.map { it.chapter_number } - return allChapters.filter { it.chapter_number !in chapterNums }.distinctBy { it.chapter_number } - } - - fun List<SChapter>.filterOrAll(source: String): List<SChapter> { - val chapters = this.filter { it.scanlator!!.contains(source) } - return if (chapters.isNotEmpty()) { - (chapters + chapters.getMissingChapters(this)).sortedByDescending { it.chapter_number } - } else { - this - } - } - - val mangaBySource = response.asJsoup().select("div[id^=stream]") - .map { sourceElement -> - var lastNum = 0F - val sourceName = sourceElement.select("i + span").text() - - sourceElement.select(chapterListSelector()) - .reversed() // so incrementing lastNum works - .map { chapterElement -> - chapterFromElement(chapterElement, sourceName, lastNum) - .also { lastNum = it.chapter_number } - } - .distinctBy { it.chapter_number } // there's even duplicate chapters within a source ( -.- ) - } - - return when (getSourcePref()) { - // source with most chapters along with chapters that source doesn't have - "most" -> { - val chapters = mangaBySource.maxByOrNull { it.count() }!! - (chapters + chapters.getMissingChapters(mangaBySource.flatten())).sortedByDescending { it.chapter_number } - } - // "smart list" - try not to miss a chapter and avoid dupes - "smart" -> mangaBySource.flatten().distinctBy { it.chapter_number }.sortedByDescending { it.chapter_number } - // use a specific source + any missing chapters, display all if none available from that source - "rock" -> mangaBySource.flatten().filterOrAll("Rock") - "duck" -> mangaBySource.flatten().filterOrAll("Duck") - "mini" -> mangaBySource.flatten().filterOrAll("Mini") - "fox" -> mangaBySource.flatten().filterOrAll("Fox") - "panda" -> mangaBySource.flatten().filterOrAll("Panda") - // all sources, all chapters - else -> mangaBySource.flatMap { it.reversed() } - } - } - - override fun chapterListSelector() = ".volume .chapter li" - - private val chapterNumberRegex = Regex("""\b\d+\.?\d?\b""") - - private fun chapterFromElement(element: Element, source: String, lastNum: Float): SChapter { - fun Float.incremented() = this + .00001F - fun Float?.orIncrementLastNum() = if (this == null || this < lastNum) lastNum.incremented() else this - - return SChapter.create().apply { - element.select(".tit > a").first().let { - url = it.attr("href").removeSuffix("1") - name = it.text() - } - // Get the chapter number or create a unique one if it's not available - chapter_number = chapterNumberRegex.findAll(name) - .toList() - .map { it.value.toFloatOrNull() } - .let { nums -> - when { - nums.count() == 1 -> nums[0].orIncrementLastNum() - nums.count() >= 2 -> nums[1].orIncrementLastNum() - else -> lastNum.incremented() - } - } - date_upload = parseDate(element.select(".time").first().text().trim()) - scanlator = source - } - } - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - @SuppressLint("DefaultLocale") - private fun parseDate(date: String): Long { - val lcDate = date.toLowerCase() - if (lcDate.endsWith("ago")) return parseRelativeDate(lcDate) - - // Handle 'yesterday' and 'today' - var relativeDate: Calendar? = null - if (lcDate.startsWith("yesterday")) { - relativeDate = Calendar.getInstance() - relativeDate.add(Calendar.DAY_OF_MONTH, -1) // yesterday - } else if (lcDate.startsWith("today")) { - relativeDate = Calendar.getInstance() - } - - relativeDate?.let { - // Since the date is not specified, it defaults to 1970! - val time = dateFormatTimeOnly.parse(lcDate.substringAfter(' ')) ?: return 0 - val cal = Calendar.getInstance() - cal.time = time - - // Copy time to relative date - it.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY)) - it.set(Calendar.MINUTE, cal.get(Calendar.MINUTE)) - return it.timeInMillis - } - - return dateFormat.parse(lcDate)?.time ?: 0 - } - - /** - * Parses dates in this form: - * `11 days ago` - */ - private fun parseRelativeDate(date: String): Long { - val trimmedDate = date.split(" ") - - if (trimmedDate[2] != "ago") return 0 - - val number = when (trimmedDate[0]) { - "a" -> 1 - else -> trimmedDate[0].toIntOrNull() ?: return 0 - } - val unit = trimmedDate[1].removeSuffix("s") // Remove 's' suffix - - val now = Calendar.getInstance() - - // Map English unit to Java unit - val javaUnit = when (unit) { - "year" -> Calendar.YEAR - "month" -> Calendar.MONTH - "week" -> Calendar.WEEK_OF_MONTH - "day" -> Calendar.DAY_OF_MONTH - "hour" -> Calendar.HOUR - "minute" -> Calendar.MINUTE - "second" -> Calendar.SECOND - else -> return 0 - } - - now.add(javaUnit, -number) - - return now.timeInMillis - } - - private val objRegex = Regex("""var _load_pages = (\[.*])""") - - override fun pageListParse(response: Response): List<Page> { - val obj = objRegex.find(response.body()!!.string())?.groupValues?.get(1) - ?: throw Exception("_load_pages not found - ${response.request().url()}") - val jsonArray = JSONArray(obj) - return (0 until jsonArray.length()).map { i -> jsonArray.getJSONObject(i).getString("u") } - .mapIndexed { i, url -> Page(i, "", if (url.startsWith("//")) "https://$url" else url) } - } - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used") - - // Unused, we can get image urls directly from the chapter page - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList( - AuthorArtistText(), - SearchTypeFilter("Title query", "name-match"), - SearchTypeFilter("Author/Artist query", "autart-match"), - SortFilter(), - GenreGroup(), - GenreInclusionFilter(), - ChapterCountFilter(), - StatusFilter(), - RatingFilter(), - TypeFilter(), - YearFilter() - ) - - private class SearchTypeFilter(name: String, val uriParam: String) : - Filter.Select<String>(name, STATE_MAP), UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (STATE_MAP[state] != "contain") { - uri.appendQueryParameter(uriParam, STATE_MAP[state]) - } - } - - companion object { - private val STATE_MAP = arrayOf("contain", "begin", "end") - } - } - - private class AuthorArtistText : Filter.Text("Author/Artist"), UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state.isNotEmpty()) { - uri.appendQueryParameter("autart", state) - } - } - } - - private class GenreFilter(val uriParam: String, displayName: String) : Filter.TriState(displayName) - - private class GenreGroup : - Filter.Group<GenreFilter>( - "Genres", - listOf( - GenreFilter("4-koma", "4 koma"), - GenreFilter("action", "Action"), - GenreFilter("adaptation", "Adaptation"), - GenreFilter("adult", "Adult"), - GenreFilter("adventure", "Adventure"), - GenreFilter("aliens", "Aliens"), - GenreFilter("animals", "Animals"), - GenreFilter("anthology", "Anthology"), - GenreFilter("award-winning", "Award winning"), - GenreFilter("comedy", "Comedy"), - GenreFilter("cooking", "Cooking"), - GenreFilter("crime", "Crime"), - GenreFilter("crossdressing", "Crossdressing"), - GenreFilter("delinquents", "Delinquents"), - GenreFilter("demons", "Demons"), - GenreFilter("doujinshi", "Doujinshi"), - GenreFilter("drama", "Drama"), - GenreFilter("ecchi", "Ecchi"), - GenreFilter("fan-colored", "Fan colored"), - GenreFilter("fantasy", "Fantasy"), - GenreFilter("food", "Food"), - GenreFilter("full-color", "Full color"), - GenreFilter("game", "Game"), - GenreFilter("gender-bender", "Gender bender"), - GenreFilter("genderswap", "Genderswap"), - GenreFilter("ghosts", "Ghosts"), - GenreFilter("gore", "Gore"), - GenreFilter("gossip", "Gossip"), - GenreFilter("gyaru", "Gyaru"), - GenreFilter("harem", "Harem"), - GenreFilter("historical", "Historical"), - GenreFilter("horror", "Horror"), - GenreFilter("incest", "Incest"), - GenreFilter("isekai", "Isekai"), - GenreFilter("josei", "Josei"), - GenreFilter("kids", "Kids"), - GenreFilter("loli", "Loli"), - GenreFilter("lolicon", "Lolicon"), - GenreFilter("long-strip", "Long strip"), - GenreFilter("mafia", "Mafia"), - GenreFilter("magic", "Magic"), - GenreFilter("magical-girls", "Magical girls"), - GenreFilter("manhwa", "Manhwa"), - GenreFilter("martial-arts", "Martial arts"), - GenreFilter("mature", "Mature"), - GenreFilter("mecha", "Mecha"), - GenreFilter("medical", "Medical"), - GenreFilter("military", "Military"), - GenreFilter("monster-girls", "Monster girls"), - GenreFilter("monsters", "Monsters"), - GenreFilter("music", "Music"), - GenreFilter("mystery", "Mystery"), - GenreFilter("ninja", "Ninja"), - GenreFilter("office-workers", "Office workers"), - GenreFilter("official-colored", "Official colored"), - GenreFilter("one-shot", "One shot"), - GenreFilter("parody", "Parody"), - GenreFilter("philosophical", "Philosophical"), - GenreFilter("police", "Police"), - GenreFilter("post-apocalyptic", "Post apocalyptic"), - GenreFilter("psychological", "Psychological"), - GenreFilter("reincarnation", "Reincarnation"), - GenreFilter("reverse-harem", "Reverse harem"), - GenreFilter("romance", "Romance"), - GenreFilter("samurai", "Samurai"), - GenreFilter("school-life", "School life"), - GenreFilter("sci-fi", "Sci fi"), - GenreFilter("seinen", "Seinen"), - GenreFilter("shota", "Shota"), - GenreFilter("shotacon", "Shotacon"), - GenreFilter("shoujo", "Shoujo"), - GenreFilter("shoujo-ai", "Shoujo ai"), - GenreFilter("shounen", "Shounen"), - GenreFilter("shounen-ai", "Shounen ai"), - GenreFilter("slice-of-life", "Slice of life"), - GenreFilter("smut", "Smut"), - GenreFilter("space", "Space"), - GenreFilter("sports", "Sports"), - GenreFilter("super-power", "Super power"), - GenreFilter("superhero", "Superhero"), - GenreFilter("supernatural", "Supernatural"), - GenreFilter("survival", "Survival"), - GenreFilter("suspense", "Suspense"), - GenreFilter("thriller", "Thriller"), - GenreFilter("time-travel", "Time travel"), - GenreFilter("toomics", "Toomics"), - GenreFilter("traditional-games", "Traditional games"), - GenreFilter("tragedy", "Tragedy"), - GenreFilter("user-created", "User created"), - GenreFilter("vampire", "Vampire"), - GenreFilter("vampires", "Vampires"), - GenreFilter("video-games", "Video games"), - GenreFilter("virtual-reality", "Virtual reality"), - GenreFilter("web-comic", "Web comic"), - GenreFilter("webtoon", "Webtoon"), - GenreFilter("wuxia", "Wuxia"), - GenreFilter("yaoi", "Yaoi"), - GenreFilter("yuri", "Yuri"), - GenreFilter("zombies", "Zombies") - ) - ), - UriFilter { - override fun addToUri(uri: Uri.Builder) { - val genresParameterValue = state.filter { it.isIncluded() }.joinToString(",") { it.uriParam } - if (genresParameterValue.isNotEmpty()) { - uri.appendQueryParameter("genres", genresParameterValue) - } - - val genresExcludeParameterValue = state.filter { it.isExcluded() }.joinToString(",") { it.uriParam } - if (genresExcludeParameterValue.isNotEmpty()) { - uri.appendQueryParameter("genres-exclude", genresExcludeParameterValue) - } - } - } - - private class GenreInclusionFilter : UriSelectFilter( - "Genre inclusion", - "genres-mode", - arrayOf( - Pair("and", "And mode"), - Pair("or", "Or mode") - ) - ) - - private class ChapterCountFilter : UriSelectFilter( - "Chapter count", - "chapters", - arrayOf( - Pair("any", "Any"), - Pair("1", "1 +"), - Pair("5", "5 +"), - Pair("10", "10 +"), - Pair("20", "20 +"), - Pair("30", "30 +"), - Pair("40", "40 +"), - Pair("50", "50 +"), - Pair("100", "100 +"), - Pair("150", "150 +"), - Pair("200", "200 +") - ) - ) - - private class StatusFilter : UriSelectFilter( - "Status", - "status", - arrayOf( - Pair("any", "Any"), - Pair("completed", "Completed"), - Pair("ongoing", "Ongoing") - ) - ) - - private class RatingFilter : UriSelectFilter( - "Rating", - "rating", - arrayOf( - Pair("any", "Any"), - Pair("5", "5 stars"), - Pair("4", "4 stars"), - Pair("3", "3 stars"), - Pair("2", "2 stars"), - Pair("1", "1 star"), - Pair("0", "0 stars") - ) - ) - - private class TypeFilter : UriSelectFilter( - "Type", - "types", - arrayOf( - Pair("any", "Any"), - Pair("manga", "Japanese Manga"), - Pair("manhwa", "Korean Manhwa"), - Pair("manhua", "Chinese Manhua"), - Pair("unknown", "Unknown") - ) - ) - - private class YearFilter : UriSelectFilter( - "Release year", - "years", - arrayOf( - Pair("any", "Any"), - // Get all years between today and 1946 - *(Calendar.getInstance().get(Calendar.YEAR) downTo 1946).map { - Pair(it.toString(), it.toString()) - }.toTypedArray() - ) - ) - - private class SortFilter : UriSelectFilter( - "Sort", - "orderby", - arrayOf( - Pair("a-z", "A-Z"), - Pair("views_a", "Views all-time"), - Pair("views_y", "Views last 365 days"), - Pair("views_s", "Views last 180 days"), - Pair("views_t", "Views last 90 days"), - Pair("rating", "Rating"), - Pair("update", "Latest"), - Pair("create", "New manga") - ), - firstIsUnspecified = false, - defaultValue = 1 - ) - - /** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ - // vals: <name, display> - private open class UriSelectFilter( - displayName: String, - val uriParam: String, - val vals: Array<Pair<String, String>>, - val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) - uri.appendQueryParameter(uriParam, vals[state].first) - } - } - - /** - * Represents a filter that is able to modify a URI. - */ - private interface UriFilter { - fun addToUri(uri: Uri.Builder) - } - - // Preferences - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val myPref = androidx.preference.ListPreference(screen.context).apply { - key = SOURCE_PREF_TITLE - title = SOURCE_PREF_TITLE - entries = sourceArray.map { it.first }.toTypedArray() - entryValues = sourceArray.map { it.second }.toTypedArray() - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SOURCE_PREF, entry).commit() - } - } - screen.addPreference(myPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val myPref = ListPreference(screen.context).apply { - key = SOURCE_PREF_TITLE - title = SOURCE_PREF_TITLE - entries = sourceArray.map { it.first }.toTypedArray() - entryValues = sourceArray.map { it.second }.toTypedArray() - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SOURCE_PREF, entry).commit() - } - } - screen.addPreference(myPref) - } - private fun getSourcePref(): String? = preferences.getString(SOURCE_PREF, "all") - - companion object { - private const val SOURCE_PREF_TITLE = "Chapter List Source" - private const val SOURCE_PREF = "Manga_Park_Source" - private val sourceArray = arrayOf( - Pair("All sources, all chapters", "all"), - Pair("Source with most chapters", "most"), - Pair("Smart list", "smart"), - Pair("Prioritize source: Rock", "rock"), - Pair("Prioritize source: Duck", "duck"), - Pair("Prioritize source: Mini", "mini"), - Pair("Prioritize source: Fox", "fox"), - Pair("Prioritize source: Panda", "panda") - ) - } -} diff --git a/src/en/mangapill/AndroidManifest.xml b/src/en/mangapill/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangapill/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangapill/build.gradle b/src/en/mangapill/build.gradle deleted file mode 100644 index 1adf93758..000000000 --- a/src/en/mangapill/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaPill' - pkgNameSuffix = 'en.mangapill' - extClass = '.MangaPill' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangapill/res/mipmap-hdpi/ic_launcher.png b/src/en/mangapill/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 2500cbc0f..000000000 Binary files a/src/en/mangapill/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapill/res/mipmap-mdpi/ic_launcher.png b/src/en/mangapill/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index b827ef6de..000000000 Binary files a/src/en/mangapill/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapill/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangapill/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 427db16c6..000000000 Binary files a/src/en/mangapill/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapill/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangapill/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 875845cd3..000000000 Binary files a/src/en/mangapill/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapill/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangapill/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index f14da50b9..000000000 Binary files a/src/en/mangapill/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangapill/res/web_hi_res_512.png b/src/en/mangapill/res/web_hi_res_512.png deleted file mode 100644 index e83834c40..000000000 Binary files a/src/en/mangapill/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangapill/src/eu/kanade/tachiyomi/extension/en/mangapill/MangaPill.kt b/src/en/mangapill/src/eu/kanade/tachiyomi/extension/en/mangapill/MangaPill.kt deleted file mode 100644 index 2c973b30f..000000000 --- a/src/en/mangapill/src/eu/kanade/tachiyomi/extension/en/mangapill/MangaPill.kt +++ /dev/null @@ -1,218 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangapill - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class MangaPill : ParsedHttpSource() { - - override val name = "MangaPill" - override val baseUrl = "https://mangapill.com" - override val lang = "en" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/search?q=&type=&status=&page=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl", headers) - } - - override fun popularMangaSelector() = ".grid.justify-between.gap-3.grid-cols-2 > div" - override fun latestUpdatesSelector() = ".w-full.flex.rounded.border.border-sm.border-color-border-primary.mb-2" - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").attr("data-src") - manga.setUrlWithoutDomain(element.select("a").first().attr("href")) - manga.title = element.select(".mt-2 a").text() - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").attr("data-src") - var url = element.select("a").first().attr("href") - manga.setUrlWithoutDomain(url.substringBeforeLast("/").replace("chapters", "manga").substringBeforeLast("-") + "/" + url.substringAfterLast("/").substringBefore("-chapter")) - manga.title = element.select(".mb-2 a").text().substringBefore("Chapter").trim() - return manga - } - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.next.page-numbers" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select(".flex.flex-col > div:nth-child(1) > .text-color-text-secondary").text() - manga.artist = "" - val genres = mutableListOf<String>() - document.select("a[href*=genre]").forEach { element -> - val genre = element.text() - genres.add(genre) - } - manga.genre = genres.joinToString(", ") - manga.status = parseStatus(document.select("h5:contains(Status) + div").text()) - manga.description = document.select(".flex.flex-col > div p").first().text() - manga.thumbnail_url = document.select(".object-cover").first().attr("data-src") - - return manga - } - - private fun parseStatus(element: String): Int = when { - - element.toLowerCase().contains("publishing") -> SManga.ONGOING - element.toLowerCase().contains("finished") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "option[value]" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.attr("value") - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement) - chapter.name = element.text() - chapter.date_upload = 0 - return chapter - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - document.select("picture img").forEachIndexed { i, it -> - pages.add(Page(i, "", it.attr("data-src"))) - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("q", query) - - filters.forEach { filter -> - when (filter) { - is GenreList -> { - - val genreInclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } - } - if (genreInclude.isNotEmpty()) { - genreInclude.forEach { genre -> - url.addQueryParameter("genre", genre) - } - } - } - is Status -> url.addQueryParameter("status", filter.toUriPart()) - is Type -> url.addQueryParameter("type", filter.toUriPart()) - } - } - return GET(url.toString(), headers) - } - - private class Type : UriPartFilter( - "Type", - arrayOf( - Pair("All", ""), - Pair("Manga", "manga"), - Pair("Novel", "novel"), - Pair("One-Shot", "one-shot"), - Pair("Doujinshi", "doujinshi"), - Pair("Manhwa", "manhwa"), - Pair("Manhua", "manhua"), - Pair("Oel", "oel") - ) - ) - - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - private class Status : UriPartFilter( - "Status", - arrayOf( - Pair("All", ""), - Pair("Publishing", "publishing"), - Pair("Finished", "finished"), - Pair("On Hiatus", "on hiatus"), - Pair("Discontinued", "discontinued"), - Pair("Not yet Published", "not yet published") - ) - ) - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - Status(), - Type(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("Action"), - Genre("Adventure"), - Genre("Cars"), - Genre("Comedy"), - Genre("Dementia"), - Genre("Demons"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Game"), - Genre("Harem"), - Genre("Hentai"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Kids"), - Genre("Magic"), - Genre("Martial Arts"), - Genre("Mecha"), - Genre("Military"), - Genre("Music"), - Genre("Mystery"), - Genre("Parody"), - Genre("Police"), - Genre("Psychological"), - Genre("Romance"), - Genre("Samurai"), - Genre("School"), - Genre("Sci-Fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Space"), - Genre("Sports"), - Genre("Super Power"), - Genre("Supernatural"), - Genre("Thriller"), - Genre("Vampire"), - Genre("Yaoi"), - Genre("Yuri") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/en/mangarockes/AndroidManifest.xml b/src/en/mangarockes/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangarockes/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangarockes/build.gradle b/src/en/mangarockes/build.gradle deleted file mode 100644 index c35fc1beb..000000000 --- a/src/en/mangarockes/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaRock.es' - pkgNameSuffix = 'en.mangarockes' - extClass = '.MangaRockEs' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangarockes/res/mipmap-hdpi/ic_launcher.png b/src/en/mangarockes/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d781a98f4..000000000 Binary files a/src/en/mangarockes/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangarockes/res/mipmap-mdpi/ic_launcher.png b/src/en/mangarockes/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f16c6ddad..000000000 Binary files a/src/en/mangarockes/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangarockes/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangarockes/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a44510632..000000000 Binary files a/src/en/mangarockes/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangarockes/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangarockes/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 873a21662..000000000 Binary files a/src/en/mangarockes/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangarockes/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangarockes/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d312cafe9..000000000 Binary files a/src/en/mangarockes/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangarockes/res/web_hi_res_512.png b/src/en/mangarockes/res/web_hi_res_512.png deleted file mode 100644 index cb6ffacee..000000000 Binary files a/src/en/mangarockes/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangarockes/src/eu/kanade/tachiyomi/extension/en/mangarockes/MangaRockEs.kt b/src/en/mangarockes/src/eu/kanade/tachiyomi/extension/en/mangarockes/MangaRockEs.kt deleted file mode 100644 index e44901c2c..000000000 --- a/src/en/mangarockes/src/eu/kanade/tachiyomi/extension/en/mangarockes/MangaRockEs.kt +++ /dev/null @@ -1,324 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangarockes - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.JsonArray -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale -import kotlin.experimental.and -import kotlin.experimental.xor - -class MangaRockEs : ParsedHttpSource() { - - override val name = "MangaRock.es" - - override val baseUrl = "https://mangarock.es" - - override val lang = "en" - - override val supportsLatest = true - - // Handles the page decoding - override val client: OkHttpClient = network.cloudflareClient.newBuilder().addInterceptor( - fun(chain): Response { - val url = chain.request().url().toString() - val response = chain.proceed(chain.request()) - if (!url.endsWith(".mri")) return response - - val decoded: ByteArray = decodeMri(response) - val mediaType = MediaType.parse("image/webp") - val rb = ResponseBody.create(mediaType, decoded) - return response.newBuilder().body(rb).build() - } - ).build() - - // Popular - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/$page?sort=rank", headers) - - override fun popularMangaSelector() = "div.col-five" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("h3 a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "a.page-link:contains(next)" - - // Latest - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/manga/latest/$page?sort=date", headers) - - override fun latestUpdatesSelector() = "div.product-item-detail" - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/search/${query.replace(" ", "+")}/$page", headers) - } else { - val url = HttpUrl.parse("$baseUrl/manga" + if (page > 1) "/$page" else "")!!.newBuilder() - filters.forEach { filter -> - when (filter) { - is StatusFilter -> url.addQueryParameter("status", filter.toUriPart()) - is RankFilter -> url.addQueryParameter("rank", filter.toUriPart()) - is SortBy -> url.addQueryParameter("sort", filter.toUriPart()) - is GenreList -> { - val genres = filter.state - .filter { it.state } - .joinToString(".") { it.uriPart } - url.addQueryParameter("genres", genres) - } - } - } - GET(url.toString(), headers) - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - return SManga.create().apply { - document.select("div.block-info-manga").let { info -> - thumbnail_url = info.select("div.thumb_inner").attr("style") - .substringAfter("'").substringBefore("'") - title = info.select("h1").text() - author = info.select("div.author_item").text() - status = info.select("div.status_chapter_item").text().substringBefore(" ").let { - when { - it.contains("Ongoing", ignoreCase = true) -> SManga.ONGOING - it.contains("Completed", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - } - genre = document.select("div.tags a").joinToString { it.text() } - description = document.select("div.full.summary p").filterNot { it.text().isNullOrEmpty() }.joinToString("\n") - } - } - - // Chapters - - override fun chapterListSelector(): String = "tbody[data-test=chapter-table] tr" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - date_upload = element.select("td").lastOrNull()?.text()?.let { date -> - if (date.contains("ago", ignoreCase = true)) { - val trimmedDate = date.substringBefore(" ago").removeSuffix("s").split(" ") - - val calendar = Calendar.getInstance() - when (trimmedDate[1]) { - "day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) } - "hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) } - "minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) } - "second" -> calendar.apply { add(Calendar.SECOND, -trimmedDate[0].toInt()) } - } - - calendar.timeInMillis - } else { - SimpleDateFormat("MMM d, yyyy", Locale.US).parse(date)?.time ?: 0L - } - } ?: 0 - } - } - - // Pages - - private val gson by lazy { Gson() } - - override fun pageListParse(response: Response): List<Page> { - val responseString = response.body()!!.string() - return Regex("""mangaData = (\[.*]);""", RegexOption.IGNORE_CASE).find(responseString)?.groupValues?.get(1)?.let { array -> - gson.fromJson<JsonArray>(array) - .mapIndexed { i, jsonElement -> Page(i, "", jsonElement.asJsonObject["url"].asString) } - } - ?: Regex("""getManga\(\d+, '(http.*)',""").findAll(responseString).toList() - .mapIndexed { i, mr -> Page(i, "", mr.groupValues[1]) } - } - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("This method should not be called!") - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!") - - // Filters - - // See drawWebpToCanvas function in the site's client.js file - // Extracted code: https://jsfiddle.net/6h2sLcs4/30/ - private fun decodeMri(response: Response): ByteArray { - val data = response.body()!!.bytes() - - // Decode file if it starts with "E" (space when XOR-ed later) - if (data[0] != 69.toByte()) return data - - // Reconstruct WEBP header - // Doc: https://developers.google.com/speed/webp/docs/riff_container#webp_file_header - val buffer = ByteArray(data.size + 15) - val size = data.size + 7 - buffer[0] = 82 // R - buffer[1] = 73 // I - buffer[2] = 70 // F - buffer[3] = 70 // F - buffer[4] = (255.toByte() and size.toByte()) - buffer[5] = (size ushr 8).toByte() and 255.toByte() - buffer[6] = (size ushr 16).toByte() and 255.toByte() - buffer[7] = (size ushr 24).toByte() and 255.toByte() - buffer[8] = 87 // W - buffer[9] = 69 // E - buffer[10] = 66 // B - buffer[11] = 80 // P - buffer[12] = 86 // V - buffer[13] = 80 // P - buffer[14] = 56 // 8 - - // Decrypt file content using XOR cipher with 101 as the key - val cipherKey = 101.toByte() - for (r in data.indices) { - buffer[r + 15] = cipherKey xor data[r] - } - - return buffer - } - - // Filters - - private class StatusFilter : UriPartFilter( - "Status", - arrayOf( - Pair("All", "all"), - Pair("Completed", "completed"), - Pair("Ongoing", "ongoing") - ) - ) - - private class RankFilter : UriPartFilter( - "Rank", - arrayOf( - Pair("All", "all"), - Pair("1 - 999", "1-999"), - Pair("1k - 2k", "1000-2000"), - Pair("2k - 3k", "2000-3000"), - Pair("3k - 4k", "3000-4000"), - Pair("4k - 5k", "4000-5000"), - Pair("5k - 6k", "5000-6000"), - Pair("6k - 7k", "6000-7000"), - Pair("7k - 8k", "7000-8000"), - Pair("8k - 9k", "8000-9000"), - Pair("9k - 19k", "9000-10000"), - Pair("10k - 11k", "10000-11000") - ) - ) - - private class SortBy : UriPartFilter( - "Sort by", - arrayOf( - Pair("Name", "name"), - Pair("Rank", "rank") - ) - ) - - private class Genre(name: String, val uriPart: String) : Filter.CheckBox(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - override fun getFilterList() = FilterList( - // Search and filter don't work at the same time - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - StatusFilter(), - RankFilter(), - SortBy(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll('._2DMqI .mdl-checkbox')].map(n => `Genre("${n.querySelector('.mdl-checkbox__label').innerText}", "${n.querySelector('input').dataset.oid}")`).sort().join(',\n') - // on https://mangarock.com/manga - private fun getGenreList() = listOf( - Genre("4-koma", "4-koma"), - Genre("Action", "action"), - Genre("Adult", "adult"), - Genre("Adventure", "adventure"), - Genre("Comedy", "comedy"), - Genre("Demons", "demons"), - Genre("Doujinshi", "doujinshi"), - Genre("Drama", "drama"), - Genre("Ecchi", "ecchi"), - Genre("Fantasy", "fantasy"), - Genre("Gender Bender", "gender-bender"), - Genre("Harem", "harem"), - Genre("Historical", "historical"), - Genre("Horror", "horror"), - Genre("Isekai", "isekai"), - Genre("Josei", "josei"), - Genre("Kids", "kids"), - Genre("Magic", "magic"), - Genre("Martial Arts", "martial-arts"), - Genre("Mature", "mature"), - Genre("Mecha", "mecha"), - Genre("Military", "military"), - Genre("Music", "music"), - Genre("Mystery", "mystery"), - Genre("One Shot", "one-shot"), - Genre("Parody", "parody"), - Genre("Police", "police"), - Genre("Psychological", "psychological"), - Genre("Romance", "romance"), - Genre("School Life", "school-life"), - Genre("Sci-Fi", "sci-fi"), - Genre("Seinen", "seinen"), - Genre("Shoujo Ai", "shoujo-ai"), - Genre("Shoujo", "shoujo"), - Genre("Shounen Ai", "shounen-ai"), - Genre("Shounen", "shounen"), - Genre("Slice of Life", "slice-of-life"), - Genre("Smut", "smut"), - Genre("Space", "space"), - Genre("Sports", "sports"), - Genre("Super Power", "super-power"), - Genre("Supernatural", "supernatural"), - Genre("Tragedy", "tragedy"), - Genre("Vampire", "vampire"), - Genre("Webtoons", "webtoons"), - Genre("Yaoi", "yaoi"), - Genre("Yuri", "yuri") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/en/mangasail/AndroidManifest.xml b/src/en/mangasail/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangasail/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangasail/build.gradle b/src/en/mangasail/build.gradle deleted file mode 100644 index 37a25fc94..000000000 --- a/src/en/mangasail/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangasail' - pkgNameSuffix = 'en.mangasail' - extClass = '.Mangasail' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangasail/res/mipmap-hdpi/ic_launcher.png b/src/en/mangasail/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 68beb8f1b..000000000 Binary files a/src/en/mangasail/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangasail/res/mipmap-mdpi/ic_launcher.png b/src/en/mangasail/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 89a451a98..000000000 Binary files a/src/en/mangasail/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangasail/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangasail/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 76028aef7..000000000 Binary files a/src/en/mangasail/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangasail/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangasail/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 22d015485..000000000 Binary files a/src/en/mangasail/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangasail/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangasail/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 786b256ca..000000000 Binary files a/src/en/mangasail/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangasail/res/web_hi_res_512.png b/src/en/mangasail/res/web_hi_res_512.png deleted file mode 100644 index 32fcf78f3..000000000 Binary files a/src/en/mangasail/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangasail/src/eu/kanade/tachiyomi/extension/en/mangasail/Mangasail.kt b/src/en/mangasail/src/eu/kanade/tachiyomi/extension/en/mangasail/Mangasail.kt deleted file mode 100644 index 7cf1a4450..000000000 --- a/src/en/mangasail/src/eu/kanade/tachiyomi/extension/en/mangasail/Mangasail.kt +++ /dev/null @@ -1,170 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangasail - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.Jsoup.parse -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Mangasail : ParsedHttpSource() { - - override val name = "Mangasail" - - override val baseUrl = "https://www.mangasail.co" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - /* Site loads some manga info (manga cover, author name, status, etc.) client side through JQuery - need to add this header for when we request these data fragments - Also necessary for latest updates request */ - override fun headersBuilder() = super.headersBuilder().add("X-Authcache", "1")!! - - override fun popularMangaSelector() = "tbody tr" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/directory/hot" + if (page > 1) "/hot?page= + ${page - 1}" else "", headers) - } - - override fun latestUpdatesSelector() = "ul#latest-list > li" - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/sites/all/modules/authcache/modules/authcache_p13n/frontcontroller/authcache.php?r=frag/block/showmanga-lastest_list&o[q]=node", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("td:first-of-type a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = element.select("td img").first().attr("src") - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.title = element.select("a strong").text() - element.select("a:has(img)").let { - manga.url = it.attr("href") - // Thumbnails are kind of low-res on latest updates page, transform the img url to get a better version - manga.thumbnail_url = it.select("img").first().attr("src").substringBefore("?").replace("styles/minicover/public/", "") - } - return manga - } - - override fun popularMangaNextPageSelector() = "table + div.text-center ul.pagination li.next a" - - override fun latestUpdatesNextPageSelector(): String = "There is no next page" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/node/$query" + if (page > 1) "?page= + ${page - 1}" else "") - } - - override fun searchMangaSelector() = "h3.title" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - // Search page doesn't contain cover images, have to get them from the manga's page; but first we need that page's node number - val node = getNodeNumber(client.newCall(GET(it.attr("href"), headers)).execute().asJsoup()) - manga.thumbnail_url = getNodeDetail(node, "field_image2") - } - return manga - } - - private val gson by lazy { Gson() } - - // Function to get data fragments from website - private fun getNodeDetail(node: String, field: String): String? { - val requestUrl = "$baseUrl/sites/all/modules/authcache/modules/authcache_p13n/frontcontroller/authcache.php?a[field][0]=$node:full:en&r=asm/field/node/$field&o[q]=node/$node" - val responseString = client.newCall(GET(requestUrl, headers)).execute().body()?.string() ?: return null - return with(gson.fromJson<JsonObject>(responseString)) { - when (field) { - "field_image2" -> this["field"]["$node:full:en"].asString.substringAfter("src=\"").substringBefore("\"") - "field_status", "field_author", "field_artist" -> this["field"]["$node:full:en"].asString.substringAfter("even\">").substringBefore("</div>") - "body" -> parse(this["field"]["$node:full:en"].asString, baseUrl).select("p").text().substringAfter("summary: ") - "field_genres" -> parse(this["field"]["$node:full:en"].asString, baseUrl).select("a").text() - else -> null - } - } - } - - // Get a page's node number so we can get data fragments for that page - private fun getNodeNumber(document: Document): String { - return document.select("[rel=shortlink]").attr("href").split("/").last().replace("\"", "") - } - - override fun searchMangaNextPageSelector() = "li.next a" - - // On source's website most of these details are loaded through JQuery - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - title = document.select("div.main-content-inner").select("h1").first().text() - getNodeNumber(document).let { node -> - author = getNodeDetail(node, "field_author") - artist = getNodeDetail(node, "field_artist") - genre = getNodeDetail(node, "field_genres")?.replace(" ", ", ") - status = getNodeDetail(node, "field_status").toStatus() - description = getNodeDetail(node, "body") - thumbnail_url = getNodeDetail(node, "field_image2") - } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("Ongoing") -> SManga.ONGOING - this.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "tbody tr" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.select("a").first().attr("href")) - chapter.name = element.select("a").text() - chapter.date_upload = parseChapterDate(element.select("td + td").text()) - return chapter - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("d MMM yyyy", Locale.US) - } - } - - private fun parseChapterDate(string: String): Long { - return dateFormat.parse(string.substringAfter("on "))?.time ?: 0L - } - - override fun pageListParse(document: Document): List<Page> { - val objectString = document.select("script:containsData(paths)").first().data() - .substringAfter(" ").substringBefore(");") - return gson.fromJson<JsonObject>(objectString)["showmanga"]["paths"].asJsonArray.mapIndexed { i, jsonElement -> - Page(i, "", jsonElement.string) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/en/mangatown/AndroidManifest.xml b/src/en/mangatown/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/mangatown/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/mangatown/build.gradle b/src/en/mangatown/build.gradle deleted file mode 100644 index 8f14bbc5f..000000000 --- a/src/en/mangatown/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangatown' - pkgNameSuffix = 'en.mangatown' - extClass = '.Mangatown' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangatown/res/mipmap-hdpi/ic_launcher.png b/src/en/mangatown/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 57592634e..000000000 Binary files a/src/en/mangatown/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangatown/res/mipmap-mdpi/ic_launcher.png b/src/en/mangatown/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1f412ee71..000000000 Binary files a/src/en/mangatown/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangatown/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangatown/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 821857cbc..000000000 Binary files a/src/en/mangatown/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangatown/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangatown/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index da4c28db4..000000000 Binary files a/src/en/mangatown/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangatown/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangatown/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 97746fbb7..000000000 Binary files a/src/en/mangatown/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangatown/res/web_hi_res_512.png b/src/en/mangatown/res/web_hi_res_512.png deleted file mode 100644 index e0e8569da..000000000 Binary files a/src/en/mangatown/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/mangatown/src/eu/kanade/tachiyomi/extension/en/mangatown/Mangatown.kt b/src/en/mangatown/src/eu/kanade/tachiyomi/extension/en/mangatown/Mangatown.kt deleted file mode 100644 index a911c33ed..000000000 --- a/src/en/mangatown/src/eu/kanade/tachiyomi/extension/en/mangatown/Mangatown.kt +++ /dev/null @@ -1,138 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangatown - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Mangatown : ParsedHttpSource() { - - override val name = "Mangatown" - - override val baseUrl = "https://www.mangatown.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaSelector() = "li:has(a.manga_cover)" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/directory/0-0-0-0-0-0/$page.htm") - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest/$page.htm") - } - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("p.title a").first().let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.next:not([href^=javascript])" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return POST("$baseUrl/search?page=$page&name=$query", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.article_content") - - return SManga.create().apply { - title = infoElement.select("h1").text() - author = infoElement.select("b:containsOwn(author) + a").text() - artist = infoElement.select("b:containsOwn(artist) + a").text() - status = if (infoElement.select("div.chapter_content:contains(has been licensed)").isNotEmpty()) { - SManga.LICENSED - } else { - parseStatus(infoElement.select("li:has(b:containsOwn(status))").text()) - } - genre = infoElement.select("li:has(b:containsOwn(genre)) a").joinToString { it.text() } - description = document.select("span#show").text().removeSuffix("HIDE") - thumbnail_url = document.select("div.detail_info img").attr("abs:src") - } - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("Ongoing", ignoreCase = true) -> SManga.ONGOING - status.contains("Completed", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "ul.chapter_list li" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { urlElement -> - setUrlWithoutDomain(urlElement.attr("href")) - name = "${urlElement.text()} ${element.select("span:not(span.time,span.new)").joinToString(" ") { it.text() }}" - } - date_upload = parseDate(element.select("span.time").text()) - } - } - - private fun parseDate(date: String): Long { - return when { - date.contains("Today") -> Calendar.getInstance().apply {}.timeInMillis - date.contains("Yesterday") -> Calendar.getInstance().apply { add(Calendar.DAY_OF_MONTH, -1) }.timeInMillis - else -> { - try { - SimpleDateFormat("MMM dd,yyyy", Locale.US).parse(date)?.time ?: 0L - } catch (e: Exception) { - 0L - } - } - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("select#top_chapter_list ~ div.page_select option:not(:contains(featured))").mapIndexed { i, element -> - Page(i, element.attr("value").substringAfter("com")) - } - } - - // Get the page - override fun imageUrlRequest(page: Page) = GET(baseUrl + page.url) - - // Get the image from the requested page - override fun imageUrlParse(response: Response): String { - return response.asJsoup().select("div#viewer img").attr("abs:src") - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/manhwamanga/AndroidManifest.xml b/src/en/manhwamanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/manhwamanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/manhwamanga/build.gradle b/src/en/manhwamanga/build.gradle deleted file mode 100644 index b5e469189..000000000 --- a/src/en/manhwamanga/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ManhwaManga.net' - pkgNameSuffix = 'en.manhwamanga' - extClass = '.ManhwaManga' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/manhwamanga/res/mipmap-hdpi/ic_launcher.png b/src/en/manhwamanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 84f2e9487..000000000 Binary files a/src/en/manhwamanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwamanga/res/mipmap-mdpi/ic_launcher.png b/src/en/manhwamanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 068af711f..000000000 Binary files a/src/en/manhwamanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwamanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/manhwamanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ffcd483f5..000000000 Binary files a/src/en/manhwamanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwamanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/manhwamanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 14be764b9..000000000 Binary files a/src/en/manhwamanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwamanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/manhwamanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f549c1e7d..000000000 Binary files a/src/en/manhwamanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwamanga/res/web_hi_res_512.png b/src/en/manhwamanga/res/web_hi_res_512.png deleted file mode 100644 index c307cca26..000000000 Binary files a/src/en/manhwamanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/manhwamanga/src/eu/kanade/tachiyomi/extension/en/manhwamanga/ManhwaManga.kt b/src/en/manhwamanga/src/eu/kanade/tachiyomi/extension/en/manhwamanga/ManhwaManga.kt deleted file mode 100644 index e2d39ce69..000000000 --- a/src/en/manhwamanga/src/eu/kanade/tachiyomi/extension/en/manhwamanga/ManhwaManga.kt +++ /dev/null @@ -1,186 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.manhwamanga - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -@Nsfw -class ManhwaManga : ParsedHttpSource() { - override val name = "ManhwaManga.net" - override val baseUrl = "https://manhwamanga.net" - override val lang = "en" - override val supportsLatest = true - - override fun popularMangaSelector() = ".home-truyendecu" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - override fun chapterListSelector() = "#list-chapter > div.row > div > ul > li:nth-child(n)" - - override fun popularMangaNextPageSelector() = "li.active+li a[data-page]" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/most-views/page/$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest-updates/page/$page", headers) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/?s=$query", headers) - } else { - val url = HttpUrl.parse("$baseUrl/category/")!!.newBuilder() - filters.forEach { filter -> - when (filter) { - - is GenreFilter -> url.addPathSegment(filter.toUriPart()) - } - } - url.addPathSegment("page") - url.addPathSegment("$page") - GET(url.toString(), headers) - } - } - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").attr("href")) - manga.title = element.select("a").attr("title") - manga.thumbnail_url = element.select("a img").attr("src") - - return manga - } - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - protected fun getXhrChapters(mangaId: String): Document { - val xhrHeaders = headersBuilder().add("Content-Type: application/x-www-form-urlencoded; charset=UTF-8") - .build() - val body = RequestBody.create(null, "action=tw_ajax&type=list_chap&id=$mangaId") - return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, body)).execute().asJsoup() - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val dataIdSelector = "input[id^=id_post]" - - return getXhrChapters(document.select(dataIdSelector).attr("value")).select("option").map { chapterFromElement(it) }.reversed() - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - element.let { urlElement -> - chapter.setUrlWithoutDomain(urlElement.attr("value")) - chapter.name = urlElement.text() - } - chapter.date_upload = 0 - - return chapter - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select("h3.title").text() - description = document.select("div.desc-text > p").text() - thumbnail_url = document.select("div.books > div > img").attr("src") - author = document.select("div.info > div:nth-child(1) > a").attr("title") - genre = document.select("div.info > div:nth-child(2) > a").joinToString { it.text() } - status = document.select("div.info > div:nth-child(3) > span").text().let { - when { - it.contains("Ongoing") -> SManga.ONGOING - it.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - } - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select(".chapter_beta_content p img").forEachIndexed { index, element -> - add(Page(index, "", element.attr("src"))) - } - } - - override fun imageUrlRequest(page: Page) = throw Exception("Not used") - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - GenreFilter(getGenreList()) - ) - class GenreFilter(vals: Array<Pair<String, String>>) : UriPartFilter("Category", vals) - - private fun getGenreList() = arrayOf( - Pair("All", ""), - Pair("Action", "action"), - Pair("Adult", "adult"), - Pair("Adventure", "adventure"), - Pair("BL", "bl"), - Pair("Comedy", "comedy"), - Pair("Comic", "comic"), - Pair("Crime", "crime"), - Pair("Detective", "detective"), - Pair("Drama", "drama"), - Pair("Ecchi", "ecchi"), - Pair("Fantasy", "fantasy"), - Pair("Gender bender", "gender-bender"), - Pair("GL", "gl"), - Pair("Gossip", "gossip"), - Pair("Harem", "harem"), - Pair("HentaiVN.Net", "hentaivn"), - Pair("Historical", "historical"), - Pair("HoiHentai.Com", "hoihentai-com"), - Pair("Horror", "horror"), - Pair("Incest", "incest"), - Pair("Isekai", "isekai"), - Pair("Manhua", "manhua"), - Pair("Martial arts", "martial-arts"), - Pair("Mature", "mature"), - Pair("Mecha", "mecha"), - Pair("Medical", "medical"), - Pair("Monster/Tentacle", "monster-tentacle"), - Pair("Mystery", "mystery"), - Pair("Novel", "novel"), - Pair("Office Life", "office-life"), - Pair("One shot", "one-shot"), - Pair("Psychological", "psychological"), - Pair("Revenge", "revenge"), - Pair("Romance", "romance"), - Pair("School Life", "school-life"), - Pair("Sci Fi", "sci-fi"), - Pair("Seinen", "seinen"), - Pair("Shoujo", "shoujo"), - Pair("Shounen", "shounen"), - Pair("Slice of Life", "slice-of-life"), - Pair("Smut", "smut"), - Pair("Sports", "sports"), - Pair("Supernatural", "supernatural"), - Pair("Teenager", "teenager"), - Pair("Thriller", "thriller"), - Pair("Time Travel", "time-travel"), - Pair("Tragedy", "tragedy"), - Pair("Uncensored", "uncensored"), - Pair("Vampire", "vampire"), - Pair("Webtoon", "webtoon"), - Pair("Yaoi", "yaoi"), - Pair("Yuri", "yuri") - ) - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/en/manhwatime/AndroidManifest.xml b/src/en/manhwatime/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/manhwatime/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/manhwatime/build.gradle b/src/en/manhwatime/build.gradle deleted file mode 100644 index 3a5304900..000000000 --- a/src/en/manhwatime/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ManhwaTime' - pkgNameSuffix = 'en.manhwatime' - extClass = '.ManhwaTime' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/manhwatime/res/mipmap-hdpi/ic_launcher.png b/src/en/manhwatime/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index eb33c8993..000000000 Binary files a/src/en/manhwatime/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwatime/res/mipmap-mdpi/ic_launcher.png b/src/en/manhwatime/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index cfa5afe4e..000000000 Binary files a/src/en/manhwatime/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwatime/res/mipmap-xhdpi/ic_launcher.png b/src/en/manhwatime/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 76b3fdbad..000000000 Binary files a/src/en/manhwatime/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwatime/res/mipmap-xxhdpi/ic_launcher.png b/src/en/manhwatime/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a5c07e2e0..000000000 Binary files a/src/en/manhwatime/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwatime/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/manhwatime/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 01d0e3501..000000000 Binary files a/src/en/manhwatime/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manhwatime/res/web_hi_res_512.png b/src/en/manhwatime/res/web_hi_res_512.png deleted file mode 100644 index 3f289b34f..000000000 Binary files a/src/en/manhwatime/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/manhwatime/src/eu/kanade/tachiyomi/extension/en/manhwatime/ManhwaTime.kt b/src/en/manhwatime/src/eu/kanade/tachiyomi/extension/en/manhwatime/ManhwaTime.kt deleted file mode 100644 index f9b45556d..000000000 --- a/src/en/manhwatime/src/eu/kanade/tachiyomi/extension/en/manhwatime/ManhwaTime.kt +++ /dev/null @@ -1,132 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.manhwatime - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import java.text.SimpleDateFormat -import java.util.Locale - -class ManhwaTime : ParsedHttpSource() { - - override val name = "ManhwaTime" - - override val baseUrl = "https://manhwatime.xyz" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private fun Elements.imgAttr(): String? = this.firstOrNull()?.let { - if (it.hasAttr("data-src")) it.attr("abs:data-src") else it.attr("abs:src") - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/?status=&type=&order=Popular&title=", headers) - } - - override fun popularMangaSelector() = "div.post-show div.animepost" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.data > a").let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - thumbnail_url = element.select("img").imgAttr() - } - } - - // small catalog, can't tell what this should be at this point - override fun popularMangaNextPageSelector(): String? = null - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/?status=&type=&order=Latest+Update&title=", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl?s=$query", headers) - } - - override fun searchMangaSelector() = "div.animepost a" - - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.attr("title") - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.select("img").attr("abs:data-src") - } - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - with(document.select("div.infoanime")) { - thumbnail_url = select("img").imgAttr() - description = select("div.summary__content p, div[itemprop=description] p") - .filterNot { it.text().contains("Manhwa Synopsis", ignoreCase = true) } - .joinToString("\n\n") { it.text() } - genre = select("div.genre-info a").joinToString { it.text() } - author = select("span:contains(Author)").firstOrNull()?.ownText() - status = select("span:contains(Status)").firstOrNull()?.ownText().toStatus() - } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("Publishing", ignoreCase = true) -> SManga.ONGOING - this.contains("Complete", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "div#chapter_list li" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("span.lchx a").let { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - date_upload = simpleDateFormat.parse(element.select("span.date").text())?.time ?: 0 - } - } - - private val simpleDateFormat by lazy { SimpleDateFormat("MMM dd, yyyy", Locale.US) } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.reader-area img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/en/manmanga/AndroidManifest.xml b/src/en/manmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/manmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/manmanga/build.gradle b/src/en/manmanga/build.gradle deleted file mode 100644 index 0840e48c6..000000000 --- a/src/en/manmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Man Manga' - pkgNameSuffix = 'en.manmanga' - extClass = '.ManManga' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/manmanga/res/mipmap-hdpi/ic_launcher.png b/src/en/manmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 173efd8c0..000000000 Binary files a/src/en/manmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manmanga/res/mipmap-mdpi/ic_launcher.png b/src/en/manmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5046fdd98..000000000 Binary files a/src/en/manmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manmanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/manmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9e4af5bfa..000000000 Binary files a/src/en/manmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/manmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 163506098..000000000 Binary files a/src/en/manmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/manmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e31710844..000000000 Binary files a/src/en/manmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/manmanga/res/web_hi_res_512.png b/src/en/manmanga/res/web_hi_res_512.png deleted file mode 100644 index 2dafcbbfb..000000000 Binary files a/src/en/manmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/manmanga/src/eu/kanade/tachiyomi/extension/en/manmanga/ManManga.kt b/src/en/manmanga/src/eu/kanade/tachiyomi/extension/en/manmanga/ManManga.kt deleted file mode 100644 index a26c7a793..000000000 --- a/src/en/manmanga/src/eu/kanade/tachiyomi/extension/en/manmanga/ManManga.kt +++ /dev/null @@ -1,142 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.manmanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat - -class ManManga : ParsedHttpSource() { - override val name = "Man Manga" - - override val baseUrl = "https://m.manmanga.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - companion object { - val dateFormat by lazy { - SimpleDateFormat("MMM dd, yyyy") - } - } - - override fun popularMangaSelector() = "#scrollBox > #scrollContent > li > a" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/category?sort=hot&page=$page", headers) - - override fun latestUpdatesRequest(page: Int) = - GET("$baseUrl/category?sort=new&page=$page", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - GET("$baseUrl/search?keyword=$query&page=$page", headers) - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.select("div.text > h4").text().trim() - } - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.select("div.text > div.name > h4").text().trim() - } - - override fun popularMangaNextPageSelector() = "script:containsData(next_page_url):not(:containsData(false))" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val getThumbnailUrl = document.select(".bg-box .bg").attr("style") - - author = document.select(".author").text().replace("Author:", "").trim() - genre = document.select(".tags span").joinToString(", ") { - it.text().trim() - } - status = document.select(".type").text().replace("Status:", "").trim().let { - parseStatus(it) - } - description = document.select(".inner-text").text().trim() - thumbnail_url = getThumbnailUrl.substring(getThumbnailUrl.indexOf("https://"), getThumbnailUrl.indexOf("')")) - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "dl.chapter-list > dd > ul > li > a" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.attr("alt").trim() - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - if (document.select("ul.img-list > li.unloaded > img").toString().isNotEmpty()) { - document.select("ul.img-list > li.unloaded > img").forEach { - val imgUrl = it.attr("data-src") - pages.add(Page(pages.size, "", imgUrl)) - } - } else { - document.select("ul.img-list > li.loaded > img").forEach { - val imgUrl = it.attr("data-src") - pages.add(Page(pages.size, "", imgUrl)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/martialscans/AndroidManifest.xml b/src/en/martialscans/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/martialscans/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/martialscans/build.gradle b/src/en/martialscans/build.gradle deleted file mode 100644 index 3cb9518ff..000000000 --- a/src/en/martialscans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MartialScans' - pkgNameSuffix = 'en.martialscans' - extClass = '.MartialScans' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/martialscans/res/mipmap-hdpi/ic_launcher.png b/src/en/martialscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 82b577d64..000000000 Binary files a/src/en/martialscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/martialscans/res/mipmap-mdpi/ic_launcher.png b/src/en/martialscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index df728a9fd..000000000 Binary files a/src/en/martialscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/martialscans/res/mipmap-xhdpi/ic_launcher.png b/src/en/martialscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 65eb2d6b8..000000000 Binary files a/src/en/martialscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/martialscans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/martialscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7b750c6ac..000000000 Binary files a/src/en/martialscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/martialscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/martialscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2b1dd0d91..000000000 Binary files a/src/en/martialscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/martialscans/res/web_hi_res_512.png b/src/en/martialscans/res/web_hi_res_512.png deleted file mode 100644 index 4e50381e2..000000000 Binary files a/src/en/martialscans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/martialscans/src/eu/kanade/tachiyomi/extension/en/martialscans/MartialScans.kt b/src/en/martialscans/src/eu/kanade/tachiyomi/extension/en/martialscans/MartialScans.kt deleted file mode 100644 index 293c5a4f8..000000000 --- a/src/en/martialscans/src/eu/kanade/tachiyomi/extension/en/martialscans/MartialScans.kt +++ /dev/null @@ -1,107 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.martialscans - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class MartialScans : ParsedHttpSource() { - - override val name = "MartialScans" - - override val baseUrl = "https://martialscans.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0") - - // popular - override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics", headers) - - override fun popularMangaSelector() = "div.latestCards .row div" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = element.select("a").attr("abs:href").substringAfter(baseUrl) - manga.title = element.select("h2").text() - // to do -// manga.thumbnail_url = element.select("img.card-img").attr("abs:src") - return manga - } - - override fun popularMangaNextPageSelector(): String? = null - - // latest - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String? = null - - // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - - override fun searchMangaSelector(): String = throw Exception("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") - - // manga details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - thumbnail_url = document.select(".cover + div.center img").attr("abs:src") - title = document.select("h1").text() - author = document.select("h5:contains(Author) span").text() - artist = document.select("h5:contains(Artist) span").text() - // to do -// status = parseStatus(document.select("").text()) - description = document.select("p:contains(Description)").firstOrNull()?.ownText() - genre = document.select("p:contains(Tags) span").joinToString { it.text() } - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("ongoing", true) -> SManga.ONGOING - status.contains("completed", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapters - override fun chapterListSelector() = ".row div.col-xl-9.col-lg-12.col-md-12.col-sm-12.col-12 div:has(> a)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = element.select("h6").text() - // to do -// chapter.date_upload = element.select("").firstOrNull()?.text()?.let { parseChapterDate(it) } ?: 0 - - return chapter - } - - // pages - override fun pageListParse(document: Document): List<Page> { - return document.select( - ".block.center:has(img) img" - ).mapIndexed { i, element -> - Page(i, "", element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") -} diff --git a/src/en/merakiscans/AndroidManifest.xml b/src/en/merakiscans/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/merakiscans/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/merakiscans/build.gradle b/src/en/merakiscans/build.gradle deleted file mode 100644 index dc7286e57..000000000 --- a/src/en/merakiscans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MerakiScans' - pkgNameSuffix = 'en.merakiscans' - extClass = '.MerakiScans' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/merakiscans/res/mipmap-hdpi/ic_launcher.png b/src/en/merakiscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d9e000046..000000000 Binary files a/src/en/merakiscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/merakiscans/res/mipmap-mdpi/ic_launcher.png b/src/en/merakiscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3fb7b5a45..000000000 Binary files a/src/en/merakiscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/merakiscans/res/mipmap-xhdpi/ic_launcher.png b/src/en/merakiscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 46a64941a..000000000 Binary files a/src/en/merakiscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/merakiscans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/merakiscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fde3d9343..000000000 Binary files a/src/en/merakiscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/merakiscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/merakiscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a39eb6662..000000000 Binary files a/src/en/merakiscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/merakiscans/res/web_hi_res_512.png b/src/en/merakiscans/res/web_hi_res_512.png deleted file mode 100644 index 8141b8225..000000000 Binary files a/src/en/merakiscans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/merakiscans/src/eu/kanade/tachiyomi/extension/en/merakiscans/MerakiScans.kt b/src/en/merakiscans/src/eu/kanade/tachiyomi/extension/en/merakiscans/MerakiScans.kt deleted file mode 100644 index 7539eae29..000000000 --- a/src/en/merakiscans/src/eu/kanade/tachiyomi/extension/en/merakiscans/MerakiScans.kt +++ /dev/null @@ -1,140 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.merakiscans - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -class MerakiScans : ParsedHttpSource() { - override val name = "MerakiScans" - - override val baseUrl = "https://merakiscans.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - companion object { - val dateFormat by lazy { - SimpleDateFormat("MMM dd, yyyy", Locale.US) - } - } - - override fun popularMangaSelector() = "#all > #listitem > a" - - override fun latestUpdatesSelector() = "#mangalisthome > #mangalistitem > #mangaitem > #manganame > a" - - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/manga", headers) - - override fun latestUpdatesRequest(page: Int) = - GET(baseUrl, headers) - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.select("h1.title").text().trim() - } - - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.text().trim() - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - GET("$baseUrl/manga", headers) - - override fun searchMangaSelector() = popularMangaSelector() - - // This makes it so that if somebody searches for "views" it lists everything, also includes #'s. - private fun searchMangaSelector(query: String) = "#all > #listitem > a:contains($query)" - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - private fun searchMangaParse(response: Response, query: String): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(searchMangaSelector(query)).map { element -> - searchMangaFromElement(element) - } - - return MangasPage(mangas, false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val infoElement = document.select("#content2") - author = infoElement.select("#detail_list > li:nth-child(5)").text().replace("Author:", "").trim() - artist = infoElement.select("#detail_list > li:nth-child(7)").text().replace("Artist:", "").trim() - genre = infoElement.select("#detail_list > li:nth-child(11) > a").joinToString { it.text().trim() } - status = infoElement.select("#detail_list > li:nth-child(9)").text().replace("Status:", "").trim().let { - parseStatus(it) - } - description = infoElement.select("#detail_list > span").text().trim() - thumbnail_url = infoElement.select("#info > #image > #cover_img").attr("abs:src") - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "#chapter_table > tbody > #chapter-head" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("data-href")) - name = element.select("td:nth-child(1)").text().trim() - date_upload = element.select("td:nth-child(2)").text().trim().let { parseChapterDate(it) } - } - - private fun parseChapterDate(date: String): Long { - return try { - dateFormat.parse(date.replace(Regex("(st|nd|rd|th)"), ""))?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(document: Document): List<Page> { - val doc = document.toString() - val imgarray = doc.substringAfter("var images = [").substringBefore("];").split(",").map { it.replace("\"", "") } - val mangaslug = doc.substringAfter("var manga_slug = \"").substringBefore("\";") - val chapnum = doc.substringAfter("var viewschapter = \"").substringBefore("\";") - - return imgarray.mapIndexed { i, image -> - Page(i, "", "$baseUrl/manga/$mangaslug/$chapnum/$image") - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/multporn/AndroidManifest.xml b/src/en/multporn/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/multporn/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/multporn/build.gradle b/src/en/multporn/build.gradle deleted file mode 100644 index 035ee7772..000000000 --- a/src/en/multporn/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Multporn' - pkgNameSuffix = 'en.multporn' - extClass = '.Multporn' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" \ No newline at end of file diff --git a/src/en/multporn/res/mipmap-hdpi/ic_launcher.png b/src/en/multporn/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8d9c2e0a2..000000000 Binary files a/src/en/multporn/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/multporn/res/mipmap-mdpi/ic_launcher.png b/src/en/multporn/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b06e8d083..000000000 Binary files a/src/en/multporn/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/multporn/res/mipmap-xhdpi/ic_launcher.png b/src/en/multporn/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8bc6400ed..000000000 Binary files a/src/en/multporn/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/multporn/res/mipmap-xxhdpi/ic_launcher.png b/src/en/multporn/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index de286ccc3..000000000 Binary files a/src/en/multporn/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/multporn/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/multporn/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 01a24d3e9..000000000 Binary files a/src/en/multporn/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/multporn/res/web_hi_res_512.png b/src/en/multporn/res/web_hi_res_512.png deleted file mode 100644 index 3e7c022d8..000000000 Binary files a/src/en/multporn/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/multporn/src/eu/kanade/tachiyomi/extension/en/multporn/Multporn.kt b/src/en/multporn/src/eu/kanade/tachiyomi/extension/en/multporn/Multporn.kt deleted file mode 100644 index 7b13604c6..000000000 --- a/src/en/multporn/src/eu/kanade/tachiyomi/extension/en/multporn/Multporn.kt +++ /dev/null @@ -1,426 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.multporn - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonArray -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.MediaType -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import rx.schedulers.Schedulers -import java.util.Locale - -@Nsfw -class Multporn : ParsedHttpSource() { - - override val name = "Multporn" - override val lang: String = "en" - override val baseUrl = "https://multporn.net" - override val supportsLatest = true - - private val gson = Gson() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", HEADER_USER_AGENT) - .add("Content-Type", HEADER_CONTENT_TYPE) - - // Popular - - private fun buildPopularMangaRequest(page: Int, filters: FilterList = FilterList()): Request { - val body = FormBody.Builder() - .addEncoded("page", page.toString()) - .addEncoded("view_name", "top") - .addEncoded("view_display_id", "page") - - (if (filters.isEmpty()) getFilterList(POPULAR_DEFAULT_SORT_BY_FILTER_STATE) else filters).forEach { - when (it) { - is SortBySelectFilter -> body.addEncoded("sort_by", it.selected.uri) - is SortOrderSelectFilter -> body.addEncoded("sort_order", it.selected.uri) - is PopularTypeSelectFilter -> body.addEncoded("type", it.selected.uri) - else -> { } - } - } - - return POST("$baseUrl/views/ajax", headers, body.build()) - } - - override fun popularMangaRequest(page: Int) = buildPopularMangaRequest(page - 1) - - override fun popularMangaParse(response: Response): MangasPage { - val html = gson.fromJson<JsonArray>(response.body()!!.string()) - .last { it["command"].asString == "insert" }.asJsonObject["data"].asString - - return super.popularMangaParse( - response.newBuilder() - .body(ResponseBody.create(MediaType.parse("text/html; charset=UTF-8"), html)) - .build() - ) - } - - override fun popularMangaSelector() = ".masonry-item" - override fun popularMangaNextPageSelector() = ".pager-next a" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - url = element.select(".views-field-title a").attr("href") - title = element.select(".views-field-title").text() - thumbnail_url = element.select("img").attr("abs:src") - } - - // Latest - - private fun buildLatestMangaRequest(page: Int, filters: FilterList = FilterList()): Request { - val url = HttpUrl.parse("$baseUrl/new")!!.newBuilder() - .addQueryParameter("page", page.toString()) - - (if (filters.isEmpty()) getFilterList(LATEST_DEFAULT_SORT_BY_FILTER_STATE) else filters).forEach { - when (it) { - is SortBySelectFilter -> url.addQueryParameter("sort_by", it.selected.uri) - is SortOrderSelectFilter -> url.addQueryParameter("sort_order", it.selected.uri) - is LatestTypeSelectFilter -> url.addQueryParameter("type", it.selected.uri) - else -> { } - } - } - - return GET(url.toString(), headers) - } - - override fun latestUpdatesRequest(page: Int) = buildLatestMangaRequest(page - 1) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - // Search - - private fun textSearchFilterParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select("#content .col-1:contains(Views:),.col-2:contains(Views:)") - .map { popularMangaFromElement(it) } - - val hasNextPage = popularMangaNextPageSelector().let { - document.select(it).firstOrNull() - } != null - - return MangasPage(mangas, hasNextPage) - } - - private fun buildSearchMangaRequest(page: Int, query: String, filtersArg: FilterList = FilterList()): Request { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("page", page.toString()) - .addQueryParameter("views_fulltext", query) - - (if (filtersArg.isEmpty()) getFilterList(SEARCH_DEFAULT_SORT_BY_FILTER_STATE) else filtersArg).forEach { - when (it) { - is SortBySelectFilter -> url.addQueryParameter("sort_by", it.selected.uri) - is SearchTypeSelectFilter -> url.addQueryParameter("type", it.selected.uri) - else -> { } - } - } - - return GET(url.toString(), headers) - } - - private fun buildTextSearchFilterRequests(page: Int, filters: List<TextSearchFilter>): List<Request> { - return filters.map { - it.stateURIs.map { queryURI -> - GET("$baseUrl/${it.uri}/$queryURI?page=0,$page") - } - }.flatten() - } - - private fun squashMangasPageObservables(observables: List<Observable<MangasPage?>>): Observable<MangasPage> { - return Observable.from(observables) - .flatMap { it.observeOn(Schedulers.io()) } - .toList() - .map { it.filterNotNull() } - .map { pages -> - MangasPage( - pages.map { it.mangas }.flatten().distinctBy { it.url }, - pages.any { it.hasNextPage } - ) - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - - val sortByFilterType = filters.findInstance<SortBySelectFilter>()?.requestType ?: POPULAR_REQUEST_TYPE - val textSearchFilters = filters.filterIsInstance<TextSearchFilter>().filter { it.state.isNotBlank() } - - return when { - textSearchFilters.isNotEmpty() -> { - val requests = buildTextSearchFilterRequests(page - 1, textSearchFilters) - - squashMangasPageObservables( - requests.map { - client.newCall(it).asObservable().map { res -> - if (res.code() == 200) textSearchFilterParse(res) - else null - } - } - ) - } - query.isNotEmpty() || sortByFilterType == SEARCH_REQUEST_TYPE -> { - val request = buildSearchMangaRequest(page - 1, query, filters) - client.newCall(request).asObservableSuccess().map { searchMangaParse(it) } - } - sortByFilterType == LATEST_REQUEST_TYPE -> { - val request = buildLatestMangaRequest(page - 1, filters) - client.newCall(request).asObservableSuccess().map { latestUpdatesParse(it) } - } - else -> { - val request = buildPopularMangaRequest(page - 1, filters) - client.newCall(request).asObservableSuccess().map { popularMangaParse(it) } - } - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - - val sortByFilterType = filters.findInstance<SortBySelectFilter>()?.requestType ?: POPULAR_REQUEST_TYPE - - return when { - query.isNotEmpty() || sortByFilterType == SEARCH_REQUEST_TYPE -> buildSearchMangaRequest(page - 1, query, filters) - sortByFilterType == LATEST_REQUEST_TYPE -> buildLatestMangaRequest(page - 1, filters) - else -> buildPopularMangaRequest(page - 1, filters) - } - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - // Details - - private fun parseUnlabelledAuthorNames(document: Document): List<String> = listOf( - "field-name-field-author", - "field-name-field-authors-gr", - "field-name-field-img-group", - "field-name-field-hentai-img-group", - "field-name-field-rule-63-section" - ).map { document.select(".$it a").map { a -> a.text().trim() } }.flatten() - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - - title = document.select("h1#page-title").text() - - val infoMap = listOf( - "Section", - "Characters", - "Tags", - "Author" - ).map { - it to document.select(".field:has(.field-label:contains($it:)) .links a").map { t -> t.text().trim() } - }.toMap() - - artist = (infoMap.getValue("Author") + parseUnlabelledAuthorNames(document)) - .distinct().joinToString() - author = artist - - genre = listOf("Tags", "Section", "Characters") - .map { infoMap.getValue(it) }.flatten().distinct().joinToString() - - status = infoMap["Section"]?.firstOrNull { it == "Ongoings" }?.let { SManga.ONGOING } ?: SManga.COMPLETED - - val pageCount = document.select(".jb-image img").size - - description = infoMap - .filter { it.key in arrayOf("Section", "Characters") } - .filter { it.value.isNotEmpty() } - .map { "${it.key}:\n${it.value.joinToString()}" } - .let { - it + listOf( - "Pages:\n$pageCount" - ) - } - .joinToString("\n\n") - } - } - - // Chapters - - override fun chapterListSelector(): String = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - override fun chapterListParse(response: Response): List<SChapter> = throw UnsupportedOperationException("Not used") - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.just( - listOf( - SChapter.create().apply { - url = manga.url - name = "Chapter" - chapter_number = 1f - } - ) - ) - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select(".jb-image img").mapIndexed { i, image -> - Page(i, imageUrl = image.attr("abs:src").replace("/styles/juicebox_2k/public", "").substringBefore("?")) - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = getFilterList(POPULAR_DEFAULT_SORT_BY_FILTER_STATE) - - open class URIFilter(open val name: String, open val uri: String) - - class RequestTypeURIFilter( - val requestType: String, - override var name: String, - override val uri: String - ) : URIFilter(name, uri) - - open class URISelectFilter(name: String, open val filters: List<URIFilter>, state: Int = 0) : - Filter.Select<String>(name, filters.map { it.name }.toTypedArray(), state) { - open val selected: URIFilter - get() = filters[state] - } - - open class TypeSelectFilter(name: String, filters: List<URIFilter>) : URISelectFilter(name, filters) - - private class PopularTypeSelectFilter(filters: List<URIFilter>) : TypeSelectFilter("Popular Type", filters) - - private class LatestTypeSelectFilter(filters: List<URIFilter>) : TypeSelectFilter("Latest Type", filters) - - private class SearchTypeSelectFilter(filters: List<URIFilter>) : TypeSelectFilter("Search Type", filters) - - open class TextSearchFilter(name: String, val uri: String) : Filter.Text(name) { - val stateURIs: List<String> - get() { - return state.split(",").filter { it != "" }.map { - Regex("[^A-Za-z0-9]").replace(it, " ").trim() - .replace("\\s+".toRegex(), "_").toLowerCase(Locale.getDefault()) - }.distinct() - } - } - - private class SortBySelectFilter(override val filters: List<RequestTypeURIFilter>, state: Int) : - URISelectFilter( - "Sort By", - filters.map { filter -> - filter.let { it.name = "[${it.requestType}] ${it.name}"; it } - }, - state - ) { - val requestType: String - get() = filters[state].requestType - } - - private class SortOrderSelectFilter(filters: List<URIFilter>) : URISelectFilter("Order By", filters) - - private fun getFilterList(sortByFilterState: Int) = FilterList( - Filter.Header("Text search only works with Relevance and Author"), - SortBySelectFilter(getSortByFilters(), sortByFilterState), - Filter.Header("Order By only works with Popular and Latest"), - SortOrderSelectFilter(getSortOrderFilters()), - Filter.Header("Type filters apply based on selected Sort By option"), - PopularTypeSelectFilter(getPopularTypeFilters()), - LatestTypeSelectFilter(getLatestTypeFilters()), - SearchTypeSelectFilter(getSearchTypeFilters()), - Filter.Separator(), - Filter.Header("Filters below ignore text search and all options above"), - Filter.Header("Query must match title's non-special characters"), - Filter.Header("Separate queries with comma (,)"), - TextSearchFilter("Comic Tags", "category"), - TextSearchFilter("Comic Characters", "characters"), - TextSearchFilter("Comic Authors", "authors_comics"), - TextSearchFilter("Comic Sections", "comics"), - TextSearchFilter("Manga Categories", "category_hentai"), - TextSearchFilter("Manga Characters", "characters_hentai"), - TextSearchFilter("Manga Authors", "authors_hentai_comics"), - TextSearchFilter("Manga Sections", "hentai_manga"), - TextSearchFilter("Picture Authors", "authors_albums"), - TextSearchFilter("Picture Sections", "pictures"), - TextSearchFilter("Hentai Sections", "hentai"), - TextSearchFilter("Rule 63 Sections", "rule_63"), - TextSearchFilter("Gay Tags", "category_gay") - ) - - private fun getPopularTypeFilters() = listOf( - URIFilter("Comics", "1"), - URIFilter("Hentai Manga", "2"), - URIFilter("Cartoon Pictures", "3"), - URIFilter("Hentai Pictures", "4"), - URIFilter("Rule 63", "10"), - URIFilter("Author Albums", "11") - ) - - private fun getLatestTypeFilters() = listOf( - URIFilter("Comics", "1"), - URIFilter("Hentai Manga", "2"), - URIFilter("Cartoon Pictures", "3"), - URIFilter("Hentai Pictures", "4"), - URIFilter("Author Albums", "10") - ) - - private fun getSearchTypeFilters() = listOf( - URIFilter("Comics", "1"), - URIFilter("Hentai Manga", "2"), - URIFilter("Gay Comics", "3"), - URIFilter("Cartoon Pictures", "4"), - URIFilter("Hentai Pictures", "5"), - URIFilter("Rule 63", "11"), - URIFilter("Humor", "13") - ) - - private fun getSortByFilters() = listOf( - RequestTypeURIFilter(POPULAR_REQUEST_TYPE, "Total Views", "totalcount_1"), - RequestTypeURIFilter(POPULAR_REQUEST_TYPE, "Views Today", "daycount"), - RequestTypeURIFilter(POPULAR_REQUEST_TYPE, "Last Viewed", "timestamp"), - RequestTypeURIFilter(LATEST_REQUEST_TYPE, "Date Posted", "created"), - RequestTypeURIFilter(LATEST_REQUEST_TYPE, "Date Updated", "changed"), - RequestTypeURIFilter(SEARCH_REQUEST_TYPE, "Relevance", "search_api_relevance"), - RequestTypeURIFilter(SEARCH_REQUEST_TYPE, "Author", "author") - ) - - private fun getSortOrderFilters() = listOf( - URIFilter("Descending", "DESC"), - URIFilter("Ascending", "ASC") - ) - - private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T - - companion object { - - const val LATEST_DEFAULT_SORT_BY_FILTER_STATE = 3 - const val POPULAR_DEFAULT_SORT_BY_FILTER_STATE = 0 - const val SEARCH_DEFAULT_SORT_BY_FILTER_STATE = 5 - - const val LATEST_REQUEST_TYPE = "Latest" - const val POPULAR_REQUEST_TYPE = "Popular" - const val SEARCH_REQUEST_TYPE = "Search" - - const val HEADER_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36" - const val HEADER_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=UTF-8" - } -} diff --git a/src/en/myhentaicomics/AndroidManifest.xml b/src/en/myhentaicomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/myhentaicomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/myhentaicomics/build.gradle b/src/en/myhentaicomics/build.gradle deleted file mode 100644 index 56680e996..000000000 --- a/src/en/myhentaicomics/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MyHentaiComics' - pkgNameSuffix = 'en.myhentaicomics' - extClass = '.MyHentaiComics' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/myhentaicomics/res/mipmap-hdpi/ic_launcher.png b/src/en/myhentaicomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 16350c947..000000000 Binary files a/src/en/myhentaicomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaicomics/res/mipmap-mdpi/ic_launcher.png b/src/en/myhentaicomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 415d73fd2..000000000 Binary files a/src/en/myhentaicomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaicomics/res/mipmap-xhdpi/ic_launcher.png b/src/en/myhentaicomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 079706390..000000000 Binary files a/src/en/myhentaicomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaicomics/res/mipmap-xxhdpi/ic_launcher.png b/src/en/myhentaicomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 979c8e170..000000000 Binary files a/src/en/myhentaicomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaicomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/myhentaicomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e8bae1710..000000000 Binary files a/src/en/myhentaicomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaicomics/res/web_hi_res_512.png b/src/en/myhentaicomics/res/web_hi_res_512.png deleted file mode 100644 index 0dc13af62..000000000 Binary files a/src/en/myhentaicomics/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/myhentaicomics/src/eu/kanade/tachiyomi/extension/en/myhentaicomics/MyHentaiComics.kt b/src/en/myhentaicomics/src/eu/kanade/tachiyomi/extension/en/myhentaicomics/MyHentaiComics.kt deleted file mode 100644 index 81209e6d4..000000000 --- a/src/en/myhentaicomics/src/eu/kanade/tachiyomi/extension/en/myhentaicomics/MyHentaiComics.kt +++ /dev/null @@ -1,195 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.myhentaicomics - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -@Nsfw -class MyHentaiComics : ParsedHttpSource() { - - override val name = "MyHentaiComics" - - override val baseUrl = "https://myhentaicomics.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/index.php/tag/2402?page=$page", headers) - } - - override fun popularMangaSelector() = "li.g-item" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("h2").text() - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "a.ui-state-default span.ui-icon-seek-next" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/index.php/?page=$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/index.php/search?q=$query&page=$page", headers) - } else { - var url = baseUrl - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is GenreFilter -> url += filter.toUriPart() + "?page=$page" - } - } - GET(url, headers) - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("h2, p").text() - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val tags = document.select("div.g-description a").partition { tag -> - tag.text().startsWith("Artist: ") - } - return SManga.create().apply { - artist = tags.first.joinToString { it.text().substringAfter(" ") } - author = artist - genre = tags.second.joinToString { it.text() } - thumbnail_url = document.select("img.g-thumbnail").first().attr("abs:src").replace("/thumbs/", "/resizes/") - } - } - - // Chapters - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return Observable.just( - listOf( - SChapter.create().apply { - name = "Chapter" - url = manga.url - } - ) - ) - } - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - // recursively parse paginated pages - fun parsePage(document: Document) { - document.select("img.g-thumbnail").map { img -> - pages.add(Page(pages.size, "", img.attr("abs:src").replace("/thumbs/", "/resizes/"))) - } - document.select("ul.g-paginator a.ui-state-default:contains(Next)").firstOrNull()?.let { a -> - parsePage(client.newCall(GET(a.attr("abs:href"), headers)).execute().asJsoup()) - } - } - - parsePage(document) - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("Cannot combine search types!"), - Filter.Separator("-----------------"), - GenreFilter() - ) - - private class GenreFilter : UriPartFilter( - "Genres", - arrayOf( - Pair("<Choose a genre>", ""), - Pair("3D", "/index.php/tag/2403"), - Pair("Asian", "/index.php/tag/2404"), - Pair("Ass Expansion", "/index.php/tag/2405"), - Pair("BBW", "/index.php/tag/2406"), - Pair("Beastiality", "/index.php/tag/2407"), - Pair("Bisexual", "/index.php/tag/2408"), - Pair("Body Swap", "/index.php/tag/2410"), - Pair("Breast Expansion", "/index.php/tag/2413"), - Pair("Bukakke", "/index.php/tag/2412"), - Pair("Cheating", "/index.php/tag/2414"), - Pair("Crossdressing", "/index.php/tag/2415"), - Pair("Femdom", "/index.php/tag/2417"), - Pair("Furry", "/index.php/tag/2418"), - Pair("Futanari", "/index.php/tag/2419"), - Pair("Futanari On Male", "/index.php/tag/2430"), - Pair("Gangbang", "/index.php/tag/2421"), - Pair("Gay", "/index.php/tag/2422"), - Pair("Gender Bending", "/index.php/tag/2423"), - Pair("Giantess", "/index.php/tag/2424"), - Pair("Gloryhole", "/index.php/tag/2425"), - Pair("Hardcore", "/index.php/tag/2426"), - Pair("Harem", "/index.php/tag/2427"), - Pair("Incest", "/index.php/tag/2450"), - Pair("Interracial", "/index.php/tag/2409"), - Pair("Lactation", "/index.php/tag/2428"), - Pair("Lesbian", "/index.php/tag/3167"), - Pair("Milf", "/index.php/tag/2431"), - Pair("Mind Control & Hypnosis", "/index.php/tag/2432"), - Pair("Muscle Girl", "/index.php/tag/2434"), - Pair("Pegging", "/index.php/tag/2437"), - Pair("Pregnant", "/index.php/tag/2438"), - Pair("Rape", "/index.php/tag/2433"), - Pair("Strap-On", "/index.php/tag/2441"), - Pair("Superheroes", "/index.php/tag/2443"), - Pair("Tentacles", "/index.php/tag/2444") - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/en/myhentaigallery/AndroidManifest.xml b/src/en/myhentaigallery/AndroidManifest.xml deleted file mode 100644 index 6e4d8d6aa..000000000 --- a/src/en/myhentaigallery/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".en.myhentaigallery.MyHentaiGalleryUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="myhentaigallery.com" - android:pathPattern="/gallery/thumbnails/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/en/myhentaigallery/build.gradle b/src/en/myhentaigallery/build.gradle deleted file mode 100644 index 725f787d4..000000000 --- a/src/en/myhentaigallery/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MyHentaiGallery' - pkgNameSuffix = 'en.myhentaigallery' - extClass = '.MyHentaiGallery' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/myhentaigallery/res/mipmap-hdpi/ic_launcher.png b/src/en/myhentaigallery/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 74a06a5a3..000000000 Binary files a/src/en/myhentaigallery/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaigallery/res/mipmap-mdpi/ic_launcher.png b/src/en/myhentaigallery/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d5c02a06c..000000000 Binary files a/src/en/myhentaigallery/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaigallery/res/mipmap-xhdpi/ic_launcher.png b/src/en/myhentaigallery/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ca6ca7700..000000000 Binary files a/src/en/myhentaigallery/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaigallery/res/mipmap-xxhdpi/ic_launcher.png b/src/en/myhentaigallery/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1ad787673..000000000 Binary files a/src/en/myhentaigallery/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaigallery/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/myhentaigallery/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0b20d3506..000000000 Binary files a/src/en/myhentaigallery/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/myhentaigallery/res/web_hi_res_512.png b/src/en/myhentaigallery/res/web_hi_res_512.png deleted file mode 100644 index 55964b494..000000000 Binary files a/src/en/myhentaigallery/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/myhentaigallery/src/eu/kanade/tachiyomi/extension/en/myhentaigallery/MyHentaiGallery.kt b/src/en/myhentaigallery/src/eu/kanade/tachiyomi/extension/en/myhentaigallery/MyHentaiGallery.kt deleted file mode 100644 index 0f0ff80d5..000000000 --- a/src/en/myhentaigallery/src/eu/kanade/tachiyomi/extension/en/myhentaigallery/MyHentaiGallery.kt +++ /dev/null @@ -1,250 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.myhentaigallery - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -@Nsfw -class MyHentaiGallery : ParsedHttpSource() { - - override val name = "MyHentaiGallery" - - override val baseUrl = "https://myhentaigallery.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/gallery/category/2/$page", headers) - } - - override fun popularMangaSelector() = "div.comic-inner" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("h2").text() - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "li.next" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/gallery/$page") - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/gallery/thumbnails/$id", headers) - - private fun searchMangaByIdParse(response: Response, id: String): MangasPage { - val details = mangaDetailsParse(response) - details.url = "/gallery/thumbnails/$id" - return MangasPage(listOf(details), false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(searchMangaByIdRequest(id)) - .asObservableSuccess() - .map { response -> searchMangaByIdParse(response, id) } - } else { - super.fetchSearchManga(page, query, filters) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/search/$page?query=$query", headers) - } else { - val url = HttpUrl.parse("$baseUrl/gallery/category")!!.newBuilder() - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - url.addPathSegment(filter.toUriPart()) - } - } - } - url.addPathSegment("$page") - - GET(url.toString(), headers) - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return document.select("div.comic-header").let { info -> - SManga.create().apply { - title = info.select("h1").text() - genre = info.select("div:containsOwn(categories) a").joinToString { it.text() } - artist = info.select("div:containsOwn(artists) a").text() - thumbnail_url = info.select("div.comic-cover img").attr("abs:src") - description = info.select("div:containsOwn(groups) a").let { groups -> - if (groups.isNotEmpty()) "Groups: ${groups.joinToString { it.text() }}\n" else "" - } - description += info.select("div:containsOwn(parodies) a").let { groups -> - if (groups.isNotEmpty()) "Parodies: ${groups.joinToString { it.text() }}" else "" - } - } - } - } - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - return listOf( - SChapter.create().apply { - name = "Chapter" - url = response.request().url().toString().substringAfter(baseUrl) - } - ) - } - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.comic-thumb img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src").replace("/thumbnail/", "/original/")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - GenreFilter() - ) - - private class GenreFilter : UriPartFilter( - "Category", - arrayOf( - Pair("<select>", "---"), - Pair("3D Comic", "3"), - Pair("Ahegao", "2740"), - Pair("Anal", "2741"), - Pair("Asian", "4"), - Pair("Ass Expansion", "5"), - Pair("Aunt", "6"), - Pair("BBW", "7"), - Pair("Beastiality", "8"), - Pair("Bimbofication", "3430"), - Pair("Bisexual", "9"), - Pair("Black & Interracial", "10"), - Pair("Body Swap", "11"), - Pair("Bondage", "12"), - Pair("Breast Expansion", "13"), - Pair("Brother", "14"), - Pair("Bukakke", "15"), - Pair("Catgirl", "2742"), - Pair("Cheating", "16"), - Pair("Cousin", "17"), - Pair("Crossdressing", "18"), - Pair("Dad", "19"), - Pair("Daughter", "20"), - Pair("Dick Growth", "21"), - Pair("Ebony", "3533"), - Pair("Elf", "2744"), - Pair("Exhibitionism", "2745"), - Pair("Father", "22"), - Pair("Femdom", "23"), - Pair("Foot Fetish", "3253"), - Pair("Furry", "24"), - Pair("Futanari & Shemale & Dickgirl", "25"), - Pair("Futanari X Female", "3416"), - Pair("Futanari X Futanari", "3415"), - Pair("Futanari X Male", "26"), - Pair("Gangbang", "27"), - Pair("Gay & Yaoi", "28"), - Pair("Gender Bending", "29"), - Pair("Giantess", "30"), - Pair("Gloryhole", "31"), - Pair("Hairy Female", "3418"), - Pair("Hardcore", "36"), - Pair("Harem", "37"), - Pair("Incest", "38"), - Pair("Inseki", "3417"), - Pair("Kemonomimi", "3368"), - Pair("Lactation", "39"), - Pair("Lesbian & Yuri & Girls Only", "40"), - Pair("Milf", "41"), - Pair("Mind Break", "3419"), - Pair("Mind Control & Hypnosis", "42"), - Pair("Mom", "43"), - Pair("Mother", "44"), - Pair("Muscle Girl", "45"), - Pair("Muscle Growth", "46"), - Pair("Nephew", "47"), - Pair("Niece", "48"), - Pair("Orgy", "49"), - Pair("Pegging", "50"), - Pair("Possession", "51"), - Pair("Pregnant & Impregnation", "52"), - Pair("Rape", "53"), - Pair("Sister", "54"), - Pair("Solo", "2746"), - Pair("Son", "55"), - Pair("Spanking", "56"), - Pair("Stomach Bulge", "57"), - Pair("Strap-On", "58"), - Pair("Superheroes", "59"), - Pair("Tentacles", "60"), - Pair("Threesome", "61"), - Pair("Transformation", "62"), - Pair("Uncle", "63"), - Pair("Urination", "64"), - Pair("Vore", "65"), - Pair("Weight Gain", "66") - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - companion object { - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/en/myhentaigallery/src/eu/kanade/tachiyomi/extension/en/myhentaigallery/MyHentaiGalleryUrlActivity.kt b/src/en/myhentaigallery/src/eu/kanade/tachiyomi/extension/en/myhentaigallery/MyHentaiGalleryUrlActivity.kt deleted file mode 100644 index 8879c7dce..000000000 --- a/src/en/myhentaigallery/src/eu/kanade/tachiyomi/extension/en/myhentaigallery/MyHentaiGalleryUrlActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.myhentaigallery - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://myhentaigallery.com/gallery/thumbnails/xxxxx intents and redirects them to - * the main Tachiyomi process. - */ -class MyHentaiGalleryUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 2) { - val id = pathSegments[2] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${MyHentaiGallery.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("MyHentaiGalUrlActivity", e.toString()) - } - } else { - Log.e("MyHentaiGalUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/en/naniscans/AndroidManifest.xml b/src/en/naniscans/AndroidManifest.xml deleted file mode 100644 index 16997e31e..000000000 --- a/src/en/naniscans/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".en.naniscans.NaniScansUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="naniscans.com" - android:pathPattern="/titles/..*" - android:scheme="https" /> - <data - android:host="www.naniscans.com" - android:pathPattern="/titles/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/en/naniscans/build.gradle b/src/en/naniscans/build.gradle deleted file mode 100644 index 87805eb7f..000000000 --- a/src/en/naniscans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NANI? Scans' - pkgNameSuffix = 'en.naniscans' - extClass = '.NaniScans' - extVersionCode = 5 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/naniscans/res/mipmap-hdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9821a2161..000000000 Binary files a/src/en/naniscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/naniscans/res/mipmap-mdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 584f7fd82..000000000 Binary files a/src/en/naniscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/naniscans/res/mipmap-xhdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7b8855485..000000000 Binary files a/src/en/naniscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/naniscans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 21fe8337e..000000000 Binary files a/src/en/naniscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/naniscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f18241d6a..000000000 Binary files a/src/en/naniscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/naniscans/res/web_hi_res_512.png b/src/en/naniscans/res/web_hi_res_512.png deleted file mode 100644 index 4e3407e0b..000000000 Binary files a/src/en/naniscans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt deleted file mode 100644 index 3785cd56d..000000000 --- a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt +++ /dev/null @@ -1,181 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.naniscans - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import rx.Observable -import java.lang.UnsupportedOperationException -import java.text.SimpleDateFormat -import java.util.Locale - -class NaniScans : HttpSource() { - override val baseUrl = "https://naniscans.com" - override val lang = "en" - override val name = "NANI? Scans" - override val supportsLatest = true - override val versionId = 2 - - private val dateParser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()) - - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesParse(response: Response): MangasPage { - val titlesJson = JSONArray(response.body()!!.string()) - val mangaMap = mutableMapOf<Long, SManga>() - - for (i in 0 until titlesJson.length()) { - val manga = titlesJson.getJSONObject(i) - - if (manga.getString("type") != "Comic") - continue - - var date = manga.getString("updatedAt") - - if (date == "null") - date = "2018-04-10T17:38:56" - - mangaMap[dateParser.parse(date)!!.time] = getBareSManga(manga) - } - - return MangasPage(mangaMap.toSortedMap().values.toList().asReversed(), false) - } - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/titles") - - override fun popularMangaParse(response: Response): MangasPage { - val titlesJson = JSONArray(response.body()!!.string()) - val mangaList = mutableListOf<SManga>() - - for (i in 0 until titlesJson.length()) { - val manga = titlesJson.getJSONObject(i) - - if (manga.getString("type") != "Comic") - continue - - mangaList.add(getBareSManga(manga)) - } - - return MangasPage(mangaList, false) - } - - override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/api/titles/search?term=$query") - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = client.newCall(chapterListRequest(manga)).asObservableSuccess().map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/titles/${manga.url}") - - override fun mangaDetailsParse(response: Response): SManga { - val titleJson = JSONObject(response.body()!!.string()) - - if (titleJson.getString("type") != "Comic") - throw UnsupportedOperationException("Tachiyomi only supports Comics.") - - return SManga.create().apply { - title = titleJson.getString("name") - artist = titleJson.getString("artist") - author = titleJson.getString("author") - description = titleJson.getString("synopsis") - status = getStatus(titleJson.getString("status")) - thumbnail_url = "$baseUrl${titleJson.getString("coverUrl")}" - genre = titleJson.getJSONArray("tags").join(", ").replace("\"", "") - url = titleJson.getString("id") - } - } - - override fun chapterListRequest(manga: SManga) = GET("$baseUrl/api/titles/${manga.url}") - - override fun chapterListParse(response: Response): List<SChapter> { - val titleJson = JSONObject(response.body()!!.string()) - - if (titleJson.getString("type") != "Comic") - throw UnsupportedOperationException("Tachiyomi only supports Comics.") - - val chaptersJson = titleJson.getJSONArray("chapters") - val chaptersList = mutableListOf<SChapter>() - - for (i in 0 until chaptersJson.length()) { - val chapter = chaptersJson.getJSONObject(i) - - chaptersList.add( - SChapter.create().apply { - chapter_number = chapter.get("number").toString().toFloat() - name = getChapterTitle(chapter) - date_upload = dateParser.parse(chapter.getString("releaseDate"))!!.time - url = "${titleJson.getString("id")}_${chapter.getString("id")}" - } - ) - } - - return chaptersList - } - - override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/api/chapters/${chapter.url.substring(37, 73)}") - - override fun pageListParse(response: Response): List<Page> { - val jsonObject = JSONObject(response.body()!!.string()) - - val pagesJson = jsonObject.getJSONArray("pages") - val pagesList = mutableListOf<Page>() - - for (i in 0 until pagesJson.length()) { - val item = pagesJson.getJSONObject(i) - - pagesList.add(Page(item.getInt("number"), "", "$baseUrl${item.getString("pageUrl")}")) - } - - return pagesList - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not Used.") - - private fun getStatus(status: String): Int = when (status) { - "Ongoing" -> SManga.ONGOING - "Completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun getChapterTitle(chapter: JSONObject): String { - val chapterName = mutableListOf<String>() - - if (chapter.getString("volume") != "null") { - chapterName.add("Vol." + chapter.getString("volume")) - } - - if (chapter.getString("number") != "null") { - chapterName.add("Ch." + chapter.getString("number")) - } - - if (chapter.getString("name") != "null") { - if (chapterName.isNotEmpty()) { - chapterName.add("-") - } - - chapterName.add(chapter.getString("name")) - } - - if (chapterName.isEmpty()) { - chapterName.add("Oneshot") - } - - return chapterName.joinToString(" ") - } - - private fun getBareSManga(manga: JSONObject): SManga = SManga.create().apply { - title = manga.getString("name") - thumbnail_url = "$baseUrl${manga.getString("coverUrl")}" - url = manga.getString("id") - } -} diff --git a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScansUrlActivity.kt b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScansUrlActivity.kt deleted file mode 100644 index f5b400bf0..000000000 --- a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScansUrlActivity.kt +++ /dev/null @@ -1,35 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.naniscans - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -class NaniScansUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val titleid = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - - putExtra("query", titleid) - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("NaniScansUrlActivity", e.toString()) - } - } else { - Log.e("NaniScansUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/en/nhentaicom/AndroidManifest.xml b/src/en/nhentaicom/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/nhentaicom/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/nhentaicom/build.gradle b/src/en/nhentaicom/build.gradle deleted file mode 100644 index f0180d800..000000000 --- a/src/en/nhentaicom/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'nHentai.com (unoriginal)' - pkgNameSuffix = 'en.nhentai.com' - extClass = '.NHentaiCom' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/nhentaicom/res/mipmap-hdpi/ic_launcher.png b/src/en/nhentaicom/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 72dc38e95..000000000 Binary files a/src/en/nhentaicom/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nhentaicom/res/mipmap-mdpi/ic_launcher.png b/src/en/nhentaicom/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index dce2ccade..000000000 Binary files a/src/en/nhentaicom/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nhentaicom/res/mipmap-xhdpi/ic_launcher.png b/src/en/nhentaicom/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 52022db8e..000000000 Binary files a/src/en/nhentaicom/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nhentaicom/res/mipmap-xxhdpi/ic_launcher.png b/src/en/nhentaicom/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 10f15cc83..000000000 Binary files a/src/en/nhentaicom/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nhentaicom/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/nhentaicom/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 490949e05..000000000 Binary files a/src/en/nhentaicom/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nhentaicom/res/web_hi_res_512.png b/src/en/nhentaicom/res/web_hi_res_512.png deleted file mode 100644 index f3e4f213f..000000000 Binary files a/src/en/nhentaicom/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/nhentaicom/src/eu/kanade/tachiyomi/extension/en/nhentai/com/NHentaiCom.kt b/src/en/nhentaicom/src/eu/kanade/tachiyomi/extension/en/nhentai/com/NHentaiCom.kt deleted file mode 100644 index acf4bf0a2..000000000 --- a/src/en/nhentaicom/src/eu/kanade/tachiyomi/extension/en/nhentai/com/NHentaiCom.kt +++ /dev/null @@ -1,201 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.nhentai.com - -import android.app.Application -import android.content.SharedPreferences -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -@Nsfw -class NHentaiCom : HttpSource() { - - override val name = "nHentai.com (unoriginal)" - - override val baseUrl = "https://nhentai.com" - - override val lang = "en" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - val mangas = jsonObject["data"].asJsonArray.map { json -> - SManga.create().apply { - title = json["title"].string - thumbnail_url = json["image_url"].string - url = json["slug"].string - } - } - - return MangasPage(mangas, jsonObject["current_page"].int < jsonObject["last_page"].int) - } - private fun getMangaUrl(url: String): String { - return HttpUrl.parse(url)!!.newBuilder() - .setQueryParameter("nsfw", "false").toString() - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET(getMangaUrl("$baseUrl/api/comics?page=$page&q=&sort=popularity&order=desc&duration=week"), headers) - } - - override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(getMangaUrl("$baseUrl/api/comics?page=$page&q=&sort=uploaded_at&order=desc&duration=day"), headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/api/comics")!!.newBuilder() - .addQueryParameter("per_page", "18") - .addQueryParameter("page", page.toString()) - .addQueryParameter("order", "desc") - .addQueryParameter("q", query) - .addQueryParameter("nsfw", "false") - - filters.forEach { filter -> - when (filter) { - is SortFilter -> url.addQueryParameter("sort", filter.toUriPart()) - is DurationFilter -> url.addQueryParameter("duration", filter.toUriPart()) - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Details - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = - client.newCall(apiMangaDetailsRequest(manga)).asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET(getMangaUrl("$baseUrl/en/webtoon/${manga.url}"), headers) - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${manga.url}"), headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - return SManga.create().apply { - description = jsonObject["description"].nullString - status = jsonObject["status"].nullString.toStatus() - thumbnail_url = jsonObject["image_url"].nullString - genre = try { jsonObject["tags"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } - artist = try { jsonObject["artists"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } - author = try { jsonObject["authors"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("complete", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return Observable.just( - listOf( - SChapter.create().apply { - name = "chapter" - url = manga.url - } - ) - ) - } - - override fun chapterListRequest(manga: SManga): Request = throw Exception("not used") - - override fun chapterListParse(response: Response): List<SChapter> = throw UnsupportedOperationException("Not used") - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${chapter.url}/images"), headers) - } - - override fun pageListParse(response: Response): List<Page> { - return gson.fromJson<JsonObject>(response.body()!!.string())["images"].asJsonArray.mapIndexed { i, json -> - Page(i, "", json["source_url"].string) - } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - DurationFilter(getDurationList()), - SortFilter(getSortList()) - ) - - private class DurationFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Duration", pairs) - - private class SortFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Sorted by", pairs) - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private fun getDurationList() = arrayOf( - Pair("All time", "all"), - Pair("Year", "year"), - Pair("Month", "month"), - Pair("Week", "week"), - Pair("Day", "day") - ) - - private fun getSortList() = arrayOf( - Pair("Popularity", "popularity"), - Pair("Date", "uploaded_at") - ) -} diff --git a/src/en/nineanime/AndroidManifest.xml b/src/en/nineanime/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/nineanime/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/nineanime/build.gradle b/src/en/nineanime/build.gradle deleted file mode 100644 index 1eff4b251..000000000 --- a/src/en/nineanime/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NineAnime' - pkgNameSuffix = 'en.nineanime' - extClass = '.NineAnime' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/nineanime/res/mipmap-hdpi/ic_launcher.png b/src/en/nineanime/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c491a3010..000000000 Binary files a/src/en/nineanime/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nineanime/res/mipmap-mdpi/ic_launcher.png b/src/en/nineanime/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 548bea1ff..000000000 Binary files a/src/en/nineanime/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nineanime/res/mipmap-xhdpi/ic_launcher.png b/src/en/nineanime/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d091762f1..000000000 Binary files a/src/en/nineanime/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nineanime/res/mipmap-xxhdpi/ic_launcher.png b/src/en/nineanime/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fb74a189e..000000000 Binary files a/src/en/nineanime/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nineanime/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/nineanime/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 619d868f9..000000000 Binary files a/src/en/nineanime/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nineanime/res/web_hi_res_512.png b/src/en/nineanime/res/web_hi_res_512.png deleted file mode 100644 index cfd1f6269..000000000 Binary files a/src/en/nineanime/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/nineanime/src/eu/kanade/tachiyomi/extension/en/nineanime/NineAnime.kt b/src/en/nineanime/src/eu/kanade/tachiyomi/extension/en/nineanime/NineAnime.kt deleted file mode 100644 index 9c19c0832..000000000 --- a/src/en/nineanime/src/eu/kanade/tachiyomi/extension/en/nineanime/NineAnime.kt +++ /dev/null @@ -1,290 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.nineanime - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class NineAnime : ParsedHttpSource() { - - override val name = "NineAnime" - - override val baseUrl = "https://www.nineanime.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .followRedirects(true) - .build() - - // not necessary for normal usage but added in an attempt to fix usage with VPN (see #3476) - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) Gecko/20100101 Firefox/77") - .add("Accept-Language", "en-US,en;q=0.5") - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/category/index_$page.html?sort=views", headers) - } - - override fun popularMangaSelector() = "div.post" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("p.title a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "a.next" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/category/index_$page.html?sort=updated", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/search/?name=$query&page=$page.html", headers) - } else { - var url = "$baseUrl/category/" - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is GenreFilter -> url += filter.toUriPart() + "_$page.html" - } - } - GET(url, headers) - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - with(document.select("div.manga-detailtop")) { - thumbnail_url = select("img.detail-cover").attr("abs:src") - author = select("span:contains(Author) + a").joinToString { it.text() } - artist = select("span:contains(Artist) + a").joinToString { it.text() } - status = when (select("p:has(span:contains(Status))").firstOrNull()?.ownText()) { - "Ongoing" -> SManga.ONGOING - "Completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - with(document.select("div.manga-detailmiddle")) { - genre = select("p:has(span:contains(Genre)) a").joinToString { it.text() } - description = select("p.mobile-none").text() - } - } - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + "${manga.url}?waring=1", headers) - } - - override fun chapterListSelector() = "ul.detail-chlist li" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { - name = it.select("span").firstOrNull()?.text() ?: it.text() - setUrlWithoutDomain(it.attr("href")) - } - date_upload = element.select("span.time").text().toDate() - } - } - - private fun String.toDate(): Long { - return try { - if (this.contains("ago")) { - val split = this.split(" ") - val cal = Calendar.getInstance() - when { - split[1].contains("minute") -> cal.apply { add(Calendar.MINUTE, split[0].toInt()) }.timeInMillis - split[1].contains("hour") -> cal.apply { add(Calendar.HOUR, split[0].toInt()) }.timeInMillis - else -> 0 - } - } else { - SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH).parse(this)?.time ?: 0L - } - } catch (_: ParseException) { - 0 - } - } - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - val pageListHeaders = headersBuilder().add("Referer", "$baseUrl/manga/").build() - return GET(baseUrl + chapter.url, pageListHeaders) - } - - override fun pageListParse(document: Document): List<Page> { - val script = document.select("script:containsData(all_imgs_url)").firstOrNull()?.data() - ?: throw Exception("all_imgsurl not found") - return Regex(""""(http.*)",""").findAll(script).mapIndexed { i, mr -> - Page(i, "", mr.groupValues[1]) - }.toList() - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("Note: ignored if using text search!"), - Filter.Separator("-----------------"), - GenreFilter() - ) - - private class GenreFilter : UriPartFilter( - "Genres", - arrayOf( - Pair("All", "All"), - Pair("4-Koma", "4-Koma"), - Pair("Action", "Action"), - Pair("Adaptation", "Adaptation"), - Pair("Adult", "Adult"), - Pair("Adventure", "Adventure"), - Pair("Aliens", "Aliens"), - Pair("All", "category"), - Pair("Animals", "Animals"), - Pair("Anthology", "Anthology"), - Pair("Award Winning", "Award+Winning"), - Pair("Comedy", "Comedy"), - Pair("Cooking", "Cooking"), - Pair("Crime", "Crime"), - Pair("Crossdressing", "Crossdressing"), - Pair("Delinquents", "Delinquents"), - Pair("Demons", "Demons"), - Pair("Doujinshi", "Doujinshi"), - Pair("Drama", "Drama"), - Pair("Ecchi", "Ecchi"), - Pair("Fantasy", "Fantasy"), - Pair("Food", "Food"), - Pair("Full Color", "Full+color"), - Pair("Game", "Game"), - Pair("Gender Bender", "Gender+Bender"), - Pair("Genderswap", "Genderswap"), - Pair("Ghosts", "Ghosts"), - Pair("Gossip", "Gossip"), - Pair("Gyaru", "Gyaru"), - Pair("Harem", "Harem"), - Pair("Hentai", "Hentai"), - Pair("Historical", "Historical"), - Pair("Horror", "Horror"), - Pair("Incest", "Incest"), - Pair("Isekai", "Isekai"), - Pair("Josei", "Josei"), - Pair("Kids", "Kids"), - Pair("Loli", "Loli"), - Pair("Long Strip", "Long+strip"), - Pair("Mafia", "Mafia"), - Pair("Magic", "Magic"), - Pair("Magical Girls", "Magical+Girls"), - Pair("Manga", "Manga"), - Pair("Manhua", "Manhua"), - Pair("Manhwa", "Manhwa"), - Pair("Martial Arts", "Martial+Arts"), - Pair("Mature", "Mature"), - Pair("Mecha", "Mecha"), - Pair("Medical", "Medical"), - Pair("Military", "Military"), - Pair("Monster Girls", "Monster+girls"), - Pair("Monsters", "Monsters"), - Pair("Music", "Music"), - Pair("Mystery", "Mystery"), - Pair("N/A", "N%2Fa"), - Pair("Ninja", "Ninja"), - Pair("None", "None"), - Pair("Office Workers", "Office+workers"), - Pair("Official Colored", "Official+colored"), - Pair("One Shot", "One+Shot"), - Pair("Oneshot", "Oneshot"), - Pair("Parody", "Parody"), - Pair("Philosophical", "Philosophical"), - Pair("Police", "Police"), - Pair("Post Apocalyptic", "Post+apocalyptic"), - Pair("Psychological", "Psychological"), - Pair("Reincarnation", "Reincarnation"), - Pair("Reverse Harem", "Reverse+harem"), - Pair("Romance", "Romance"), - Pair("Samurai", "Samurai"), - Pair("School Life", "School+Life"), - Pair("Sci Fi", "sci+fi"), - Pair("Sci-Fi", "Sci-fi"), - Pair("Seinen", "Seinen"), - Pair("Shota", "Shota"), - Pair("Shotacon", "Shotacon"), - Pair("Shoujo", "Shoujo"), - Pair("Shoujo Ai", "Shoujo+Ai"), - Pair("Shounen", "Shounen"), - Pair("Shounen Ai", "Shounen+Ai"), - Pair("Slice Of Life", "Slice+Of+Life"), - Pair("Smut", "Smut"), - Pair("Sports", "Sports"), - Pair("Super Power", "Super+power"), - Pair("Superhero", "Superhero"), - Pair("Supernatural", "Supernatural"), - Pair("Survival", "Survival"), - Pair("Thriller", "Thriller"), - Pair("Time Travel", "Time+travel"), - Pair("Toomics", "Toomics"), - Pair("Tragedy", "Tragedy"), - Pair("Uncategorized", "Uncategorized"), - Pair("User Created", "User+created"), - Pair("Vampire", "Vampire"), - Pair("Vampires", "Vampires"), - Pair("Video Games", "Video+games"), - Pair("Virtual Reality", "Virtual+reality"), - Pair("Web Comic", "Web+comic"), - Pair("Webtoon", "Webtoon"), - Pair("Webtoons", "Webtoons"), - Pair("Wuxia", "Wuxia"), - Pair("Yaoi", "Yaoi"), - Pair("Yuri", "Yuri"), - Pair("Zombies", "Zombies"), - Pair("[No Chapters]", "%5Bno+chapters%5D") - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/en/nuxscans/AndroidManifest.xml b/src/en/nuxscans/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/nuxscans/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/nuxscans/build.gradle b/src/en/nuxscans/build.gradle deleted file mode 100644 index a3a71e39f..000000000 --- a/src/en/nuxscans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Nux Scans' - pkgNameSuffix = 'en.nuxscans' - extClass = '.NuxScans' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/nuxscans/res/mipmap-hdpi/ic_launcher.png b/src/en/nuxscans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1da8ef62a..000000000 Binary files a/src/en/nuxscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nuxscans/res/mipmap-mdpi/ic_launcher.png b/src/en/nuxscans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5d44e0437..000000000 Binary files a/src/en/nuxscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nuxscans/res/mipmap-xhdpi/ic_launcher.png b/src/en/nuxscans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 38fa8c74f..000000000 Binary files a/src/en/nuxscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nuxscans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/nuxscans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b404d6028..000000000 Binary files a/src/en/nuxscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nuxscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/nuxscans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3a93452a3..000000000 Binary files a/src/en/nuxscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nuxscans/res/web_hi_res_512.png b/src/en/nuxscans/res/web_hi_res_512.png deleted file mode 100644 index 02ec3d4d7..000000000 Binary files a/src/en/nuxscans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/nuxscans/src/eu/kanade/tachiyomi/extension/en/nuxscans/NuxScans.kt b/src/en/nuxscans/src/eu/kanade/tachiyomi/extension/en/nuxscans/NuxScans.kt deleted file mode 100644 index bdce0adf7..000000000 --- a/src/en/nuxscans/src/eu/kanade/tachiyomi/extension/en/nuxscans/NuxScans.kt +++ /dev/null @@ -1,110 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.nuxscans - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class NuxScans : ParsedHttpSource() { - - override val name = "Nux Scans" - override val baseUrl = "https://nuxscans.blogspot.com" - val baseUrl2 = "https://nuxscans-comics.blogspot.com" - override val lang = "en" - override val supportsLatest = false - override val client: OkHttpClient = network.cloudflareClient - - // popular - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl2) - } - - override fun popularMangaSelector() = "#Blog1 .hfeed .hentry .post-content" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select(".post-image-link img").attr("src") - manga.url = element.select(".post-info .post-title a").attr("href").substringAfter(baseUrl2) - manga.title = element.select(".post-info .post-tag").text() - return manga - } - - override fun popularMangaNextPageSelector(): String? = null - - // latest - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException() - - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException() - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException() - - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException() - - // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException() - - override fun searchMangaSelector(): String = throw UnsupportedOperationException() - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException() - - override fun searchMangaNextPageSelector(): String? = throw UnsupportedOperationException() - - // manga details - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl2 + manga.url) - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - thumbnail_url = document.select("a#unclick").attr("href") - title = document.select("div.post-header .post-tag").text() - description = document.select(".gridnux .column1 .text-overflow").joinToString("\n") { it.text() } - } - - private fun parseStatus(element: String): Int = when { - element.toLowerCase().contains("ongoing") -> SManga.ONGOING - element.toLowerCase().contains("completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapters - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl2 + manga.url) - } - - override fun chapterListSelector() = ".column1 .text-truncate a" - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = element.select("p").text() - return chapter - } - - // pages - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - var i = 0 - document.select(".post-content .separator a img").forEach { element -> - val url = element.attr("src") - i++ - if (url.isNotEmpty()) { - pages.add(Page(i, "", url)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" -} diff --git a/src/en/nyahentai/AndroidManifest.xml b/src/en/nyahentai/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/nyahentai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/nyahentai/build.gradle b/src/en/nyahentai/build.gradle deleted file mode 100644 index 601654e64..000000000 --- a/src/en/nyahentai/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NyaHentai' - pkgNameSuffix = 'en.nyahentai' - extClass = '.NyaHentai' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/nyahentai/res/mipmap-hdpi/ic_launcher.png b/src/en/nyahentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3ebb59850..000000000 Binary files a/src/en/nyahentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nyahentai/res/mipmap-mdpi/ic_launcher.png b/src/en/nyahentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4b9f6328e..000000000 Binary files a/src/en/nyahentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nyahentai/res/mipmap-xhdpi/ic_launcher.png b/src/en/nyahentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bd8698865..000000000 Binary files a/src/en/nyahentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nyahentai/res/mipmap-xxhdpi/ic_launcher.png b/src/en/nyahentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5c0838411..000000000 Binary files a/src/en/nyahentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nyahentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/nyahentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ffebb2fc3..000000000 Binary files a/src/en/nyahentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/nyahentai/res/web_hi_res_512.png b/src/en/nyahentai/res/web_hi_res_512.png deleted file mode 100644 index a85a2f743..000000000 Binary files a/src/en/nyahentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/nyahentai/src/eu/kanade/tachiyomi/extension/en/nyahentai/NyaHentai.kt b/src/en/nyahentai/src/eu/kanade/tachiyomi/extension/en/nyahentai/NyaHentai.kt deleted file mode 100644 index ef55bb0da..000000000 --- a/src/en/nyahentai/src/eu/kanade/tachiyomi/extension/en/nyahentai/NyaHentai.kt +++ /dev/null @@ -1,211 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.nyahentai - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -@Nsfw -class NyaHentai : ParsedHttpSource() { - companion object { - const val TAG = "NyaHentai" - } - - override val name = "NyaHentai (en)" - - override val baseUrl = "https://nyahentai.com" - - val languageUrl = "$baseUrl/language/english" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun latestUpdatesSelector() = "div.container div.gallery a" - - override fun latestUpdatesRequest(page: Int): Request { - return if (page == 1) { - GET(languageUrl, headers) - } else { - GET("$languageUrl/page/$page", headers) - } - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("div.caption").text() - manga.thumbnail_url = element.select("img.lazyload").attr("abs:data-src") - - return manga - } - - override fun latestUpdatesNextPageSelector() = "section.pagination a[rel=next]" - - private fun parseTAG(tag: String): String = tag.replace("\\((.*)\\)".toRegex(), "").trim() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div#bigcontainer.container") - val manga = SManga.create() - val genres = mutableListOf<String>() - - infoElement.select("div.tag-container:contains(Tags) a").forEach { element -> - val genre = parseTAG(element.text()) - genres.add(genre) - } - - manga.title = infoElement.select("h1").text() - manga.author = "" - manga.artist = parseTAG(infoElement.select("div.tag-container:contains(Artists) a").text()) - manga.status = SManga.COMPLETED - manga.genre = genres.joinToString(", ") - manga.thumbnail_url = infoElement.select("div#cover a img.lazyload").attr("abs:data-src") - - manga.description = getDesc(document) - - return manga - } - - private fun getDesc(document: Document): String { - val infoElement = document.select("div#bigcontainer.container") - - val pages = - infoElement.select("div#info > div:contains(pages)")?.text()?.replace(" pages", "") - - val multiDescriptions = listOf( - "Parodies", - "Characters", - "Groups", - "Languages", - "Categories" - ).map { - it to infoElement.select("div.tag-container:contains($it) a") - .map { v -> parseTAG(v.text()) } - } - .filter { !it.second.isNullOrEmpty() } - .map { "${it.first}: ${it.second.joinToString()}" } - - val descriptions = listOf( - multiDescriptions.joinToString("\n\n"), - pages?.let { "Pages: $it" } - ) - - return descriptions.joinToString("\n\n") - } - - override fun chapterListParse(response: Response) = with(response.asJsoup()) { - listOf( - SChapter.create().apply { - name = "Single Chapter" - setUrlWithoutDomain(response.request().url().toString()) - } - ) - } - - override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl${chapter.url}list/1/") - - override fun chapterListSelector(): String = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = - throw UnsupportedOperationException("Not used") - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - val imageUrl = document.select(".container img.current-img").attr("abs:src") - - val idRegex = "(.*)/galleries\\/(\\d+)\\/(\\d*)\\.(\\w+)".toRegex() - val match = idRegex.find(imageUrl) - - val base = match?.groups?.get(1)?.value - val id = match?.groups?.get(2)?.value - val ext = match?.groups?.get(4)?.value - - val total: Int = (document.select("#pagination-page-top .num-pages").text()).toInt() - - for (i in 1..total) { - pages.add(Page(i, "", "$base/galleries/$id/$i.$ext")) - } - - return pages - } - - override fun imageUrlParse(document: Document): String = - throw UnsupportedOperationException("Not used") - - override fun popularMangaRequest(page: Int): Request = - GET("$languageUrl/popular/page/$page", headers) - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - private lateinit var tagUrl: String - - // TODO: Additional filter options, specifically the type[] parameter - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // todo: remove "english" from the search query in the future - var url = "$baseUrl/search/q_$query english/page/$page" - - if (query.isBlank()) { - filters.forEach { filter -> - when (filter) { - is Tag -> { - url = if (page == 1) { - "$baseUrl/tag/${filter.state}&type[]=3" // "Contents" tag - } else { - "$tagUrl/page/$page" - } - } - } - } - } - - return GET(url, headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - return if (response.request().url().toString().contains("tag?")) { - response.asJsoup().select("table.table tbody tr a:first-of-type").attr("abs:href").let { - if (it.isNotEmpty()) { - tagUrl = it - super.searchMangaParse(client.newCall(GET(tagUrl, headers)).execute()) - } else { - MangasPage(emptyList(), false) - } - } - } else { - super.searchMangaParse(response) - } - } - - override fun searchMangaSelector() = latestUpdatesSelector() - - override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - Tag("Tag") - ) - - private class Tag(name: String) : Filter.Text(name) -} diff --git a/src/en/oglaf/AndroidManifest.xml b/src/en/oglaf/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/oglaf/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/oglaf/build.gradle b/src/en/oglaf/build.gradle deleted file mode 100644 index ae16ef29c..000000000 --- a/src/en/oglaf/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Oglaf' - pkgNameSuffix = 'en.oglaf' - extClass = '.Oglaf' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/oglaf/res/mipmap-hdpi/ic_launcher.png b/src/en/oglaf/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 609885895..000000000 Binary files a/src/en/oglaf/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/oglaf/res/mipmap-mdpi/ic_launcher.png b/src/en/oglaf/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 884a4f998..000000000 Binary files a/src/en/oglaf/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/oglaf/res/mipmap-xhdpi/ic_launcher.png b/src/en/oglaf/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 228937a8c..000000000 Binary files a/src/en/oglaf/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/oglaf/res/mipmap-xxhdpi/ic_launcher.png b/src/en/oglaf/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 825ec8eab..000000000 Binary files a/src/en/oglaf/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/oglaf/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/oglaf/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 61fe31fc6..000000000 Binary files a/src/en/oglaf/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/oglaf/res/web_hi_res_512.png b/src/en/oglaf/res/web_hi_res_512.png deleted file mode 100644 index f7dd6172f..000000000 Binary files a/src/en/oglaf/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/oglaf/src/eu/kanade/tachiyomi/extension/en/oglaf/Oglaf.kt b/src/en/oglaf/src/eu/kanade/tachiyomi/extension/en/oglaf/Oglaf.kt deleted file mode 100644 index 25eaa9d8f..000000000 --- a/src/en/oglaf/src/eu/kanade/tachiyomi/extension/en/oglaf/Oglaf.kt +++ /dev/null @@ -1,109 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.oglaf - -import eu.kanade.tachiyomi.annotations.Nsfw -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -@Nsfw -class Oglaf : ParsedHttpSource() { - - override val name = "Oglaf" - - override val baseUrl = "https://www.oglaf.com" - - override val lang = "en" - - override val supportsLatest = false - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val manga = SManga.create().apply { - title = "Oglaf" - artist = "Trudy Cooper & Doug Bayne" - author = "Trudy Cooper & Doug Bayne" - status = SManga.ONGOING - url = "/archive/" - description = "Filth and other Fantastical Things in handy webcomic form." - thumbnail_url = "https://i.ibb.co/tzY0VQ9/oglaf.png" - } - - return Observable.just(MangasPage(arrayListOf(manga), false)) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val chapterList = super.chapterListParse(response).distinct() - return chapterList.mapIndexed { - i, ch -> - ch.apply { chapter_number = chapterList.size.toFloat() - i } - } - } - - override fun chapterListSelector() = "a:has(img[width=400])" - - override fun chapterFromElement(element: Element): SChapter { - val nameRegex = - """/(.*)/""".toRegex() - val chapter = SChapter.create() - chapter.url = element.attr("href") - chapter.name = nameRegex.find(element.attr("href"))!!.groupValues[1] - return chapter - } - - override fun pageListParse(document: Document): List<Page> { - val urlRegex = - """/.*/\d*/""".toRegex() - val pages = mutableListOf<Page>() - - fun addPage(document: Document) { - pages.add(Page(pages.size, "", document.select("img#strip").attr("abs:src"))) - val next = document.select("a[rel=next]").attr("href") - if (urlRegex.matches(next)) addPage(client.newCall(GET(baseUrl + next, headers)).execute().asJsoup()) - } - - addPage(document) - - return pages - } - - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - override fun popularMangaSelector(): String = throw Exception("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun searchMangaSelector(): String = throw Exception("Not used") - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - - override fun popularMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - override fun latestUpdatesSelector(): String = throw Exception("Not used") -} diff --git a/src/en/patchfriday/AndroidManifest.xml b/src/en/patchfriday/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/patchfriday/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/patchfriday/build.gradle b/src/en/patchfriday/build.gradle deleted file mode 100644 index e3e2d2715..000000000 --- a/src/en/patchfriday/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Patch Friday' - pkgNameSuffix = 'en.patchfriday' - extClass = '.PatchFriday' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/patchfriday/res/mipmap-hdpi/ic_launcher.png b/src/en/patchfriday/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3826e1a84..000000000 Binary files a/src/en/patchfriday/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/patchfriday/res/mipmap-mdpi/ic_launcher.png b/src/en/patchfriday/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index eb1448a54..000000000 Binary files a/src/en/patchfriday/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/patchfriday/res/mipmap-xhdpi/ic_launcher.png b/src/en/patchfriday/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a7bb8efad..000000000 Binary files a/src/en/patchfriday/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/patchfriday/res/mipmap-xxhdpi/ic_launcher.png b/src/en/patchfriday/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 23e5cc74f..000000000 Binary files a/src/en/patchfriday/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/patchfriday/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/patchfriday/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b1275000d..000000000 Binary files a/src/en/patchfriday/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/patchfriday/res/web_hi_res_512.png b/src/en/patchfriday/res/web_hi_res_512.png deleted file mode 100644 index 5d99d4a6e..000000000 Binary files a/src/en/patchfriday/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/patchfriday/src/eu/kanade/tachiyomi/extension/en/patchfriday/PatchFriday.kt b/src/en/patchfriday/src/eu/kanade/tachiyomi/extension/en/patchfriday/PatchFriday.kt deleted file mode 100644 index ea8d130ff..000000000 --- a/src/en/patchfriday/src/eu/kanade/tachiyomi/extension/en/patchfriday/PatchFriday.kt +++ /dev/null @@ -1,103 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.patchfriday - -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable - -class PatchFriday : HttpSource() { - - override val name = "Patch Friday" - - override val baseUrl = "https://patchfriday.com" - - override val lang = "en" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - private fun createManga(): SManga { - return SManga.create().apply { - initialized = true - title = "Patch Friday" - url = "" - thumbnail_url = "https://patchfriday.com/patches/68.png" - description = "The IT security webcomic" - } - } - - // Popular - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return Observable.just(MangasPage(listOf(createManga()), false)) - } - - override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Details - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return Observable.just(createManga()) - } - - override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException("Not used") - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - val last = response.asJsoup().select("ul.strip_toolbar li a[rel=next]").attr("href") - .removeSurrounding("/").toInt() - - return listOf(1..last).flatten().reversed().map { - SChapter.create().apply { - name = "#$it - " - url = "/$it/" - } - } - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val cName = client.newCall(GET(baseUrl + chapter.url)).execute().asJsoup().select("div#strip_title").text() - - chapter.apply { name += cName } - } - - // Pages - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return Observable.just(listOf(Page(0, baseUrl + chapter.url))) - } - - override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(response: Response): String { - return response.asJsoup().select("div#strip_image img").attr("abs:src") - } - - override fun getFilterList() = FilterList() -} diff --git a/src/en/perveden/AndroidManifest.xml b/src/en/perveden/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/perveden/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/perveden/build.gradle b/src/en/perveden/build.gradle deleted file mode 100644 index a493788a0..000000000 --- a/src/en/perveden/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Perveden' - pkgNameSuffix = 'en.perveden' - extClass = '.Perveden' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/perveden/res/mipmap-hdpi/ic_launcher.png b/src/en/perveden/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 773444ec5..000000000 Binary files a/src/en/perveden/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/perveden/res/mipmap-mdpi/ic_launcher.png b/src/en/perveden/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a4b97dcfe..000000000 Binary files a/src/en/perveden/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/perveden/res/mipmap-xhdpi/ic_launcher.png b/src/en/perveden/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index cdf36f539..000000000 Binary files a/src/en/perveden/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/perveden/res/mipmap-xxhdpi/ic_launcher.png b/src/en/perveden/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 90278a445..000000000 Binary files a/src/en/perveden/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/perveden/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/perveden/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0db019b95..000000000 Binary files a/src/en/perveden/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/perveden/res/web_hi_res_512.png b/src/en/perveden/res/web_hi_res_512.png deleted file mode 100644 index 1476a57af..000000000 Binary files a/src/en/perveden/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/perveden/src/eu/kanade/tachiyomi/extension/en/perveden/Perveden.kt b/src/en/perveden/src/eu/kanade/tachiyomi/extension/en/perveden/Perveden.kt deleted file mode 100644 index 7822e5542..000000000 --- a/src/en/perveden/src/eu/kanade/tachiyomi/extension/en/perveden/Perveden.kt +++ /dev/null @@ -1,1349 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.perveden - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -@Nsfw -class Perveden : ParsedHttpSource() { - - override val name = "PervEden" - - override val baseUrl = "https://www.perveden.com" - - override val lang = "en" - - override val supportsLatest = true - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/en/en-directory/?order=3&page=$page", headers) - - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/en/en-directory/?order=1&page=$page", headers) - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/en/en-directory/")?.newBuilder()!!.addQueryParameter("title", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is StatusList -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("status", it) } - is Types -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("type", it) } - is TextField -> url.addQueryParameter(filter.key, filter.state) - is OrderBy -> filter.state?.let { - val sortId = it.index - url.addQueryParameter("order", if (it.ascending) "-$sortId" else "$sortId") - } - is GenreField -> filter.state.toLowerCase(Locale.ENGLISH).split(',', ';').forEach { - val id = genres[it.trim()] - if (id != null) url.addQueryParameter(filter.key, id) - } - } - } - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "table#mangaList > tbody > tr:has(td:gt(1))" - - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - element.select("td > a").first()?.let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - } - - override fun searchMangaNextPageSelector() = "a:has(span.next)" - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val infos = document.select("div.rightbox") - - author = infos.select("a[href^=/en/en-directory/?author]").first()?.text() - artist = infos.select("a[href^=/en/en-directory/?artist]").first()?.text() - genre = infos.select("a[href^=/en/en-directory/?categoriesInc]").joinToString { it.text() } - description = document.select("h2#mangaDescription").text() - status = parseStatus(infos.select("h4:containsOwn(Status)").first()?.nextSibling().toString()) - val img = infos.select("div.mangaImage2 > img").first()?.attr("src") - if (!img.isNullOrBlank()) thumbnail_url = img.let { "https:$it" } - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing", true) -> SManga.ONGOING - status.contains("Completed", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div#leftContent > table > tbody > tr" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val a = element.select("a[href^=/en/en-manga/]").first() - - setUrlWithoutDomain(a.attr("href")) - name = a?.select("b")?.first()?.text().orEmpty() - date_upload = element.select("td.chapterDate").first()?.text()?.let { parseChapterDate(it.trim()) } ?: 0L - } - - private fun parseChapterDate(date: String): Long = - when { - "Today" in date -> { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - "Yesterday" in date -> { - Calendar.getInstance().apply { - add(Calendar.DATE, -1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - else -> - try { - SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("option[value^=/en/en-manga/]").forEach { - add(Page(size, "$baseUrl${it.attr("value")}")) - } - } - - override fun imageUrlParse(document: Document): String = document.select("a#nextA.next > img").first()?.attr("src").let { "https:$it" } - - private class NamedId(name: String, val id: Int) : Filter.CheckBox(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class GenreField(name: String, val key: String) : Filter.Text(name) - private class OrderBy : Filter.Sort( - "Order by", - arrayOf("Manga title", "Views", "Chapters", "Latest chapter"), - Selection(1, false) - ) - - private class StatusList(statuses: List<NamedId>) : Filter.Group<NamedId>("Stato", statuses) - private class Types(types: List<NamedId>) : Filter.Group<NamedId>("Tipo", types) - - override fun getFilterList() = FilterList( - TextField("Author", "author"), - TextField("Artist", "artist"), - GenreField("Included genres", "categoriesInc"), - GenreField("Excluded genres", "categoriesExcl"), - OrderBy(), - Types(types()), - StatusList(statuses()) - ) - - private fun types() = listOf( - NamedId("Japanese Manga", 0), - NamedId("Korean Manhwa", 1), - NamedId("Chinese Manhua", 2), - NamedId("Comic", 3), - NamedId("Doujinshi", 4) - ) - - private fun statuses() = listOf( - NamedId("Ongoing", 1), - NamedId("Completed", 2), - NamedId("Suspended", 0) - ) - - private val genres = mapOf( - Pair("+", "53c5967245b9ef2b85e38cfa"), - Pair("100mjack", "56b8ea8a719a160f1970f592"), - Pair("111 touban", "57e2d1af719a1649a0b6f40e"), - Pair("21st century renaissance", "5899939e719a165a8cf59398"), - Pair("4den hiro", "56c2285d719a168abf0cf302"), - Pair("7days", "56c22ad2719a168abf0d1182"), - Pair("action", "4e70ea24c092255ef7004f85"), - Pair("adult", "4e70e91dc092255ef7001727"), - Pair("adventure", "4e70ea24c092255ef7004f86"), - Pair("ahegao", "51793ded45b9efe88c5e59cd"), - Pair("alien", "5591faf0719a16886c3376b0"), - Pair("amputee", "55925592719a16886c347d02"), - Pair("anilingus", "5591faec719a16886c33769e"), - Pair("animal boy", "577e6173719a1672c697a00a"), - Pair("animal ears", "5591faf0719a16886c3376ac"), - Pair("animal girl", "5591fc1f719a16886c337c0a"), - Pair("armpit", "55920eb7719a16886c33b920"), - Pair("autofellatio", "55920a59719a16886c33ac99"), - Pair("bbm / fat man", "577e5fd2719a1614690bd0f5"), - Pair("bbw", "5591fbcc719a16886c337a85"), - Pair("bdsm", "5591fb8f719a16886c337982"), - Pair("band-aid", "55921508719a16886c33c325"), - Pair("bathroom sex", "5591fafe719a16886c3376e4"), - Pair("bestiality", "5591fed8719a16886c3383f7"), - Pair("big areola", "5591fb8d719a16886c337975"), - Pair("big ass", "5591faf4719a16886c3376c4"), - Pair("bikini", "5591fb7b719a16886c337933"), - Pair("birth", "5591fed8719a16886c3383f5"), - Pair("bisexual", "55921d83719a16886c33da43"), - Pair("blackmail", "5591fc6c719a16886c337d77"), - Pair("blindfold", "5591fbaa719a16886c3379f8"), - Pair("blindness", "55923a7c719a16886c3427fb"), - Pair("bloomers", "5591ff70719a16886c3384e5"), - Pair("blowjob", "5591faed719a16886c3376a4"), - Pair("body painting", "55925589719a16886c347ce4"), - Pair("body swap", "55922ee2719a16886c340825"), - Pair("body writing", "559215a4719a16886c33c602"), - Pair("bodysuit", "5591ff70719a16886c3384e9"), - Pair("breast expansion", "559207e0719a16886c33a1dd"), - Pair("breast sucking", "5591faed719a16886c3376a2"), - Pair("bride", "5591fb7b719a16886c337934"), - Pair("bukkake", "5591fb31719a16886c3377f2"), - Pair("bunny ears", "5591fea4719a16886c3383b6"), - Pair("bunnygirl", "5591fd9d719a16886c338136"), - Pair("cbt", "5591fb8e719a16886c33797b"), - Pair("cat ears", "5591fb7a719a16886c33792c"), - Pair("catgirl", "5591ff70719a16886c3384e6"), - Pair("centaur", "559207e0719a16886c33a1dc"), - Pair("cervical penetration", "577e6176719a1672c697a022"), - Pair("chastity belt", "5592087e719a16886c33a520"), - Pair("childhood friend", "5591ffd8719a16886c33867f"), - Pair("chinese dress", "559202f8719a16886c339044"), - Pair("collar", "5591fbab719a16886c3379fb"), - Pair("comedy", "4e70e91dc092255ef7001728"), - Pair("condom", "5591fb32719a16886c3377f6"), - Pair("cow girl", "5591ff70719a16886c3384e7"), - Pair("crossdressing", "5591fb31719a16886c3377ef"), - Pair("crotchless / breastless", "577e5fe7719a161c005866b7"), - Pair("dandere", "55925dbc719a16886c349758"), - Pair("daughter", "5591fd72719a16886c33807c"), - Pair("deepthroat", "5591fb7b719a16886c33792d"), - Pair("defloration", "5591fb0c719a16886c337709"), - Pair("devil", "5591fc5d719a16886c337d2d"), - Pair("diaper", "55928caa719a16886c3536f7"), - Pair("dirty old man", "5591fb1b719a16886c337762"), - Pair("doctor", "55920cbf719a16886c33b290"), - Pair("dog ears", "5591fe05719a16886c33825b"), - Pair("dog girl", "5591fe03719a16886c338257"), - Pair("dojikko", "5591ff70719a16886c3384e4"), - Pair("doll", "55922f89719a16886c340be4"), - Pair("double penetration", "5591fb1b719a16886c337763"), - Pair("drama", "4e70ea23c092255ef7004f2f"), - Pair("drugs", "5591faf0719a16886c3376b4"), - Pair("drunk", "5591faec719a16886c33769c"), - Pair("ecchi", "4e70ea01c092255ef7004653"), - Pair("egg laying", "55920850719a16886c33a40c"), - Pair("elder sister", "5591ff18719a16886c338487"), - Pair("elf", "5591fb73719a16886c33790e"), - Pair("enema", "5591fc6c719a16886c337d7b"), - Pair("exhibitionism", "5591fb0c719a16886c337710"), - Pair("eyepatch", "5591ff19719a16886c338488"), - Pair("face sitting", "5591faee719a16886c3376a5"), - Pair("fairy", "55921560719a16886c33c4c5"), - Pair("fantasy", "4e70ea23c092255ef7004f5e"), - Pair("fingering", "5591faec719a16886c3376a0"), - Pair("fisting", "559208c0719a16886c33a66d"), - Pair("foot fetish", "5591fbd7719a16886c337acb"), - Pair("footjob", "5591fbab719a16886c3379fd"), - Pair("fox ears", "55921632719a16886c33c8fd"), - Pair("fox girl", "55921632719a16886c33c8fe"), - Pair("freckles", "5592019b719a16886c338dde"), - Pair("french kissing", "577e5fd2719a1614690bd100"), - Pair("frotting", "559211ff719a16886c33be47"), - Pair("full color", "5591faf0719a16886c3376b2"), - Pair("fundoshi", "559215a4719a16886c33c600"), - Pair("furry", "5591fe03719a16886c338258"), - Pair("futa on female", "577e5fd1719a1614690bd0e9"), - Pair("futa on male", "5591fc9c719a16886c337e51"), - Pair("futanari on futanari", "5591fd87719a16886c3380de"), - Pair("gangbang", "5591fb90719a16886c337983"), - Pair("gender bender", "4e70e925c092255ef7001957"), - Pair("ghost", "5591fc0a719a16886c337bbe"), - Pair("giantess", "559253c5719a16886c347648"), - Pair("gloves", "577e5fe8719a161c005866bf"), - Pair("gokkun", "5591fbaa719a16886c3379f6"), - Pair("gothic", "55921f79719a16886c33dfa7"), - Pair("group sex", "5591faed719a16886c3376a3"), - Pair("guro", "5591fe3f719a16886c3382d3"), - Pair("hairjob", "559206a7719a16886c339d7f"), - Pair("hairy armpits", "55920591719a16886c3399e3"), - Pair("handjob", "5591fb8e719a16886c33797a"), - Pair("happy sex", "5591faf0719a16886c3376ae"), - Pair("harem", "4e70ea23c092255ef7004f55"), - Pair("hat", "577e627d719a160f53827289"), - Pair("heterochromia", "559200e4719a16886c338af5"), - Pair("historical", "4e70ea23c092255ef7004f5f"), - Pair("horror", "4e70ea26c092255ef70050a9"), - Pair("hotpants", "55920090719a16886c3389ac"), - Pair("huge breasts", "5591fb8f719a16886c337981"), - Pair("huge penis", "5591fb32719a16886c3377f7"), - Pair("human toilet", "577e5fd2719a1614690bd0f8"), - Pair("hypnosis", "5592044f719a16886c33928e"), - Pair("imouto", "5591fb7b719a16886c337936"), - Pair("impregnation", "5591faf0719a16886c3376af"), - Pair("inverted nipples", "5591fef4719a16886c338434"), - Pair("kimono", "5591fed8719a16886c3383f6"), - Pair("kimono / yukata", "577e5fd1719a1614690bd0ec"), - Pair("kunoichi", "55920071719a16886c33892e"), - Pair("kuudere", "55921679719a16886c33c9d5"), - Pair("lactation", "5591faf0719a16886c3376b7"), - Pair("lamia", "55920850719a16886c33a40d"), - Pair("large breasts", "5591faec719a16886c33769d"), - Pair("legal loli", "577e5fe8719a161c005866c0"), - Pair("licking", "5591fb1b719a16886c337760"), - Pair("lowleg", "5591fb8d719a16886c337974"), - Pair("milf", "5591fb8d719a16886c337972"), - Pair("maebari", "55921a27719a16886c33d237"), - Pair("magical girl", "5591fba4719a16886c3379e2"), - Pair("male on futa", "577e6279719a160f53827278"), - Pair("maledom", "5591fbab719a16886c3379f9"), - Pair("martial arts", "4e70ea27c092255ef70050cd"), - Pair("masturbation", "5591fb23719a16886c337799"), - Pair("mature", "4e70e91dc092255ef700172b"), - Pair("mecha", "4e70ea31c092255ef7005410"), - Pair("menstruation", "55921cc6719a16886c33d7da"), - Pair("mermaid", "5592713f719a16886c34df91"), - Pair("military uniform", "5591fba2719a16886c3379cf"), - Pair("mind break", "5591fb0c719a16886c33770b"), - Pair("mind control", "5591fafe719a16886c3376e6"), - Pair("monster", "5591fafd719a16886c3376e2"), - Pair("mother", "5591fc57719a16886c337cff"), - Pair("mouth gag", "5591fc6c719a16886c337d7a"), - Pair("multiple breasts", "559207e0719a16886c33a1da"), - Pair("multiple insertion", "5591fb8e719a16886c337977"), - Pair("multiple penises", "5591fe3f719a16886c3382d4"), - Pair("muscle", "55920071719a16886c33892d"), - Pair("mystery", "4e70ea29c092255ef70051fa"), - Pair("naked apron", "5591fb7a719a16886c33792b"), - Pair("nipple fuck", "55920242719a16886c338ec0"), - Pair("nipple penetration", "577e5e30719a165576d6a2a4"), - Pair("nun", "55920692719a16886c339d4e"), - Pair("nyotaimori", "559a5ebb719a16532b1e67a9"), - Pair("office lady", "5591fb8e719a16886c33797c"), - Pair("onahole", "55920754719a16886c339f5d"), - Pair("onsen", "5591fe04719a16886c338259"), - Pair("oppai loli", "5591fb4e719a16886c337885"), - Pair("otouto", "55920409719a16886c33922e"), - Pair("pajamas", "55920c39719a16886c33afdd"), - Pair("pantyhose", "5591fb7b719a16886c337931"), - Pair("pet girl", "5591fb1b719a16886c33775e"), - Pair("phimosis", "5591fb8d719a16886c337970"), - Pair("piercings", "559203aa719a16886c3391be"), - Pair("police uniform", "55920545719a16886c3397dd"), - Pair("princess", "5591fba2719a16886c3379ce"), - Pair("prostitution", "5591faf4719a16886c3376c5"), - Pair("psychological", "4e70ea01c092255ef7004650"), - Pair("pubic hair", "5591faf0719a16886c3376b1"), - Pair("race queen", "5591fde9719a16886c33822d"), - Pair("rape", "5591fafe719a16886c3376e5"), - Pair("reverse ntr", "577e5fea719a161c005866ca"), - Pair("reverse rape", "5591fb8d719a16886c33796f"), - Pair("robot girl", "5592068d719a16886c339d47"), - Pair("romance", "4e70e9ffc092255ef70045b4"), - Pair("scat", "55920445719a16886c339268"), - Pair("school life", "4e70e925c092255ef7001958"), - Pair("school swimsuit", "5591ff71719a16886c3384eb"), - Pair("school uniform", "5591faf4719a16886c3376c1"), - Pair("sci-fi", "4e70ea26c092255ef70050c0"), - Pair("sex toys", "5591faec719a16886c33769f"), - Pair("shemale", "55920d30719a16886c33b4f6"), - Pair("shimaidon", "577e62ab719a161e140239e3"), - Pair("shoujo ai", "4e70ea23c092255ef7004f52"), - Pair("shounen ai", "4e70ea01c092255ef7004649"), - Pair("sixty-nine", "5591faef719a16886c3376a7"), - Pair("slave", "5591fb1b719a16886c33775c"), - Pair("sleeping", "5591fb5f719a16886c3378ca"), - Pair("slice of life", "4e70e925c092255ef7001959"), - Pair("slime girl", "559205c7719a16886c339b3a"), - Pair("slut", "577e5fe7719a161c005866b8"), - Pair("small penis", "5591fd87719a16886c3380db"), - Pair("smegma", "5591fb8e719a16886c337976"), - Pair("smut", "4e70ea24c092255ef7004f95"), - Pair("spanking", "559200cf719a16886c338ab4"), - Pair("spitroast", "5591fb1c719a16886c337765"), - Pair("sports", "4e70ea2ac092255ef7005231"), - Pair("squirting", "5591faec719a16886c3376a1"), - Pair("stalking", "559201d6719a16886c338e2c"), - Pair("stomach bulge", "5591fb32719a16886c3377f8"), - Pair("straight shota", "5591fb4e719a16886c337884"), - Pair("strap-on", "5591fbba719a16886c337a3c"), - Pair("succubus", "55920135719a16886c338c97"), - Pair("sumata", "5591fbab719a16886c3379fa"), - Pair("supernatural", "4e70ea24c092255ef7004f7c"), - Pair("suppository", "55925035719a16886c346ed3"), - Pair("symbol shaped pupils", "5591ff71719a16886c3384ed"), - Pair("tan", "559203aa719a16886c3391bf"), - Pair("tattoo", "55920607719a16886c339c73"), - Pair("threesome", "577e5fe5719a161c005866a4"), - Pair("time stop", "559267cf719a16886c34bc59"), - Pair("toddler", "5591fba2719a16886c3379d4"), - Pair("torture", "5591ffe8719a16886c3386c1"), - Pair("tragedy", "4e70ea24c092255ef7004f98"), - Pair("transparent clothing", "5591fe10719a16886c33826f"), - Pair("tribadism", "5591ff10719a16886c338474"), - Pair("twins", "5591ffb9719a16886c3385ed"), - Pair("twintails", "5591faf0719a16886c3376ad"), - Pair("urethra insertion", "55c91399719a1679540946f3"), - Pair("vampire", "5591fb31719a16886c3377f1"), - Pair("virginity (male)", "577e5fd2719a1614690bd0f7"), - Pair("vomit", "5591ffe8719a16886c3386c0"), - Pair("vore", "55920850719a16886c33a40f"), - Pair("voyeurism", "5591fb8d719a16886c337973"), - Pair("waitress", "559208c0719a16886c33a66c"), - Pair("wakamezake", "5592dac3719a16886c360852"), - Pair("watersports", "5591fd58719a16886c338032"), - Pair("widow", "559228f7719a16886c33fd86"), - Pair("witch", "55920864719a16886c33a484"), - Pair("yaoi", "4e70ea01c092255ef700464a"), - Pair("yukata", "55920828719a16886c33a357"), - Pair("yuri", "4e70ea01c092255ef700464d"), - Pair("zombie", "559232df719a16886c34163f"), - Pair("abdg encirclement formation", "57d1056f719a1612b7e9d933"), - Pair("abdg houimou", "57d1056f719a1612b7e9d932"), - Pair("abortion", "581fa0fc719a16211afd5940"), - Pair("absorption", "571f61fe719a167a10f0894e"), - Pair("age progression", "56b8e577719a160f1970b4c8"), - Pair("age regression", "56c22b5f719a168abf0d18c4"), - Pair("ahegao", "556467fa719a1686396e957f"), - Pair("aihara", "56e765d9719a16989a47bf5a"), - Pair("aika tsube", "574dd952719a16606128e055"), - Pair("aka usagi", "574dd9cb719a16606128e3db"), - Pair("akamiro", "56c227d5719a168abf0ced93"), - Pair("akaneko", "56c22861719a168abf0cf334"), - Pair("akari yomatsuri", "570cb2c5719a16861b23ebaa"), - Pair("alien girl", "571f27a2719a16067f34afed"), - Pair("all the way through", "56ddf3f6719a16450f8235ce"), - Pair("almarosso", "5731d49c719a160bac0aa2e7"), - Pair("already uploaded", "56b8e498719a160f1970a92f"), - Pair("alsetro", "5769bfff719a167bb7f3c0a4"), - Pair("amata x 96", "56de2d3a719a166fd66fcdca"), - Pair("ambient", "5796066a719a1604964f9402"), - Pair("amino", "573b2be6719a16738c4183df"), - Pair("anal", "4f00e025c09225131600002b"), - Pair("anal birth", "570cb3da719a16861b23f497"), - Pair("andromeda", "56f0bd99719a16856dd2038e"), - Pair("ane naru mono", "57d3fce0719a1648cd56bd79"), - Pair("anegaoka sanchoume", "56e74ad9719a165d4fe1748d"), - Pair("angel", "4f03ae61c092256d81000ad5"), - Pair("animated", "5590cbe7719a1685eace68ab"), - Pair("anorexic", "578d202e719a168c492c2c2e"), - Pair("antans8092", "57c9c52e719a1647b7d6195f"), - Pair("anthology", "56fa05a9719a1669f2a7bed6"), - Pair("ao kurage", "56fa065b719a1669f2a7c39b"), - Pair("aoi dennou", "58969c4a719a166afdaf135d"), - Pair("aomaru", "56c2297d719a168abf0d0026"), - Pair("aomoto sari", "58292fbf719a164668a6bdb0"), - Pair("apron", "4f03f496c092256d81008769"), - Pair("armpit licking", "56b8e8bc719a160f1970de1c"), - Pair("armpit sex", "56b8e742719a160f1970cc46"), - Pair("artbook", "586f0f3d719a1601e2b5c53a"), - Pair("artistcg", "56b8e438719a160f1970a4e0"), - Pair("asage", "56c22d2e719a168abf0d2edd"), - Pair("ashikoki", "4f00e5b1c09225220600012b"), - Pair("ashikoki", "55646787719a1686396e9425"), - Pair("asian girly", "57287dfd719a1639c2c8583b"), - Pair("asphyxiation", "56b8e46a719a160f1970a716"), - Pair("ass expansion", "574e1088719a164c3b023763"), - Pair("assjob", "56b8e891719a160f1970dbe2"), - Pair("aster", "582146e3719a160217cd422a"), - Pair("ato", "56ddf48a719a16450f823bf9"), - Pair("atoko", "5875fb1c719a16738b9f41a8"), - Pair("aunt", "56b8e4e4719a160f1970ad2a"), - Pair("autopaizuri", "57287e03719a1639c2c8588f"), - Pair("ayumi otosaka", "56b8eaa7719a160f1970f6f9"), - Pair("azusa nakajo", "56b8e648719a160f1970becb"), - Pair("bakunyuu", "5445a92945b9ef840fcdaba8"), - Pair("ball sucking", "56c22d38719a168abf0d2f76"), - Pair("balljob", "57bd3ef3719a163e0da0af16"), - Pair("balls expansion", "5731d538719a160bac0aa780"), - Pair("ban daiki", "589a3c70719a161a1b370941"), - Pair("bandages", "56b8e3c7719a160ccaf2ebfe"), - Pair("banjaku", "570c79ed719a1621f04affab"), - Pair("bara", "52e7a50045b9ef44698f7134"), - Pair("bbm", "56b8e337719a1608ab978ef4"), - Pair("bear", "5744ba6e719a162b60f1051d"), - Pair("bee girl", "57287f06719a1639c2c865fc"), - Pair("bemani", "56cb7ed7719a161b35a6a700"), - Pair("beni", "56b8e79c719a160f1970d03b"), - Pair("big areolae", "56b8e49d719a160f1970a96b"), - Pair("big balls", "56b8e4bc719a160f1970ab0e"), - Pair("big breasts", "56b8e33f719a1608ab978f19"), - Pair("big clit", "56b8e43f719a160f1970a516"), - Pair("big nipples", "56b8e3b8719a160ccaf2eae7"), - Pair("big penis", "56b8e3b8719a160ccaf2eae8"), - Pair("big vagina", "575730f9719a167268694230"), - Pair("bike shorts", "56b8e6ac719a160f1970c3c6"), - Pair("biting", "5493f01f45b9ef2e15b4b845"), - Pair("bittsu", "5769c138719a167bb7f3ce84"), - Pair("blind", "56cb61d6719a167c4e9e8dc0"), - Pair("blink", "56c22a9e719a168abf0d0e53"), - Pair("blood", "56b8e600719a160f1970bad4"), - Pair("blowjob face", "56b8e36a719a160ccaf2e5d9"), - Pair("blue keshi", "577ca8e4719a169cd4768b7e"), - Pair("body modification", "56b8e488719a160f1970a884"), - Pair("bodystocking", "57f59b0d719a167b1dbd5cc6"), - Pair("bojo g. gangster", "5875a6c2719a165b3c314143"), - Pair("boku wa mari no naka", "584a253d719a168a1c1bbcc6"), - Pair("bondage", "55023ecd719a1676c2a42957"), - Pair("bondage", "556467e9719a1686396e953c"), - Pair("booty", "56ad63a2719a164dbd1d4b21"), - Pair("brain fuck", "56de2c65719a166fd66fc488"), - Pair("breast feeding", "56b8e710719a160f1970c952"), - Pair("breast reduction", "58799b45719a162e0a414ec9"), - Pair("brother", "56b8e4fd719a160f1970ae8f"), - Pair("bukakke", "4f01ece8c092253ba7006aab"), - Pair("bull", "57bc41cc719a162732aaee12"), - Pair("bullseye", "57033fdd719a16216ffcd111"), - Pair("bunker k", "577c6e00719a1623436efb73"), - Pair("bunny boy", "56b8e98b719a160f1970e904"), - Pair("business suit", "56b8e4a9719a160f1970aa04"), - Pair("butler", "56b8e69f719a160f1970c32e"), - Pair("c60", "574481c6719a163976f9e62c"), - Pair("c62", "5744840e719a163976fa00c1"), - Pair("c63", "57287e49719a1639c2c85c43"), - Pair("cagliostro", "56b8e8bc719a160f1970de1e"), - Pair("calm atmosphere", "56de2be0719a166fd66fbf15"), - Pair("candy-city", "56de2b3a719a166fd66fb967"), - Pair("cannibalism", "56c22743719a168abf0ce659"), - Pair("carmine", "5703402a719a16216ffcd3f2"), - Pair("cashier", "56d4d4cf719a16848b4cc692"), - Pair("cat", "57034142719a16216ffcde22"), - Pair("catboy", "56b8e6aa719a160f1970c3ae"), - Pair("catfight", "5728b748719a16117c8d08c8"), - Pair("cats claw", "56de2d08719a166fd66fcb8a"), - Pair("censored", "5590b046719a167a01f350e2"), - Pair("cervix penetration", "56b8e3b7719a160ccaf2eadc"), - Pair("chaoroushi", "56c22b00719a168abf0d13bd"), - Pair("charie", "56b8e4ec719a160f1970ada2"), - Pair("cheating", "4f00e1c4c0922515ab000185"), - Pair("cheating", "55646753719a1686396e9359"), - Pair("cheerleader", "4f01f2ddc092253ba7007534"), - Pair("chibita", "56f0a0ab719a166ee74a1a0d"), - Pair("chikan", "4f00ee64c092252206000c93"), - Pair("chloroform", "56b8e726719a160f1970cace"), - Pair("christmas", "567af645719a165958294818"), - Pair("chubby", "53ab31aa45b9efd826b4fe7b"), - Pair("chubby", "556467e9719a1686396e953e"), - Pair("chung seiker", "57dfdaa1719a1686deb8aa8e"), - Pair("ciao baby", "56c229a7719a168abf0d0221"), - Pair("circle ao kurage", "56fa065b719a1669f2a7c39c"), - Pair("claris", "56c22b3c719a168abf0d16e5"), - Pair("clemont", "57bceb55719a1687ea2bc3bb"), - Pair("clit growth", "56b8e488719a160f1970a887"), - Pair("clown", "57b99f11719a163fba895493"), - Pair("coach", "56b8e4bc719a160f1970ab0b"), - Pair("color", "4f00e187c0922515ab000135"), - Pair("color", "55646778719a1686396e93f7"), - Pair("comaku", "56cb7ffd719a161b35a6b2ff"), - Pair("commamion", "56ddf3ea719a16450f823539"), - Pair("commanding eagle", "571f6011719a167a10f07a9b"), - Pair("compilation", "56b8e763719a160f1970cdbb"), - Pair("coprophagia", "56b8e61b719a160f1970bc38"), - Pair("corruption", "56b8e580719a160f1970b539"), - Pair("corset", "56b8e3b3719a160ccaf2ea92"), - Pair("cosplay", "5518d301719a168688910203"), - Pair("cosplaying", "56b8e571719a160f1970b466"), - Pair("cousin", "56b8e4f7719a160f1970ae53"), - Pair("cowman", "56de2c69719a166fd66fc4ad"), - Pair("crotch tattoo", "57f000b1719a162d156df4ed"), - Pair("crown", "56b8e8bc719a160f1970de1d"), - Pair("cum bath", "5728b853719a16117c8d0fe8"), - Pair("cum in eye", "56f0a1d4719a166ee74a27ac"), - Pair("cum swap", "56f0a211719a166ee74a2ab3"), - Pair("cunnilingus", "5490dca045b9efa75b8f6627"), - Pair("cunnilingus", "55646787719a1686396e9427"), - Pair("cuntboy", "589f825c719a167f7d0d0d74"), - Pair("cure magical", "57e02edd719a161a6b8ce14f"), - Pair("cure twinkle", "56b8e8cb719a160f1970ded6"), - Pair("cyan", "56cb80ab719a161b35a6ba6b"), - Pair("dai roku seitetsu", "56f0a13f719a166ee74a2064"), - Pair("dainyu", "56f0a203719a166ee74a2a04"), - Pair("dainyu dougumo", "56f0a203719a166ee74a2a05"), - Pair("daken", "57033fdd719a16216ffcd112"), - Pair("dark avengers", "57033fdd719a16216ffcd113"), - Pair("dark nipples", "56c22c00719a168abf0d207d"), - Pair("dark pit", "57bb44b9719a169ef398c2e5"), - Pair("dark sclera", "5849281e719a1634eef403ca"), - Pair("dark skin", "556467fa719a1686396e957d"), - Pair("darkskin", "4f00ea93c0922522060007e1"), - Pair("date naoto", "5794b4cf719a167e4baa78e4"), - Pair("dearka elsman", "56cb6416719a167c4e9eaa5a"), - Pair("decensored", "54ee5fd5719a1670422d15aa"), - Pair("delmo", "57160a73719a1695b38aaa4b"), - Pair("demon", "56b8e8c3719a160f1970de7c"), - Pair("demongirl", "4f03ae61c092256d81000ad6"), - Pair("denjarasu yamada", "58346488719a1688280d6f18"), - Pair("dick growth", "56b8e352719a1608ab978f68"), - Pair("dickgirl on dickgirl", "56d4b9cb719a164632263b74"), - Pair("dickgirl on male", "56ddf4ef719a16450f824176"), - Pair("dicknipples", "56f9ea3e719a164395bc0f2e"), - Pair("dilf", "56b8e3bf719a160ccaf2eb67"), - Pair("dog", "56b8e42f719a160f1970a498"), - Pair("dog boy", "56ddf302719a16450f822bf6"), - Pair("doggie style", "586e121e719a1621d850fb56"), - Pair("doll joints", "56b8e41b719a160f1970a393"), - Pair("double anal", "56b8e3c3719a160ccaf2eba4"), - Pair("double blowjob", "5728ba1d719a16117c8d1db5"), - Pair("double vaginal", "56b8e45b719a160f1970a652"), - Pair("dougi", "56b8e7d9719a160f1970d33f"), - Pair("doujinshi", "56b8e389719a160ccaf2e7b8"), - Pair("dpe", "56c22c6d719a168abf0d2517"), - Pair("dragon", "56c22970719a168abf0cff71"), - Pair("ear fuck", "5769f893719a1672d0b8946a"), - Pair("ebisawa nira", "56b8ea8a719a160f1970f591"), - Pair("edogawa koubou", "57160ace719a1695b38aad2b"), - Pair("edwin black", "56f0a13f719a166ee74a2065"), - Pair("eggs", "56d4b9ba719a164632263a9b"), - Pair("electric shocks", "5773172a719a166f4297dee6"), - Pair("elizabeth bathory", "57033ffd719a16216ffcd21f"), - Pair("emotionless sex", "56b8e4af719a160f1970aa5f"), - Pair("eren", "56cb7f74719a161b35a6ae68"), - Pair("eye penetration", "585e4025719a1636e2d6b84f"), - Pair("eyemask", "56b8e631719a160f1970bd4d"), - Pair("eyo", "56fa0548719a1669f2a7bc17"), - Pair("ezo renge", "56b8e5d8719a160f1970b906"), - Pair("ezo renkon", "56b8e5d8719a160f1970b907"), - Pair("f.o.f", "56f0a18f719a166ee74a2465"), - Pair("face to face", "582fc748719a16491f6a9cdb"), - Pair("fakers manual", "56b8ecee719a167b52e95a1c"), - Pair("fangs", "546fcb0045b9ef8e2468f3e8"), - Pair("fangs", "556467b6719a1686396e94af"), - Pair("farrah", "5885cd1d719a16181d3a77a3"), - Pair("farting", "56b8e61b719a160f1970bc37"), - Pair("father", "56b8e616719a160f1970bbea"), - Pair("felli", "56b8e718719a160f1970c9d4"), - Pair("females only", "56b8e4b8719a160f1970aad4"), - Pair("femdom", "4f00e673c09225220600022b"), - Pair("femdom", "556467b6719a1686396e94ad"), - Pair("feminization", "56b8e3c3719a160ccaf2eba8"), - Pair("ffm theesome", "5728b799719a16117c8d0b77"), - Pair("ffm threesome", "56b8e36a719a160ccaf2e5da"), - Pair("fft threesome", "56b8e43e719a160f1970a512"), - Pair("filming", "56b8e49d719a160f1970a96c"), - Pair("first person perspective", "56cb7f9a719a161b35a6b000"), - Pair("fish", "56f0a193719a166ee74a2489"), - Pair("foot insertion", "570c972e719a16054fd7c7a0"), - Pair("foot licking", "56b8e654719a160f1970bf76"), - Pair("forbidden content", "56b8e736719a160f1970cba9"), - Pair("forced", "4f00db0ec0922505a6000001"), - Pair("forniphilia", "56de2c55719a166fd66fc3db"), - Pair("fox boy", "57287e89719a1639c2c85fc8"), - Pair("frog", "56cb63ac719a167c4e9ea4eb"), - Pair("frottage", "56b8e8ef719a160f1970e0ce"), - Pair("fsweatfsole", "56f0bd03719a16856dd1fd42"), - Pair("fujii rino", "574dd9d6719a16606128e461"), - Pair("fujimoto go", "56b8e717719a160f1970c9c7"), - Pair("fujiwara yuuka", "56f0a0ab719a166ee74a1a0e"), - Pair("ful lcen", "56d4ba37719a164632264108"), - Pair("full body tattoo", "56b8e47a719a160f1970a7de"), - Pair("full censorship", "56b8e36a719a160ccaf2e5d7"), - Pair("fushimori tonkatsu", "56f0a211719a166ee74a2ab4"), - Pair("futanari", "4f00e7d5c0922522060003d9"), - Pair("futanari", "55646784719a1686396e9417"), - Pair("futanari on male", "56b8e425719a160f1970a40a"), - Pair("fuuka kazaguruma", "57e3260e719a165f19a37b75"), - Pair("gag", "56b8e49c719a160f1970a963"), - Pair("galko", "56c22bb8719a168abf0d1cdb"), - Pair("gaping", "56de2c93719a166fd66fc6a7"), - Pair("garter belt", "56b8e372719a160ccaf2e667"), - Pair("gasmask", "56b8e8e6719a160f1970e061"), - Pair("gass. mosa", "56fa06ac719a1669f2a7c628"), - Pair("genderbend", "54815eff45b9efd7b2d893fc"), - Pair("genussmittel", "58443681719a1685174341c9"), - Pair("getty", "5864d7a0719a16871a28e3c4"), - Pair("giant", "57160a50719a1695b38aa911"), - Pair("giantesskatelyn", "5773181c719a166f4297e8d5"), - Pair("giftkuchen", "56e74a59719a165d4fe16ed3"), - Pair("giiza", "56e74bff719a165d4fe18196"), - Pair("ging freecss", "57287fe5719a1639c2c86fa9"), - Pair("girls only", "56e74999719a165d4fe166b8"), - Pair("glasses", "54752d2345b9ef81e12e2ed6"), - Pair("glasses", "556467f4719a1686396e9567"), - Pair("glastonbury1966", "57f85a49719a1625873c85c3"), - Pair("glory hole", "56b8e616719a160f1970bbe9"), - Pair("goblin", "56cb7e28719a161b35a69ff0"), - Pair("gohan oomori", "56f0bd68719a16856dd201a1"), - Pair("goma brothers", "56e74981719a165d4fe16592"), - Pair("gomabura", "56e74981719a165d4fe16593"), - Pair("good job", "57731838719a166f4297e998"), - Pair("gorilla", "577c6d9c719a1623436ef63b"), - Pair("gothic lolita", "56b8e571719a160f1970b467"), - Pair("granddaughter", "56b8e659719a160f1970bfc7"), - Pair("grandfather", "57320c95719a16693cf46b4e"), - Pair("grandmother", "5744bb4b719a162b60f10eac"), - Pair("grop", "57f1522f719a1629aedc66cc"), - Pair("group", "4f00db0ec0922505a6000002"), - Pair("group", "55646756719a1686396e936d"), - Pair("growth", "56cb6226719a167c4e9e9150"), - Pair("gumiyasan", "56c22d61719a168abf0d31bc"), - Pair("guys only", "56d4b8a5719a16463226304c"), - Pair("gyarin", "5769bfff719a167bb7f3c0a5"), - Pair("gyaru", "54fcf7a7719a164793bf7a1e"), - Pair("gyaru", "556467b6719a1686396e94b0"), - Pair("gyaru-oh", "56b8e4a3719a160f1970a9b8"), - Pair("gymshorts", "56d4d649719a16848b4cd847"), - Pair("gyuo", "5863862d719a165217c26fa3"), - Pair("gyuunyuu linda", "5874a9a6719a1624355ffc0a"), - Pair("hachimitsu orange", "577c6deb719a1623436efa80"), - Pair("hachizaki suigin", "56b8e66c719a160f1970c0e1"), - Pair("hadaka", "57731873719a166f4297eb85"), - Pair("hahaoya shikkaku", "5797ac47719a164c635808ea"), - Pair("hairy", "55816856719a167bf31d5409"), - Pair("hakuchuu doudou", "573b65c1719a1629609e728a"), - Pair("halkrom", "57b6a76d719a168b3febaf31"), - Pair("handicapped", "56b8ea6b719a160f1970f3f5"), - Pair("harazumi tami", "56b8eaac719a160f1970f747"), - Pair("hardcore", "5728b748719a16117c8d08c9"), - Pair("hareta", "57035b76719a161297661786"), - Pair("harpy", "56d4b9ba719a164632263a9a"), - Pair("haruha rutei", "56c229f5719a168abf0d0643"), - Pair("haruka haruno", "56b8e63f719a160f1970be30"), - Pair("haruna mahiru", "56b8e567719a160f1970b3eb"), - Pair("hatake wo tagayasu dake", "57035b76719a161297661787"), - Pair("hatakewotagayasudake", "57035b76719a161297661788"), - Pair("hatanaka", "588fb05c719a1638581fd5f3"), - Pair("hatosable", "5744bab3719a162b60f108c8"), - Pair("headphones", "55022186719a1677296ead9a"), - Pair("headphones", "55646787719a1686396e9429"), - Pair("hentai", "4fdb1154c092250751000000"), - Pair("hentai", "5564674c719a1686396e9327"), - Pair("hidesys", "582340fe719a16683a15a8b7"), - Pair("higashi chinta", "57c57c4d719a16706e04fca3"), - Pair("high fly flow", "5760a41e719a161c54371bf7"), - Pair("high score girl", "573b2bfb719a16738c4184cf"), - Pair("highway61", "57f85a49719a1625873c85c4"), - Pair("hijab", "56b8e842719a160f1970d816"), - Pair("himeno katsuragi", "56f0a1e5719a166ee74a2889"), - Pair("hinatamizu", "578b25ed719a164d0a8b3622"), - Pair("hiro hamada", "5728b934719a16117c8d17c4"), - Pair("hiroko", "5728ba01719a16117c8d1cea"), - Pair("hiroshi nohara", "57df85ed719a1691d5f4ab2c"), - Pair("hiura kyono", "57f6982d719a16796a496724"), - Pair("hiyoubeya", "570cb321719a16861b23ee8b"), - Pair("homuraya pleiades", "56d4b957719a16463226368c"), - Pair("honebuto wasshoi", "574e1074719a164c3b023684"), - Pair("honeyamber", "587c3e64719a1612697d86b5"), - Pair("honeycomb ice cream", "577ca714719a169cd4767b32"), - Pair("hong ban-jang", "577317d7719a166f4297e5bb"), - Pair("honoka ayase", "579e43ae719a16855b671346"), - Pair("hori makoto", "57d845b5719a164697a9b291"), - Pair("horse", "56c22acb719a168abf0d1126"), - Pair("horse boy", "56c22921719a168abf0cfbbb"), - Pair("hougen", "571f6213719a167a10f089d0"), - Pair("housewife", "4f00e1c4c0922515ab000186"), - Pair("housewife", "55646756719a1686396e936e"), - Pair("how to", "57d89a0d719a168be7f092c1"), - Pair("hoyoyodou", "56b8e545719a160f1970b250"), - Pair("human cattle", "56b8e580719a160f1970b53a"), - Pair("human on furry", "56d4b874719a164632262e32"), - Pair("human pet", "56b8e85b719a160f1970d96e"), - Pair("humiliation", "4f00e084c0922513160000ae"), - Pair("humiliation", "556467e9719a1686396e953d"), - Pair("husky guy", "577c6d50719a1623436ef37f"), - Pair("hutamizu kirin", "5823e9bc719a168028c14a21"), - Pair("hyouga.", "56b8e3a9719a160ccaf2e9e3"), - Pair("idol", "543d47f145b9ef9a694a9a6c"), - Pair("ike reibun", "57287e29719a1639c2c85a66"), - Pair("ikechika", "5728ba27719a16117c8d1dea"), - Pair("ikeshita maue", "5728ba27719a16117c8d1deb"), - Pair("iku ikuo", "56c22902719a168abf0cfa3d"), - Pair("incest", "4f00e7ffc09225220600042f"), - Pair("inco", "585e401d719a1636e2d6b79d"), - Pair("incomplete", "56b8e661719a160f1970c04e"), - Pair("ineki", "57576739719a166c7f220913"), - Pair("ineminori", "56c22ad2719a168abf0d1183"), - Pair("infantilism", "57572fbf719a16726869335c"), - Pair("inflation", "56b8e337719a1608ab978ef3"), - Pair("inoue kiyoshi", "56b8e79c719a160f1970d03c"), - Pair("insect", "56c22a78719a168abf0d0c4a"), - Pair("insect boy", "577c6d5d719a1623436ef3fb"), - Pair("insect girl", "56b8e8b2719a160f1970dd8f"), - Pair("inseki", "5515d705719a164a9f01c4b4"), - Pair("interview", "562fd0d9719a16275dce2d0d"), - Pair("invisible", "56b8e4d5719a160f1970ac64"), - Pair("iroitotoiro", "57bbed97719a1645baf57432"), - Pair("irori yui", "57287ee2719a1639c2c86419"), - Pair("irrumatio", "53ddd6f345b9ef9287db6532"), - Pair("isolated island oni", "56cb7e75719a161b35a6a2d5"), - Pair("jikansakougeki", "56de2be1719a166fd66fbf1f"), - Pair("joe higashi", "56d4d4e6719a16848b4cc78d"), - Pair("josou seme", "578ccbce719a164e1d0fc074"), - Pair("juder", "57dfda5a719a1686deb8a71a"), - Pair("jurai andou", "58326a48719a1690831fdcfa"), - Pair("juurokurou", "56fa0548719a1669f2a7bc18"), - Pair("juuryoku shiki youheki", "57731939719a166f4297f35f"), - Pair("k dash", "5760a3a0719a161c54371584"), - Pair("k.tomo", "56de2be0719a166fd66fbf16"), - Pair("kagetsu hakamada", "57035caa719a16129766206f"), - Pair("kaitou yuuhi", "57960678719a1604964f9515"), - Pair("kakizaki kousei", "571608ea719a1695b38a9f3b"), - Pair("kaku tatakaeri", "57ad18a0719a1684b32b9d64"), - Pair("kamatsukatei", "56cb6317719a167c4e9e9cd5"), - Pair("kamikage kirino", "56e74bb3719a165d4fe17dc4"), - Pair("kamogawa", "5760a4a5719a161c54372361"), - Pair("kamogawa taiyaki", "570cb224719a16861b23e689"), - Pair("kangaroo", "57d3fccf719a1648cd56bcbc"), - Pair("kaoru ryuzaki", "570cb48f719a16861b23fb85"), - Pair("kappa", "56c2281c719a168abf0cf049"), - Pair("kasai yukiha", "5837b03f719a165b6e109d77"), - Pair("kebiishi", "56b8ead0719a160f1970f915"), - Pair("kemonomimi", "4f00e0c3c0922515ab000001"), - Pair("kemonomimi", "55646784719a1686396e9419"), - Pair("kenichi", "56d4d578719a16848b4cce7a"), - Pair("kenichi saruyama", "57843a0f719a1695c3045ce1"), - Pair("kigisu", "56c22a85719a168abf0d0cf9"), - Pair("kiiroi tamago", "570340ab719a16216ffcd8a0"), - Pair("kijinoko", "56c22a85719a168abf0d0cfa"), - Pair("kikaider reijiro", "5782b3c9719a169f8b21003e"), - Pair("kimcheese", "56b8e567719a160f1970b3ec"), - Pair("kirimochi niwe", "58375bdd719a1644a7ae2c79"), - Pair("kisaragi-ice", "5874fe2a719a1673c7c6fbc2"), - Pair("kissing", "56b8e4d6719a160f1970ac77"), - Pair("kitano megumi", "57e02ede719a161a6b8ce166"), - Pair("kkc", "56f9e9ae719a164395bc09fb"), - Pair("kneepit sex", "578ec60d719a1602471883d2"), - Pair("koborii", "56f9e8ed719a164395bc04ed"), - Pair("kobuta no yakata", "57db3d18719a165182d33df1"), - Pair("kogasaki yuina", "58837e84719a161bb394d487"), - Pair("komyu", "56b8eadf719a160f1970f9e0"), - Pair("konami risa", "57f6982d719a16796a496722"), - Pair("kono subarashii sekai ni shukufuku wo", "57c32dc2719a163e9f75d6db"), - Pair("kotau", "56b8e8b2719a160f1970dd90"), - Pair("kotee", "57ca6ded719a166fb640d2b1"), - Pair("koukyou gikou", "5899939e719a165a8cf59397"), - Pair("kozountoko", "57287e0d719a1639c2c8593a"), - Pair("kreuz", "570cb317719a16861b23ee23"), - Pair("kuriyama natsuki", "5796066a719a1604964f9403"), - Pair("kurumi ohnuma", "56f0a091719a166ee74a18e0"), - Pair("kusogaki teikoku", "57adc14c719a16504d6868c1"), - Pair("kuusuke matsuno", "57287faa719a1639c2c86d39"), - Pair("kyojinkou", "582fc73c719a16491f6a9c66"), - Pair("lab coat", "56b8e951719a160f1970e5c7"), - Pair("langley", "570cb219719a16861b23e627"), - Pair("lapislazuli", "56c22c10719a168abf0d213a"), - Pair("large insertions", "56b8e488719a160f1970a886"), - Pair("lastcrime", "56ddf460719a16450f8239ec"), - Pair("latex", "56b8e385719a160ccaf2e77b"), - Pair("layer cake", "56b8e7d5719a160f1970d30d"), - Pair("leg lock", "56b8e540719a160f1970b1fa"), - Pair("legjob", "57287e3f719a1639c2c85bc1"), - Pair("leone", "56cb80c4719a161b35a6bba6"), - Pair("leotard", "56b8e372719a160ccaf2e669"), - Pair("liangshan bo", "5715d1ca719a16156436fb08"), - Pair("lingerie", "5515d6bf719a164a9f01c49c"), - Pair("lingerie", "556467e2719a1686396e9523"), - Pair("listless time", "583fedab719a168fb1a71f33"), - Pair("living clothes", "56b8e4c4719a160f1970ab86"), - Pair("lizard girl", "57033ffd719a16216ffcd21e"), - Pair("lizard guy", "56fa20f4719a1685ea9a1fc7"), - Pair("log", "57f643cd719a1606a5a83a1f"), - Pair("loli", "550221c7719a1677296eada6"), - Pair("loli seiyouken", "56e749dc719a165d4fe16986"), - Pair("lolicon", "56b8e337719a1608ab978ef2"), - Pair("long tongue", "56b8e732719a160f1970cb78"), - Pair("love hotel", "5728b748719a16117c8d08ca"), - Pair("low bestiality", "56de2b7f719a166fd66fbbcf"), - Pair("low lolicon", "56d4b940719a1646322635a6"), - Pair("low shotacon", "56c22970719a168abf0cff73"), - Pair("lunacy", "56cb80ac719a161b35a6ba7e"), - Pair("m2 goo", "57f643cd719a1606a5a83a20"), - Pair("machine", "56c22893719a168abf0cf53c"), - Pair("maeda momo", "57c970cc719a165d1d1d28e9"), - Pair("maid", "4f00e032c092251316000049"), - Pair("maji", "57e08339719a1625b30f77c0"), - Pair("majikayo", "57e08339719a1625b30f77c1"), - Pair("makunouchi", "57160ace719a1695b38aad22"), - Pair("male on dickgirl", "56ddf4ef719a16450f824177"), - Pair("male on futanari", "56b8e4cf719a160f1970ac10"), - Pair("males only", "56b8e305719a1607fceb7b6d"), - Pair("mamizo", "56d4b86e719a164632262df9"), - Pair("manga", "56b8e376719a160ccaf2e6a0"), - Pair("manikoro", "570c9600719a16054fd7bd81"), - Pair("mannmaru", "5820f260719a1679c9cd5fde"), - Pair("maraschino", "56e74aa7719a165d4fe17276"), - Pair("marika hoshino", "57735129719a165a1294d95f"), - Pair("masheri", "56b8eadf719a160f1970f9df"), - Pair("mashiba kenta", "5792ba8c719a162fe1be0a15"), - Pair("masked face", "56b8e732719a160f1970cb77"), - Pair("mata kara stream", "56cb7e33719a161b35a6a080"), - Pair("matsu no an", "56c22c66719a168abf0d24b6"), - Pair("maya joukawa", "56cb6314719a167c4e9e9cb6"), - Pair("mecha boy", "56cb7ed6719a161b35a6a6f7"), - Pair("mecha girl", "56b8e98b719a160f1970e909"), - Pair("megane", "4f00e587c0922522060000eb"), - Pair("meganekko", "56ddf4b2719a16450f823dc5"), - Pair("megaton express", "56f0a211719a166ee74a2ab5"), - Pair("megumi natsu", "56cb6314719a167c4e9e9cb5"), - Pair("mei hatsume", "57c2308d719a1677ad7e5ad4"), - Pair("meiji chimera", "56f0a0ab719a166ee74a1a0f"), - Pair("meiko", "588c103c719a1649da625aee"), - Pair("melon no hoshiboshi", "56b8e9fa719a160f1970eed1"), - Pair("ment", "583fedab719a168fb1a71f34"), - Pair("merman", "578e71b0719a16407c66330f"), - Pair("metal armor", "56cb7e26719a161b35a69fe3"), - Pair("midget", "57287dca719a1639c2c8565b"), - Pair("midoriiro", "57573162719a16726869469f"), - Pair("mikado", "56c22ac7719a168abf0d10ec"), - Pair("miko", "4f32dab4c0922569dc000cf4"), - Pair("miku izayoi", "579114bc719a166b15a7c009"), - Pair("military", "56b8e337719a1608ab978ef1"), - Pair("milking", "56b8e726719a160f1970cacb"), - Pair("mille", "56d4b96b719a16463226374d"), - Pair("mimit", "57c9c52e719a1647b7d6195e"), - Pair("minigirl", "56b8e504719a160f1970aee1"), - Pair("miniguy", "56b8e47e719a160f1970a809"), - Pair("minotaur", "570c970d719a16054fd7c62e"), - Pair("mio naruse", "56b8e783719a160f1970cefb"), - Pair("misc", "5731d452719a160bac0aa036"), - Pair("mitsuko", "587c3e64719a1612697d86b4"), - Pair("mitsunoho", "56c22c28719a168abf0d222b"), - Pair("miyoshi hiromi", "570c79eb719a1621f04aff9d"), - Pair("miyuki rei", "57035ba5719a1612976618e3"), - Pair("mizugi", "4f00db5ac0922505f1000001"), - Pair("mizuki kanzaki", "573b650c719a1629609e69ac"), - Pair("mmf threesome", "56b8e337719a1608ab978eee"), - Pair("mohorovicic matako", "56cb7e33719a161b35a6a081"), - Pair("momiyama", "57f0fdcf719a163c0daba6a7"), - Pair("momo yaoyorozu", "56cb63ac719a167c4e9ea4ea"), - Pair("momoishi", "5835b618719a168d2c17820f"), - Pair("monoeye", "578d202e719a168c492c2c2f"), - Pair("monophobia", "56c22bf1719a168abf0d1fad"), - Pair("monster girl", "5564675a719a1686396e9386"), - Pair("monstergirl", "4f00e97bc092252206000612"), - Pair("moral degeneration", "56b8e47a719a160f1970a7dd"), - Pair("moralgear", "56cb62ad719a167c4e9e9797"), - Pair("mosaic censor", "56b8e9e9719a160f1970eddb"), - Pair("mosaic censorship", "56b8e3b7719a160ccaf2eadd"), - Pair("mouse boy", "570c970a719a16054fd7c619"), - Pair("mouse girl", "578c778f719a1691573a0c04"), - Pair("mousou colosseum", "5796af0d719a160e752c2fee"), - Pair("mtf threesome", "56b8e631719a160f1970bd4c"), - Pair("multi-work series", "56b8e33f719a1608ab978f1c"), - Pair("multi-works series", "57df85fc719a1691d5f4abc2"), - Pair("multiple arms", "577ca8db719a169cd4768b19"), - Pair("multiple nipples", "57c9c52e719a1647b7d61960"), - Pair("multiple paizuri", "56c22d38719a168abf0d2f75"), - Pair("musae koyama", "57df85ed719a1691d5f4ab2d"), - Pair("muscles", "555a4e8b719a1651bff3d17d"), - Pair("muscles", "556467fa719a1686396e9581"), - Pair("music box", "5728b748719a16117c8d08cb"), - Pair("mute", "571f5fbb719a167a10f077e5"), - Pair("muto", "57c9c52e719a1647b7d6195c"), - Pair("mx2j", "57f8ae8e719a169ba3a6f397"), - Pair("nagata shinichi", "56b8e647719a160f1970bec2"), - Pair("nagisora riku", "5773181c719a166f4297e8d7"), - Pair("nakadashi", "540cdc7345b9ef6cb3231358"), - Pair("nakadashi", "5564674c719a1686396e9328"), - Pair("nakanishi", "586cc09c719a1629f4914d23"), - Pair("nakasone heidi", "5787da56719a1621f2b2042b"), - Pair("nakata mitsuru", "58438dbd719a16586fea74b6"), - Pair("namanamago", "56c22a93719a168abf0d0db4"), - Pair("nanaki nanatarou", "578b7a4e719a164531bffdc0"), - Pair("natsuki kimura", "5769c167719a167bb7f3d098"), - Pair("natsume benkei", "589993a6719a165a8cf59451"), - Pair("navel fuck", "56ddf3f6719a16450f8235cf"), - Pair("nazi", "5892a7bd719a169e79e8ef68"), - Pair("ncp", "56c228cf719a168abf0cf823"), - Pair("necrophilia", "56f0a0b3719a166ee74a1a70"), - Pair("negative 69", "573b2be6719a16738c4183e0"), - Pair("neitz", "56c2297d719a168abf0d0025"), - Pair("neko no oppai", "56c22a4a719a168abf0d09c6"), - Pair("nekoya marble", "56c22abe719a168abf0d1075"), - Pair("nemu", "56b8e8bb719a160f1970de07"), - Pair("nerigom", "5875a6bc719a165b3c3140d3"), - Pair("nerv", "574e136a719a164c3b0258a5"), - Pair("netorare", "4f00e52cc092252206000054"), - Pair("netorare", "55646799719a1686396e945f"), - Pair("netori", "5426add745b9ef6d7a289a24"), - Pair("nicomarch", "56d4b955719a164632263675"), - Pair("niece", "56c22876719a168abf0cf3eb"), - Pair("ninja", "56cb62d9719a167c4e9e9994"), - Pair("ninnindou", "56d4d616719a16848b4cd5b7"), - Pair("nipple birth", "56c22d46719a168abf0d3043"), - Pair("nipple expansion", "5744bafa719a162b60f10af6"), - Pair("no color", "57731994719a166f4297f709"), - Pair("nobuko yokokawa", "573b656c719a1629609e6eee"), - Pair("non-h", "52e7a80f45b9ef5ed98db192"), - Pair("noriko", "5848d3c7719a1685432d1b72"), - Pair("nose hook", "570c96d9719a16054fd7c3c7"), - Pair("not found 05", "58239562719a161ef54929ef"), - Pair("nounanka", "5744bb4b719a162b60f10eae"), - Pair("number10", "56cb63ba719a167c4e9ea59b"), - Pair("nurse", "4f00e587c0922522060000ec"), - Pair("ochako uraraka", "5782b3d2719a169f8b2100b7"), - Pair("ochine", "57bc9658719a167558cbef60"), - Pair("octopus", "56c22a78719a168abf0d0c49"), - Pair("odd", "5836b326719a166af458a4de"), - Pair("ogawa chise", "57db916d719a164187907c15"), - Pair("oil", "56d4b8c9719a16463226317a"), - Pair("okyuuri", "56e74ad9719a165d4fe1748c"), - Pair("old lady", "58501403719a165eaaf83a4d"), - Pair("old man", "56b8e63f719a160f1970be2f"), - Pair("omega destroyer", "56f0a211719a166ee74a2ab6"), - Pair("onani", "568b0083719a1664807dd93a"), - Pair("oni", "56c2278f719a168abf0cea77"), - Pair("ontsu", "58628903719a166e8c8a4aa7"), - Pair("oppai", "4f00e003c092251316000001"), - Pair("oppai", "5564674c719a1686396e9324"), - Pair("oral", "4f00e00cc092251316000016"), - Pair("oral", "5564674c719a1686396e9323"), - Pair("orc", "56b8e4ae719a160f1970aa52"), - Pair("ore monogatari", "56b8e65b719a160f1970bfe3"), - Pair("ore no natsuyasumi", "5731d50d719a160bac0aa63c"), - Pair("oreichigo", "573b6453719a1629609e6128"), - Pair("oretto", "56d4d529719a16848b4ccb05"), - Pair("orgasm denial", "56b8e488719a160f1970a885"), - Pair("osananajimi", "4f03e821c092256d810079c2"), - Pair("osananajimi", "5564678f719a1686396e9441"), - Pair("oshimi shuuzou", "584a253d719a168a1c1bbcc7"), - Pair("oshiri", "52f863de45b9efa6e8b7e98f"), - Pair("oshiri", "556467ee719a1686396e9552"), - Pair("otonano gu-wa", "5760a269719a161c54370917"), - Pair("out of order", "56b8e527719a160f1970b0d1"), - Pair("oyakodon", "5794b4cf719a167e4baa78e6"), - Pair("paipan", "4f01f2e1c092253ba7007537"), - Pair("paizuri", "4f00e05dc09225131600008f"), - Pair("paizuri", "5564674f719a1686396e933e"), - Pair("pandacorya", "57ee0680719a16345f09c67d"), - Pair("pantyjob", "56b8e4b8719a160f1970aad2"), - Pair("parasite", "56d4ba09719a164632263e8f"), - Pair("pasties", "56b8e955719a160f1970e5ef"), - Pair("pattycake", "57f4f25f719a168e357718ed"), - Pair("pecan", "57160ace719a1695b38aad23"), - Pair("pedocchi", "56f0a1ab719a166ee74a25ac"), - Pair("pegging", "53c596d645b9ef2b85e38e9c"), - Pair("pegging", "556467b6719a1686396e94ae"), - Pair("peko pekoyama", "570cb4c6719a16861b23fe45"), - Pair("petrification", "56b8e41b719a160f1970a394"), - Pair("pettanko", "4f00e00cc092251316000017"), - Pair("pettanko", "5564675e719a1686396e939b"), - Pair("phone sex", "56c22a9e719a168abf0d0e57"), - Pair("piercing", "56b8e3c3719a160ccaf2eba5"), - Pair("pig", "56cb7e28719a161b35a69ff1"), - Pair("pig girl", "56c227f4719a168abf0ceeb8"), - Pair("pig man", "56c2282d719a168abf0cf110"), - Pair("pillory", "56c22970719a168abf0cff72"), - Pair("pirate", "5716094b719a1695b38aa1a4"), - Pair("pirokobo", "56c22b7d719a168abf0d1a19"), - Pair("piss drinking", "56c22a9e719a168abf0d0e58"), - Pair("plant girl", "56b8e527719a160f1970b0cd"), - Pair("plico", "56d4b955719a164632263676"), - Pair("pole dancing", "5715d1bc719a16156436fac1"), - Pair("policeman", "56b8e6b2719a160f1970c422"), - Pair("policewoman", "56cb632b719a167c4e9e9dd9"), - Pair("ponkotsudou", "56c228ac719a168abf0cf68f"), - Pair("poor grammar", "56b8e66c719a160f1970c0e0"), - Pair("porunamin c", "584f6b3f719a162148699f10"), - Pair("possession", "56b8e5f5719a160f1970ba8c"), - Pair("poteto dango", "56c22d2e719a168abf0d2ede"), - Pair("praseodym", "56cb7eb8719a161b35a6a5a7"), - Pair("pregnant", "541616f645b9efe716f55844"), - Pair("pregnant", "556467fa719a1686396e9580"), - Pair("prehensile hair", "56c22dc1719a168abf0d365d"), - Pair("priest", "571f5fcd719a167a10f07886"), - Pair("principal kuno", "56de2c76719a166fd66fc545"), - Pair("prolapse", "56b8e907719a160f1970e22e"), - Pair("prostate massage", "56b8e67d719a160f1970c187"), - Pair("pubic stubble", "56e749f3719a165d4fe16a97"), - Pair("public", "4f03a91cc092256d8100008b"), - Pair("public use", "56b8e907719a160f1970e22d"), - Pair("rabbit", "56c228bb719a168abf0cf735"), - Pair("raccoon girl", "56e7498f719a165d4fe1663d"), - Pair("raichi hoshimiya", "56f9e988719a164395bc08fb"), - Pair("rakuen tsuihou", "56b8ead0719a160f1970f916"), - Pair("random", "4f00e050c092251316000078"), - Pair("random", "5564680d719a1686396e95ce"), - Pair("randoseru", "56b8e8a0719a160f1970dcad"), - Pair("rat park", "56b8e7d9719a160f1970d340"), - Pair("raw", "54c81f9045b9efce8d0fd11a"), - Pair("redraw", "56cb63b9719a167c4e9ea57e"), - Pair("reika hayami", "5835b5fe719a168d2c178127"), - Pair("rem", "577ca70a719a169cd4767aff"), - Pair("replaced", "56b8e374719a160ccaf2e68c"), - Pair("reptile", "5792ba8c719a162fe1be0a14"), - Pair("retoree", "56cb80ab719a161b35a6ba6a"), - Pair("rewrite", "56cb62e5719a167c4e9e9a2d"), - Pair("reyshi", "56cb7eb8719a161b35a6a5a8"), - Pair("rian", "573b65c1719a1629609e728b"), - Pair("ricky-tick", "574dd9d6719a16606128e462"), - Pair("rimjob", "56b8e452719a160f1970a5e1"), - Pair("rina fujimoto", "570cb48f719a16861b23fb83"), - Pair("rindoh", "56b8ecee719a167b52e95a1b"), - Pair("ring memo", "56d4d529719a16848b4ccb07"), - Pair("rinko yamato", "56b8e65b719a160f1970bfe4"), - Pair("risa shirakaba", "57035c04719a161297661b21"), - Pair("robot", "56b8e891719a160f1970dbe1"), - Pair("rokuji", "56f0a13f719a166ee74a2063"), - Pair("rumiko chie", "573b66bc719a1629609e8063"), - Pair("ryoattoryo", "582fc748719a16491f6a9cdc"), - Pair("ryokurin", "57b30750719a1606ccdc81a0"), - Pair("ryona", "56b8e600719a160f1970bad3"), - Pair("saimin pikatto house", "5801e90e719a16309717beec"), - Pair("saitou miya", "585a4ba8719a16178f4f0270"), - Pair("saji-pen", "57287fb3719a1639c2c86d9d"), - Pair("sakaki miya", "57f6982d719a16796a496723"), - Pair("sakkat", "573b6507719a1629609e6952"), - Pair("sakura shirou", "56c22861719a168abf0cf335"), - Pair("sakurabobu", "56b8ea1b719a160f1970f07e"), - Pair("saliva", "5716095f719a1695b38aa21e"), - Pair("sample", "56c22ddb719a168abf0d37da"), - Pair("sangokushi puzzle taisen", "577c6fe0719a1623436f0e11"), - Pair("sanryuu kaigishitsu", "5744bac1719a162b60f10949"), - Pair("sashilot", "56b8ea99719a160f1970f639"), - Pair("sautsu", "57bb44da719a169ef398c4ad"), - Pair("scanmark", "56c22b4a719a168abf0d17c5"), - Pair("scar", "56b8e3c7719a160ccaf2ebfd"), - Pair("scarlet beriko", "5864833e719a1612a25cf783"), - Pair("scathach", "57033ffd719a16216ffcd220"), - Pair("schoolboy", "56e74ad4719a165d4fe17449"), - Pair("schoolboy uniform", "56b8e3a9719a160ccaf2e9e2"), - Pair("schoolgirl", "4f00e05dc092251316000090"), - Pair("schoolgirl", "5564674c719a1686396e9326"), - Pair("schoolgirl uniform", "56b8e36c719a160ccaf2e5f9"), - Pair("scrotal lingerie", "573b6438719a1629609e6020"), - Pair("selfcest", "56b8e78d719a160f1970cf78"), - Pair("sendai oni", "574e1095719a164c3b023814"), - Pair("senhime", "570cb38f719a16861b23f230"), - Pair("shared senses", "56cb61f5719a167c4e9e8f1a"), - Pair("shibainu lab", "57d845b5719a164697a9b292"), - Pair("shibari", "4f00e691c092252206000249"), - Pair("shikniful", "56cb8115719a161b35a6bf71"), - Pair("shima shuu", "56b8e4c0719a160f1970ab46"), - Pair("shimapan", "4f00e0c3c0922515ab000002"), - Pair("shimoyake", "56c22a9e719a168abf0d0e54"), - Pair("shinna", "57b30750719a1606ccdc81a1"), - Pair("shino kuribayashi", "56c22b7d719a168abf0d1a1a"), - Pair("shinseidaiki", "57eeaf56719a163380236b8b"), - Pair("shishioan", "57fe48ee719a16206a91c4b7"), - Pair("shitori", "56e74a59719a165d4fe16ed2"), - Pair("shota", "5569ad57719a16102373a69f"), - Pair("shotacon", "56b8e3c3719a160ccaf2eba7"), - Pair("show by rock", "56cb80ab719a161b35a6ba69"), - Pair("shrinking", "56b8e41b719a160f1970a392"), - Pair("sieyarelow", "56b8e7b9719a160f1970d1b8"), - Pair("sinseong modogi", "5715eca8719a161780dbd1b8"), - Pair("sister", "56b8e41b719a160f1970a387"), - Pair("skinsuit", "56b8e80e719a160f1970d5a6"), - Pair("slime", "56b8e7b9719a160f1970d1b7"), - Pair("slug", "56cb7f7e719a161b35a6aedd"), - Pair("small breasts", "56b8e36c719a160ccaf2e5fb"), - Pair("smell", "56b8e3ae719a160ccaf2ea33"), - Pair("smoking", "58501400719a165eaaf83a1a"), - Pair("snake", "5769c001719a167bb7f3c0b0"), - Pair("snake girl", "56b8e527719a160f1970b0cf"), - Pair("snuff", "56b8e337719a1608ab978eef"), - Pair("socks", "545ec3a945b9ef70f033c4fb"), - Pair("socks", "55646787719a1686396e9426"), - Pair("sole dickgirl", "56b8e3b8719a160ccaf2eae9"), - Pair("sole female", "56b8e33f719a1608ab978f1d"), - Pair("sole male", "56b8e33f719a1608ab978f1e"), - Pair("solo action", "56b8e469719a160f1970a70d"), - Pair("sophie houjou", "571f2766719a16067f34ae64"), - Pair("sori", "56cb6317719a167c4e9e9cd4"), - Pair("soul calibur", "57287e49719a1639c2c85c42"), - Pair("southbamboo", "56c22b00719a168abf0d13be"), - Pair("spats", "555165b8719a168965a40ffa"), - Pair("speculum", "56c22de0719a168abf0d3818"), - Pair("spicaya", "587f35b7719a1680ba7f1995"), - Pair("spider", "5876a3dd719a1614eb5ac658"), - Pair("spider girl", "56b8e527719a160f1970b0d0"), - Pair("spread", "569d758e719a1695484b1082"), - Pair("squid boy", "5808d4fa719a1655df48fc14"), - Pair("squid girl", "56b8e7b2719a160f1970d15c"), - Pair("squirrel girl", "56b8e33f719a1608ab978f1b"), - Pair("steward", "5715ecc6719a161780dbd235"), - Pair("stewardess", "570c9628719a16054fd7be1f"), - Pair("stockings", "4f00e032c09225131600004a"), - Pair("stockings", "5564674c719a1686396e9325"), - Pair("stomach deformation", "56b8e337719a1608ab978ef5"), - Pair("story arc", "56b8e370719a160ccaf2e641"), - Pair("stretching", "570c96d9719a16054fd7c3c6"), - Pair("stuck in wall", "56b8e726719a160f1970cacf"), - Pair("student", "4f03c4ddc092256d81003447"), - Pair("student council", "56d4d655719a16848b4cd8ea"), - Pair("studio crimson", "56f0bc75719a16856dd1f6c9"), - Pair("studio onion", "56b8eade719a160f1970f9cb"), - Pair("suguru kamoshida", "588870b0719a165bccf4115d"), - Pair("sunahama nosame", "573b63fb719a1629609e5d9e"), - Pair("sundress", "56b8e7d5719a160f1970d30c"), - Pair("sunglasses", "56b8e376719a160ccaf2e69f"), - Pair("suzuen", "57fe48ee719a16206a91c4b8"), - Pair("swallow sky", "56b8e6aa719a160f1970c3af"), - Pair("sweating", "56b8e305719a1607fceb7b6c"), - Pair("swimsuit", "54c052a445b9efc62468bbed"), - Pair("swimsuit", "55646764719a1686396e93b3"), - Pair("swinging", "56b8e378719a160ccaf2e6af"), - Pair("syoko hoshi", "570cb48f719a16861b23fb84"), - Pair("syounen kouraku", "5715d113719a16156436f75b"), - Pair("syringe", "56b8e337719a1608ab978ef0"), - Pair("syunzo", "56c22be0719a168abf0d1ee3"), - Pair("table masturbation", "56b8e8f8719a160f1970e15e"), - Pair("tadashi hamada", "5728b934719a16117c8d17c5"), - Pair("tagane", "5794b4cf719a167e4baa78e5"), - Pair("taichi yaegashi", "56b8e56e719a160f1970b43b"), - Pair("tail blue", "574dd92b719a16606128de91"), - Pair("tail plug", "57936393719a1660a6b00805"), - Pair("tail yellow", "574dd92b719a16606128de90"), - Pair("tailjob", "56b8e726719a160f1970cacc"), - Pair("tairame", "573b2bc1719a16738c41823a"), - Pair("taji", "57d2028d719a1699d9a55957"), - Pair("takakura row", "58316d23719a162fb6df6091"), - Pair("takanaga hinako", "571f5fcd719a167a10f07887"), - Pair("takano yumi", "57287de7719a1639c2c85733"), - Pair("takayama non", "573b66ac719a1629609e7f3c"), - Pair("takayamanon", "573b66ac719a1629609e7f3d"), - Pair("takeo gouda", "56b8e65b719a160f1970bfe2"), - Pair("takewan", "586e121e719a1621d850fb57"), - Pair("takoyaki yoshi", "57be907c719a1610094a299b"), - Pair("tall girl", "56b8e378719a160ccaf2e6b0"), - Pair("tall man", "56b8e33f719a1608ab978f1a"), - Pair("tamagohan", "585a4ba8719a16178f4f0271"), - Pair("tamokuteki kuukan", "56cb7ead719a161b35a6a532"), - Pair("tankoubon", "56b8e378719a160ccaf2e6ae"), - Pair("tanlines", "4f00e7bdc0922522060003b2"), - Pair("tanlines", "556467fa719a1686396e957e"), - Pair("tanpopo shunmaru", "56c229ec719a168abf0d05cc"), - Pair("tanukine", "56f9ea32719a164395bc0ebc"), - Pair("taoi", "57735090719a165a1294d288"), - Pair("tasu", "5715ec9b719a161780dbd188"), - Pair("tatsuhide", "56e74982719a165d4fe16599"), - Pair("tatsunokosso", "56d4b99f719a164632263958"), - Pair("teacher", "4f00e084c0922513160000af"), - Pair("teacher", "5564674f719a1686396e9340"), - Pair("tejina senpai", "57e37a6c719a166f0f8735af"), - Pair("tekoki", "4f01e19ec092253ba700585f"), - Pair("tentacles", "4f00e120c0922515ab0000a6"), - Pair("terasu", "5731d615719a160bac0aaf4c"), - Pair("the jinshan", "57f59b21719a167b1dbd5d99"), - Pair("thigh high boots", "56b8e41a719a160f1970a384"), - Pair("tiara", "56b8e580719a160f1970b53b"), - Pair("tickling", "56b8e726719a160f1970cacd"), - Pair("tights", "56f9e9ae719a164395bc09fa"), - Pair("tilia", "56cb7e19719a161b35a69f3d"), - Pair("tocori", "56de2bed719a166fd66fbf87"), - Pair("tomboy", "4f00e910c092252206000534"), - Pair("tomboy", "556467ca719a1686396e94e0"), - Pair("tomgirl", "56b8e3c3719a160ccaf2eba6"), - Pair("tomoya aki", "56cb7e0f719a161b35a69ece"), - Pair("tonsuke", "56d4d616719a16848b4cd5b8"), - Pair("tooku no mura", "56e74b98719a165d4fe17c7f"), - Pair("tooyama hirohito", "57be9073719a1610094a2934"), - Pair("tori himemiya", "5884273c719a162bbe3ccd8e"), - Pair("tottoko mtarou", "589c36ab719a1664dd43105f"), - Pair("toudori no su", "5797ac7b719a164c63580b78"), - Pair("toys", "4f00e54ec09225220600009d"), - Pair("toys", "5564680a719a1686396e95c2"), - Pair("tracksuit", "56b8e36c719a160ccaf2e5fa"), - Pair("trampling", "589e853d719a1631d15960a6"), - Pair("trans", "552ad5d6719a165e97ce8d0a"), - Pair("transformation", "56b8e36b719a160ccaf2e5ee"), - Pair("trap", "4f00e041c092251316000061"), - Pair("triple anal", "56d4d5bf719a16848b4cd1b6"), - Pair("triple penetration", "56b8e337719a1608ab978eed"), - Pair("triple vaginal", "5793634e719a1660a6b004d3"), - Pair("try hougen", "571f6213719a167a10f089d1"), - Pair("ts neinenki", "57d7f14c719a162806043c7d"), - Pair("tsuchinoko", "57320cc3719a16693cf46bcd"), - Pair("tsukimoto kizuki", "56c22b35719a168abf0d167f"), - Pair("tsukishima mist", "56b8e647719a160f1970bec1"), - Pair("tsundere", "4f00e5b1c09225220600012c"), - Pair("tsundere", "55646778719a1686396e93f6"), - Pair("ttf threesome", "56b8e8de719a160f1970dfe4"), - Pair("ttm threesome", "56b8e968719a160f1970e6e9"), - Pair("tube", "56b8e4bc719a160f1970ab0f"), - Pair("tunakan", "5760a4a5719a161c54372362"), - Pair("tutor", "56b8e5b1719a160f1970b751"), - Pair("twoframe", "5715d113719a16156436f75c"), - Pair("tyria", "56cb7e19719a161b35a69f3e"), - Pair("ueno tomoki", "577c6deb719a1623436efa82"), - Pair("unbirth", "56b8e527719a160f1970b0ce"), - Pair("uncensored", "4f00e175c0922515ab00011f"), - Pair("uncensored", "55646778719a1686396e93f8"), - Pair("uncle", "56b8e6e7719a160f1970c70b"), - Pair("underwater", "56b8e726719a160f1970caca"), - Pair("unihoge", "5895f386719a163d654bb665"), - Pair("unusual pupils", "56b8e4ad719a160f1970aa47"), - Pair("unusual teeth", "56cb7fee719a161b35a6b278"), - Pair("urination", "56b8e4b5719a160f1970aaa7"), - Pair("usagi no shippo", "56e74982719a165d4fe1659a"), - Pair("usapyon", "56f0bd67719a16856dd20197"), - Pair("usui hon hitori roudoku kai", "56b8e647719a160f1970bec3"), - Pair("uzuki", "56b8e42f719a160f1970a499"), - Pair("vacbed", "56de2b2d719a166fd66fb8f5"), - Pair("vaginal sticker", "586fb7fd719a168eed7908bb"), - Pair("vanilla", "4f00e003c092251316000002"), - Pair("vanilla", "5564674f719a1686396e933f"), - Pair("vila", "57287dd9719a1639c2c856ac"), - Pair("virginity", "56b8e36a719a160ccaf2e5d6"), - Pair("visual she", "5731d5c5719a160bac0aacd7"), - Pair("vivace", "56c22ac7719a168abf0d10ed"), - Pair("vuttiya", "588578bf719a168a49752b65"), - Pair("waiter", "579854ee719a1648fd01151e"), - Pair("waiter", "579854ee719a1648fd01151c"), - Pair("washizuka sho", "571f6011719a167a10f07a9c"), - Pair("watermarked", "57287fad719a1639c2c86d5c"), - Pair("webtoon", "579eec8e719a169e8bb534ce"), - Pair("weight gain", "573b666a719a1629609e7ad9"), - Pair("western", "5590b089719a167a01f35950"), - Pair("wet clothes", "5805894b719a160616b5863f"), - Pair("whip", "56c22cf2719a168abf0d2bd5"), - Pair("wings", "56b8e5d8719a160f1970b905"), - Pair("wolf", "56b8e598719a160f1970b63b"), - Pair("wolf boy", "56cb7f41719a161b35a6abd0"), - Pair("wolf girl", "56b8e65f719a160f1970c026"), - Pair("wooden horse", "56b8e661719a160f1970c04c"), - Pair("worm", "56e766de719a16989a47c8df"), - Pair("wormhole", "56fa053d719a1669f2a7bbd0"), - Pair("wrestling", "56b8e7b2719a160f1970d165"), - Pair("x-ray", "54ee6537719a1680a4d18331"), - Pair("x-ray", "55646787719a1686396e9428"), - Pair("yakan", "57adc14c719a16504d6868c2"), - Pair("yamamoto doujin", "577ca78a719a169cd4767f72"), - Pair("yamori misaki", "570cb497719a16861b23fbff"), - Pair("yandere", "4f00e5c8c092252206000162"), - Pair("yang-do", "58180c3e719a168510b55736"), - Pair("yayoi", "56c22b10719a168abf0d149d"), - Pair("yomogi", "57bd3f1a719a163e0da0b0c9"), - Pair("yoshizuki minoru", "5731d5c5719a160bac0aacd8"), - Pair("yoyokkun", "57ee0678719a16345f09c62f"), - Pair("yshtola", "5716094b719a1695b38aa1a5"), - Pair("yui komori", "570cb224719a16861b23e688"), - Pair("yukijirushi", "586b1ac0719a161ec7d1a9ef"), - Pair("yukino", "57f000d5719a162d156df669"), - Pair("yukowa kari", "56f0a18f719a166ee74a2464"), - Pair("yurarin", "5886ca3e719a167f77f68a2c"), - Pair("yuri", "55646784719a1686396e9418"), - Pair("yuuyake croissant", "585e401d719a1636e2d6b79e"), - Pair("zeitaku zanmai", "56c22b10719a168abf0d149e"), - Pair("zenjidou momiyama", "57f0fdcf719a163c0daba6a8"), - Pair("zero gravity", "5619c9b2719a16502106c0b2"), - Pair("zooey", "5875fb34719a16738b9f43ac") - ) -} diff --git a/src/en/pururin/AndroidManifest.xml b/src/en/pururin/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/pururin/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/pururin/build.gradle b/src/en/pururin/build.gradle deleted file mode 100644 index c65df4391..000000000 --- a/src/en/pururin/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Pururin' - pkgNameSuffix = 'en.pururin' - extClass = '.Pururin' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/pururin/res/mipmap-hdpi/ic_launcher.png b/src/en/pururin/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c656b5be7..000000000 Binary files a/src/en/pururin/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/pururin/res/mipmap-mdpi/ic_launcher.png b/src/en/pururin/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 163ee75e1..000000000 Binary files a/src/en/pururin/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/pururin/res/mipmap-xhdpi/ic_launcher.png b/src/en/pururin/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6d0d55f00..000000000 Binary files a/src/en/pururin/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/pururin/res/mipmap-xxhdpi/ic_launcher.png b/src/en/pururin/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ebc742eaa..000000000 Binary files a/src/en/pururin/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/pururin/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/pururin/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ee1a1061c..000000000 Binary files a/src/en/pururin/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/pururin/res/web_hi_res_512.png b/src/en/pururin/res/web_hi_res_512.png deleted file mode 100644 index 6bced12e7..000000000 Binary files a/src/en/pururin/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/pururin/src/eu/kanade/tachiyomi/extension/en/pururin/Pururin.kt b/src/en/pururin/src/eu/kanade/tachiyomi/extension/en/pururin/Pururin.kt deleted file mode 100644 index e60408519..000000000 --- a/src/en/pururin/src/eu/kanade/tachiyomi/extension/en/pururin/Pururin.kt +++ /dev/null @@ -1,225 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.pururin - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -@Nsfw -class Pururin : ParsedHttpSource() { - - override val name = "Pururin" - - override val baseUrl = "https://pururin.io" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun latestUpdatesSelector() = "div.container div.row-gallery a" - - override fun latestUpdatesRequest(page: Int): Request { - return if (page == 1) { - GET(baseUrl, headers) - } else { - GET("$baseUrl/browse/newest?page=$page", headers) - } - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("div.title").text() - manga.thumbnail_url = element.select("img.card-img-top").attr("abs:data-src") - - return manga - } - - override fun latestUpdatesNextPageSelector() = "ul.pagination a.page-link[rel=next]" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.box.box-gallery") - val manga = SManga.create() - val genres = mutableListOf<String>() - - document.select("tr:has(td:contains(Contents)) li").forEach { element -> - val genre = element.text() - genres.add(genre) - } - - manga.title = infoElement.select("h1").text() - manga.author = infoElement.select("tr:has(td:contains(Artist)) a").attr("title") - manga.artist = infoElement.select("tr:has(td:contains(Circle)) a").text() - manga.status = SManga.COMPLETED - manga.genre = genres.joinToString(", ") - manga.thumbnail_url = document.select("div.cover-wrapper v-lazy-image").attr("abs:src") - - manga.description = getDesc(document) - - return manga - } - - private fun getDesc(document: Document): String { - val infoElement = document.select("div.box.box-gallery") - val uploader = infoElement.select("tr:has(td:contains(Uploader)) .user-link")?.text() - val pages = infoElement.select("tr:has(td:contains(Pages)) td:eq(1)").text() - val ratingCount = infoElement.select("tr:has(td:contains(Ratings)) span[itemprop=\"ratingCount\"]")?.attr("content") - - val rating = infoElement.select("tr:has(td:contains(Ratings)) gallery-rating").attr(":rating")?.toFloatOrNull()?.let { - if (it > 5.0f) minOf(it, 5.0f) // cap rating to 5.0 for rare cases where value exceeds 5.0f - else it - } - - val multiDescriptions = listOf( - "Convention", - "Parody", - "Circle", - "Category", - "Character", - "Language" - ).map { it to infoElement.select("tr:has(td:contains($it)) a").map { v -> v.text() } } - .filter { !it.second.isNullOrEmpty() } - .map { "${it.first}: ${it.second.joinToString()}" } - - val descriptions = listOf( - multiDescriptions.joinToString("\n\n"), - uploader?.let { "Uploader: $it" }, - pages?.let { "Pages: $it" }, - rating?.let { "Ratings: $it" + (ratingCount?.let { c -> " ($c ratings)" } ?: "") } - ) - - return descriptions.joinToString("\n\n") - } - - override fun chapterListParse(response: Response) = with(response.asJsoup()) { - val mangaInfoElements = this.select(".table-gallery-info tr td:first-child").map { - it.text() to it.nextElementSibling() - }.toMap() - - val chapters = this.select(".table-collection tbody tr") - if (!chapters.isNullOrEmpty()) - chapters.map { - val details = it.select("td") - SChapter.create().apply { - chapter_number = details[0].text().removePrefix("#").toFloat() - name = details[1].select("a").text() - setUrlWithoutDomain(details[1].select("a").attr("href")) - - if (it.hasClass("active") && mangaInfoElements.containsKey("Scanlator")) - scanlator = mangaInfoElements.getValue("Scanlator").select("li a")?.joinToString { s -> s.text() } - } - } - else - listOf( - SChapter.create().apply { - name = "Chapter" - setUrlWithoutDomain(response.request().url().toString()) - - if (mangaInfoElements.containsKey("Scanlator")) - scanlator = mangaInfoElements.getValue("Scanlator").select("li a")?.joinToString { s -> s.text() } - } - ) - } - - override fun pageListRequest(chapter: SChapter): Request = GET( - "$baseUrl${chapter.url.let { - it.substringAfterLast("/").let { titleUri -> - it.replace(titleUri, "01/$titleUri") - }.replace("gallery", "read") - }}" - ) - - override fun chapterListSelector(): String = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - val galleryInfo = document.select("gallery-read").toString().substringAfter('{').substringBefore('}') - val id = galleryInfo.substringAfter("id":").substringBefore(',') - val total: Int = (galleryInfo.substringAfter("total_pages":").substringBefore(',')).toInt() - - for (i in 1..total) { - pages.add(Page(i, "", "https://cdn.pururin.io/assets/images/data/$id/$i.jpg")) - } - - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/browse/most-popular?page=$page", headers) - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - private lateinit var tagUrl: String - - // TODO: Additional filter options, specifically the type[] parameter - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = "$baseUrl/search?q=$query&page=$page" - - if (query.isBlank()) { - filters.forEach { filter -> - when (filter) { - is Tag -> { - url = if (page == 1) { - "$baseUrl/search/tag?q=${filter.state}&type[]=3" // "Contents" tag - } else { - "$tagUrl?page=$page" - } - } - } - } - } - - return GET(url, headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - return if (response.request().url().toString().contains("tag?")) { - response.asJsoup().select("table.table tbody tr a:first-of-type").attr("abs:href").let { - if (it.isNotEmpty()) { - tagUrl = it - super.searchMangaParse(client.newCall(GET(tagUrl, headers)).execute()) - } else { - MangasPage(emptyList(), false) - } - } - } else { - super.searchMangaParse(response) - } - } - - override fun searchMangaSelector() = latestUpdatesSelector() - - override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - Tag("Tag") - ) - - private class Tag(name: String) : Filter.Text(name) -} diff --git a/src/en/questionablecontent/AndroidManifest.xml b/src/en/questionablecontent/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/questionablecontent/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/questionablecontent/build.gradle b/src/en/questionablecontent/build.gradle deleted file mode 100644 index 525dc0e06..000000000 --- a/src/en/questionablecontent/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Questionable Content' - pkgNameSuffix = 'en.questionablecontent' - extClass = '.QuestionableContent' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/questionablecontent/res/mipmap-hdpi/ic_launcher.png b/src/en/questionablecontent/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9c4203670..000000000 Binary files a/src/en/questionablecontent/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/questionablecontent/res/mipmap-mdpi/ic_launcher.png b/src/en/questionablecontent/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 59a714f43..000000000 Binary files a/src/en/questionablecontent/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/questionablecontent/res/mipmap-xhdpi/ic_launcher.png b/src/en/questionablecontent/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3a7ca1ef3..000000000 Binary files a/src/en/questionablecontent/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/questionablecontent/res/mipmap-xxhdpi/ic_launcher.png b/src/en/questionablecontent/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6efb90894..000000000 Binary files a/src/en/questionablecontent/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/questionablecontent/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/questionablecontent/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 536bdb880..000000000 Binary files a/src/en/questionablecontent/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/questionablecontent/res/web_hi_res_512.png b/src/en/questionablecontent/res/web_hi_res_512.png deleted file mode 100644 index 3514279ba..000000000 Binary files a/src/en/questionablecontent/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/questionablecontent/src/eu/kanade/tachiyomi/extension/en/questionablecontent/QuestionableContent.kt b/src/en/questionablecontent/src/eu/kanade/tachiyomi/extension/en/questionablecontent/QuestionableContent.kt deleted file mode 100644 index 46c034a6b..000000000 --- a/src/en/questionablecontent/src/eu/kanade/tachiyomi/extension/en/questionablecontent/QuestionableContent.kt +++ /dev/null @@ -1,117 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.questionablecontent - -import android.app.Application -import android.content.SharedPreferences -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.online.ParsedHttpSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.util.Date - -class QuestionableContent : ParsedHttpSource() { - - override val name = "Questionable Content" - - override val baseUrl = "https://www.questionablecontent.net" - - override val lang = "en" - - override val supportsLatest = false - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val manga = SManga.create().apply { - title = "Questionable Content" - artist = "Jeph Jacques" - author = "Jeph Jacques" - status = SManga.ONGOING - url = "/archive.php" - description = "An internet comic strip about romance and robots" - thumbnail_url = "https://i.ibb.co/ZVL9ncS/qc-teh.png" - initialized = true - } - - return Observable.just(MangasPage(arrayListOf(manga), false)) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun fetchMangaDetails(manga: SManga) = fetchPopularManga(1).map { it.mangas.first() } - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = super.chapterListParse(response).distinct() - // set date of most recent chapter to today, use SharedPreferences so that we aren't changing it needlessly on refreshes - if (chapters.first().url != preferences.getString(LAST_CHAPTER_URL, null)) { - val date = Date().time - chapters.first().date_upload = date - preferences.edit().putString(LAST_CHAPTER_URL, chapters.first().url).apply() - preferences.edit().putLong(LAST_CHAPTER_DATE, date).apply() - } else { - chapters.first().date_upload = preferences.getLong(LAST_CHAPTER_DATE, 0L) - } - return chapters - } - - override fun chapterListSelector() = - """div#container a[href^="view.php?comic="]""" - - override fun chapterFromElement(element: Element): SChapter { - val urlregex = - """view\.php\?comic=(.*)""".toRegex() - val chapterUrl = element.attr("href") - val number = urlregex.find(chapterUrl)!!.groupValues[1] - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain("/$chapterUrl") - chapter.name = element.text() - chapter.chapter_number = number.toFloat() - return chapter - } - - override fun pageListParse(document: Document) = document.select("#strip").mapIndexed { i, element -> Page(i, "", baseUrl + element.attr("src").substring(1)) } - - companion object { - private const val LAST_CHAPTER_URL = "QC_LAST_CHAPTER_URL" - private const val LAST_CHAPTER_DATE = "QC_LAST_CHAPTER_DATE" - } - - override fun imageUrlParse(document: Document) = throw Exception("Not used") - - override fun popularMangaSelector(): String = throw Exception("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun searchMangaSelector(): String = throw Exception("Not used") - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - - override fun popularMangaNextPageSelector(): String? = throw Exception("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - override fun latestUpdatesSelector(): String = throw Exception("Not used") -} diff --git a/src/en/rainofsnow/AndroidManifest.xml b/src/en/rainofsnow/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/rainofsnow/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/rainofsnow/build.gradle b/src/en/rainofsnow/build.gradle deleted file mode 100644 index 85d547484..000000000 --- a/src/en/rainofsnow/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Rain Of Snow' - pkgNameSuffix = 'en.rainofsnow' - extClass = '.RainOfSnow' - extVersionCode = 5 - libVersion = '1.2' - containsNsfw = false -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/rainofsnow/res/mipmap-hdpi/ic_launcher.png b/src/en/rainofsnow/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a7cc0ebcd..000000000 Binary files a/src/en/rainofsnow/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/rainofsnow/res/mipmap-mdpi/ic_launcher.png b/src/en/rainofsnow/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 138486f1a..000000000 Binary files a/src/en/rainofsnow/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/rainofsnow/res/mipmap-xhdpi/ic_launcher.png b/src/en/rainofsnow/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 87b764205..000000000 Binary files a/src/en/rainofsnow/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/rainofsnow/res/mipmap-xxhdpi/ic_launcher.png b/src/en/rainofsnow/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 10a6226c9..000000000 Binary files a/src/en/rainofsnow/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/rainofsnow/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/rainofsnow/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c5f293043..000000000 Binary files a/src/en/rainofsnow/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/rainofsnow/res/web_hi_res_512.png b/src/en/rainofsnow/res/web_hi_res_512.png deleted file mode 100644 index 8cf0ceb39..000000000 Binary files a/src/en/rainofsnow/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/rainofsnow/src/eu/kanade/tachiyomi/extension/en/rainofsnow/RainOfSnow.kt b/src/en/rainofsnow/src/eu/kanade/tachiyomi/extension/en/rainofsnow/RainOfSnow.kt deleted file mode 100644 index 3da7d6513..000000000 --- a/src/en/rainofsnow/src/eu/kanade/tachiyomi/extension/en/rainofsnow/RainOfSnow.kt +++ /dev/null @@ -1,129 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.rainofsnow - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -open class RainOfSnow() : ParsedHttpSource() { - - override val name = "Rain Of Snow" - - override val baseUrl = "https://rainofsnow.com" - - override val lang = "en" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/comics/page/$page") - } - - override fun popularMangaSelector() = ".box .minbox" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = element.select("h3 a").attr("abs:href") - manga.title = element.select("h3").text() - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - - override fun popularMangaNextPageSelector() = ".page-numbers .next" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/")!!.newBuilder() - url.addQueryParameter("serchfor", "comics") - url.addQueryParameter("s", query) - return GET(url.build().toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsRequest(manga: SManga): Request { - if (manga.url.startsWith("http")) { - return GET(manga.url, headers) - } - return super.mangaDetailsRequest(manga) - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.title = document.select(".text h2").text() - manga.author = document.select(".vbtcolor1 li:contains(Author) .vt2").text() - manga.genre = document.select(".vbtcolor1 li:contains(Tags) .vt2").text() - manga.description = document.select("#synop p").text() - manga.thumbnail_url = document.select(".imagboca1 img").attr("abs:src") - return manga - } - - override fun chapterListRequest(manga: SManga): Request { - if (manga.url.startsWith("http")) { - return GET(manga.url, headers) - } - return super.chapterListRequest(manga) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "#chapter li" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.url = element.select("a").attr("abs:href") - chapter.name = element.select("a").text() - chapter.date_upload = element.select("small").firstOrNull()?.text() - ?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - var parsedDate = 0L - try { - parsedDate = SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(date)?.time ?: 0L - } catch (e: ParseException) { /*nothing to do, parsedDate is initialized with 0L*/ } - return parsedDate - } - - override fun pageListRequest(chapter: SChapter): Request { - if (chapter.url.startsWith("http")) { - return GET(chapter.url, headers) - } - return super.pageListRequest(chapter) - } - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("[style=display: block;] img").forEachIndexed { index, element -> - add(Page(index, "", element.attr("abs:src"))) - } - } - - override fun latestUpdatesFromElement(element: Element) = throw Exception("Not used") - override fun latestUpdatesNextPageSelector() = throw Exception("Not used") - override fun latestUpdatesRequest(page: Int) = throw Exception("Not used") - override fun latestUpdatesSelector() = throw Exception("Not used") - override fun imageUrlParse(document: Document) = throw Exception("Not used") -} diff --git a/src/en/readcomiconline/AndroidManifest.xml b/src/en/readcomiconline/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/readcomiconline/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/readcomiconline/build.gradle b/src/en/readcomiconline/build.gradle deleted file mode 100644 index c839ec7db..000000000 --- a/src/en/readcomiconline/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ReadComicOnline' - pkgNameSuffix = 'en.readcomiconline' - extClass = '.Readcomiconline' - extVersionCode = 10 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/readcomiconline/res/mipmap-hdpi/ic_launcher.png b/src/en/readcomiconline/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8ed86a9a7..000000000 Binary files a/src/en/readcomiconline/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readcomiconline/res/mipmap-mdpi/ic_launcher.png b/src/en/readcomiconline/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5729b1fe5..000000000 Binary files a/src/en/readcomiconline/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readcomiconline/res/mipmap-xhdpi/ic_launcher.png b/src/en/readcomiconline/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b91895671..000000000 Binary files a/src/en/readcomiconline/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readcomiconline/res/mipmap-xxhdpi/ic_launcher.png b/src/en/readcomiconline/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3159e2302..000000000 Binary files a/src/en/readcomiconline/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readcomiconline/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/readcomiconline/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index eb1335f4c..000000000 Binary files a/src/en/readcomiconline/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readcomiconline/res/web_hi_res_512.png b/src/en/readcomiconline/res/web_hi_res_512.png deleted file mode 100644 index 9894ccb25..000000000 Binary files a/src/en/readcomiconline/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/readcomiconline/src/eu/kanade/tachiyomi/extension/en/readcomiconline/Readcomiconline.kt b/src/en/readcomiconline/src/eu/kanade/tachiyomi/extension/en/readcomiconline/Readcomiconline.kt deleted file mode 100644 index a50a6de04..000000000 --- a/src/en/readcomiconline/src/eu/kanade/tachiyomi/extension/en/readcomiconline/Readcomiconline.kt +++ /dev/null @@ -1,247 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.readcomiconline - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale - -class Readcomiconline : ConfigurableSource, ParsedHttpSource() { - - override val name = "ReadComicOnline" - - override val baseUrl = "https://readcomiconline.li" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - } - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun popularMangaSelector() = "table.listing tr:has(a) td:nth-child(1) a" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/ComicList/MostPopular?page=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/ComicList/LatestUpdate?page=$page", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.text() - } - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "li > a:contains(Next)" - - override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val form = FormBody.Builder().apply { - add("comicName", query) - - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) - is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) } - } - } - } - return POST("$baseUrl/AdvanceSearch", headers, form.build()) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.barContent").first() - - val manga = SManga.create() - manga.artist = infoElement.select("p:has(span:contains(Artist:)) > a").first()?.text() - manga.author = infoElement.select("p:has(span:contains(Writer:)) > a").first()?.text() - manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text() - manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text() - manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.absUrl("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "table.listing tr:gt(1)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { - SimpleDateFormat("MM/dd/yyyy", Locale.getDefault()).parse(it)?.time ?: 0L - } ?: 0 - return chapter - } - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url + "&quality=${qualitypref()}", headers) - - override fun pageListParse(response: Response): List<Page> { - return Regex("""lstImages\.push\("(http.*)"\)""").findAll(response.body()!!.string()) - .toList() - .mapIndexed { i, mr -> Page(i, "", mr.groupValues[1]) } - } - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - private class Status : Filter.TriState("Completed") - private class Genre(name: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - override fun getFilterList() = FilterList( - Status(), - GenreList(getGenreList()) - ) - - // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') - // on https://readcomiconline.li/AdvanceSearch - private fun getGenreList() = listOf( - Genre("Action"), - Genre("Adventure"), - Genre("Anthology"), - Genre("Anthropomorphic"), - Genre("Biography"), - Genre("Children"), - Genre("Comedy"), - Genre("Crime"), - Genre("Drama"), - Genre("Family"), - Genre("Fantasy"), - Genre("Fighting"), - Genre("Graphic Novels"), - Genre("Historical"), - Genre("Horror"), - Genre("Leading Ladies"), - Genre("LGBTQ"), - Genre("Literature"), - Genre("Manga"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Military"), - Genre("Movies & TV"), - Genre("Music"), - Genre("Mystery"), - Genre("Mythology"), - Genre("Personal"), - Genre("Political"), - Genre("Post-Apocalyptic"), - Genre("Psychological"), - Genre("Pulp"), - Genre("Religious"), - Genre("Robots"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-Fi"), - Genre("Slice of Life"), - Genre("Sport"), - Genre("Spy"), - Genre("Superhero"), - Genre("Supernatural"), - Genre("Suspense"), - Genre("Thriller"), - Genre("Vampires"), - Genre("Video Games"), - Genre("War"), - Genre("Western"), - Genre("Zombies") - ) - // Preferences Code - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val qualitypref = androidx.preference.ListPreference(screen.context).apply { - key = QUALITY_PREF_Title - title = QUALITY_PREF_Title - entries = arrayOf("High Quality", "Low Quality") - entryValues = arrayOf("hq", "lq") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(QUALITY_PREF, entry).commit() - } - } - screen.addPreference(qualitypref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val qualitypref = ListPreference(screen.context).apply { - key = QUALITY_PREF_Title - title = QUALITY_PREF_Title - entries = arrayOf("High Quality", "Low Quality") - entryValues = arrayOf("hq", "lq") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(QUALITY_PREF, entry).commit() - } - } - screen.addPreference(qualitypref) - } - - private fun qualitypref() = preferences.getString(QUALITY_PREF, "hq") - - companion object { - private const val QUALITY_PREF_Title = "Image Quality Selector" - private const val QUALITY_PREF = "qualitypref" - } -} diff --git a/src/en/readjump/AndroidManifest.xml b/src/en/readjump/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/readjump/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/readjump/build.gradle b/src/en/readjump/build.gradle deleted file mode 100644 index 1cc72f88e..000000000 --- a/src/en/readjump/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Readjump' - pkgNameSuffix = 'en.readjump' - extClass = '.Readjump' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/readjump/res/mipmap-hdpi/ic_launcher.png b/src/en/readjump/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e4dde8de1..000000000 Binary files a/src/en/readjump/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readjump/res/mipmap-mdpi/ic_launcher.png b/src/en/readjump/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4bf376814..000000000 Binary files a/src/en/readjump/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readjump/res/mipmap-xhdpi/ic_launcher.png b/src/en/readjump/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9b7370a02..000000000 Binary files a/src/en/readjump/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readjump/res/mipmap-xxhdpi/ic_launcher.png b/src/en/readjump/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e4fde2454..000000000 Binary files a/src/en/readjump/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readjump/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/readjump/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a80896a30..000000000 Binary files a/src/en/readjump/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readjump/res/web_hi_res_512.png b/src/en/readjump/res/web_hi_res_512.png deleted file mode 100644 index 6603ce625..000000000 Binary files a/src/en/readjump/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/readjump/src/eu/kanade/tachiyomi/extension/en/readjump/Readjump.kt b/src/en/readjump/src/eu/kanade/tachiyomi/extension/en/readjump/Readjump.kt deleted file mode 100644 index c9ab35fa7..000000000 --- a/src/en/readjump/src/eu/kanade/tachiyomi/extension/en/readjump/Readjump.kt +++ /dev/null @@ -1,198 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.readjump - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.util.Calendar - -class Readjump : ParsedHttpSource() { - - override val name = "Readjump" - - override val baseUrl = "https://readjump.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/mangas", headers) - } - - override fun popularMangaSelector() = "div.h-left a" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("div.hmi-titre").text() - manga.thumbnail_url = element.select("img").attr("abs:src") - - return manga - } - - override fun popularMangaNextPageSelector() = "Not needed" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = mutableListOf<SManga>() - - document.select(latestUpdatesSelector()).map { mangas.add(latestUpdatesFromElement(it)) } - - return MangasPage(mangas.distinctBy { it.url }, false) - } - - override fun latestUpdatesSelector() = "div.h-left > div.home-manga" - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.select("div.hmi-titre a").first().attr("abs:href")) - manga.title = element.select("div.hmi-titre a").first().text() - manga.thumbnail_url = element.select("img").attr("abs:src") - - return manga - } - - override fun latestUpdatesNextPageSelector() = "not needed" - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(1) - - private fun searchMangaParse(response: Response, query: String): MangasPage { - return MangasPage(popularMangaParse(response).mangas.filter { it.title.contains(query, ignoreCase = true) }, false) - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - - document.select("div.mf-info").let { - manga.thumbnail_url = it.select("div.poster img").attr("abs:src") - } - document.select("div#chap-top").let { - manga.title = it.select("div.titre").text() - manga.description = it.select("div.synopsis").text() - } - document.select("div.info").let { - manga.status = parseStatus(it.select("div.sub-i:last-of-type span").text()) - } - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "div.chapitre" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.select("div.ch-right a.chr-button:last-of-type").attr("href")) - chapter.name = element.select("div.ch-left div.chl-titre").text() - chapter.date_upload = parseChapterDate(element.select("div.chl-date").text()) - return chapter - } - - private fun parseChapterDate(date: String): Long { - val value = date.split(" ")[0].toIntOrNull() - - return if (value != null) { - when (date.split(" ")[1]) { - "minute", "minutes" -> Calendar.getInstance().apply { - add(Calendar.MINUTE, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "hour", "hours" -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "day", "days" -> Calendar.getInstance().apply { - add(Calendar.DATE, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "week", "weeks" -> Calendar.getInstance().apply { - add(Calendar.DATE, value * 7 * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "month" -> Calendar.getInstance().apply { - add(Calendar.MONTH, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "year", "years" -> Calendar.getInstance().apply { - add(Calendar.YEAR, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - else -> { - return 0L - } - } - } else { - return 0L - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("div.sc-lel img[id]").forEachIndexed { i, img -> - pages.add(Page(i, "", img.attr("abs:data-src"))) - } - - return pages - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/readm/AndroidManifest.xml b/src/en/readm/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/readm/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/readm/build.gradle b/src/en/readm/build.gradle deleted file mode 100644 index 5148f9455..000000000 --- a/src/en/readm/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ReadM' - pkgNameSuffix = 'en.readm' - extClass = '.ReadM' - extVersionCode = 5 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/readm/res/mipmap-hdpi/ic_launcher.png b/src/en/readm/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 385f23098..000000000 Binary files a/src/en/readm/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readm/res/mipmap-mdpi/ic_launcher.png b/src/en/readm/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2e4292925..000000000 Binary files a/src/en/readm/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readm/res/mipmap-xhdpi/ic_launcher.png b/src/en/readm/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3269366d8..000000000 Binary files a/src/en/readm/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readm/res/mipmap-xxhdpi/ic_launcher.png b/src/en/readm/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e64366c9f..000000000 Binary files a/src/en/readm/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readm/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/readm/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 40fb0c35f..000000000 Binary files a/src/en/readm/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readm/res/web_hi_res_512.png b/src/en/readm/res/web_hi_res_512.png deleted file mode 100644 index 5a751052c..000000000 Binary files a/src/en/readm/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt b/src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt deleted file mode 100644 index f9395c139..000000000 --- a/src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt +++ /dev/null @@ -1,162 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.readm - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.json.JSONObject -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.Calendar -import java.util.concurrent.TimeUnit - -class ReadM : ParsedHttpSource() { - - // Info - override val name: String = "ReadM" - override val baseUrl: String = "https://readm.org" - override val lang: String = "en" - override val supportsLatest: Boolean = true - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build() - - // Popular - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/popular-manga/$page", headers) - override fun popularMangaNextPageSelector(): String? = "div.pagination a:contains(»)" - override fun popularMangaSelector(): String = "div#discover-response li" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:src") - element.select("div.subject-title a").first().apply { - title = this.text().trim() - url = this.attr("href") - } - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest-releases/$page", headers) - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = "ul.latest-updates > li" - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:data-src") - element.select("h2 a").first().apply { - title = this.text().trim() - url = this.attr("href") - } - } - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val formBody = FormBody.Builder() - .add("dataType", "json") - .add("phrase", query) - - val searchHeaders = headers.newBuilder() - .add("X-Requested-With", "XMLHttpRequest") - .add("content-type", "application/x-www-form-urlencoded; charset=UTF-8") - .build() - return POST("$baseUrl/service/search", searchHeaders, formBody.build()) - } - - override fun searchMangaNextPageSelector(): String = throw Exception("Not used") - override fun searchMangaSelector(): String = throw Exception("Not used") - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") - - override fun searchMangaParse(response: Response): MangasPage { - val json = JSONObject(response.body()!!.string()).getJSONArray("manga") - - val manga = (0 until json.length()).asSequence().toList().map { it -> - SManga.create().apply { - val jsonObject = json.getJSONObject(it) - title = jsonObject.getString("title") - url = jsonObject.getString("url") - thumbnail_url = jsonObject.getString("image") - } - } - - return MangasPage(manga, false) - } - - // Details - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - thumbnail_url = document.select("img.series-profile-thumb").attr("abs:src") - title = document.select("h1.page-title").text().trim() - author = document.select("span#first_episode a").text().trim() - artist = document.select("span#last_episode a").text().trim() - description = document.select("div.series-summary-wrapper p").text().trim() - genre = document.select("div.series-summary-wrapper div.item a").joinToString(", ") { it.text().trim() } - status = parseStatus(document.select("div.series-genres .series-status").firstOrNull()?.ownText()) - } - - protected fun parseStatus(element: String?): Int = when { - element == null -> SManga.UNKNOWN - listOf("ongoing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING - listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector(): String = "div.season_start" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.select("a").text() - url = element.select("a").attr("href") - date_upload = parseChapterDate(element.select("td.episode-date").text().trim()) - } - - private fun parseChapterDate(date: String): Long { - val dateWords: List<String> = date.split(" ") - - if (dateWords.size == 2) { - val timeAgo = Integer.parseInt(dateWords[0]) - val calendar = Calendar.getInstance() - - when { - dateWords[1].contains("Minute") -> { - calendar.add(Calendar.MINUTE, -timeAgo) - } - dateWords[1].contains("Hour") -> { - calendar.add(Calendar.HOUR_OF_DAY, -timeAgo) - } - dateWords[1].contains("Day") -> { - calendar.add(Calendar.DAY_OF_YEAR, -timeAgo) - } - dateWords[1].contains("Week") -> { - calendar.add(Calendar.WEEK_OF_YEAR, -timeAgo) - } - dateWords[1].contains("Month") -> { - calendar.add(Calendar.MONTH, -timeAgo) - } - dateWords[1].contains("Year") -> { - calendar.add(Calendar.YEAR, -timeAgo) - } - } - - return calendar.timeInMillis - } - - return 0L - } - - // Pages - - override fun imageUrlParse(document: Document): String = throw Exception("Not Used") - override fun pageListParse(document: Document): List<Page> = document.select("div.ch-images img").mapIndexed { index, element -> - Page(index, "", element.attr("abs:src")) - } -} diff --git a/src/en/readmangatoday/AndroidManifest.xml b/src/en/readmangatoday/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/readmangatoday/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/readmangatoday/build.gradle b/src/en/readmangatoday/build.gradle deleted file mode 100644 index 34842266a..000000000 --- a/src/en/readmangatoday/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ReadMangaToday' - pkgNameSuffix = 'en.readmangatoday' - extClass = '.Readmangatoday' - extVersionCode = 9 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/readmangatoday/res/mipmap-hdpi/ic_launcher.png b/src/en/readmangatoday/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4543a9e78..000000000 Binary files a/src/en/readmangatoday/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmangatoday/res/mipmap-mdpi/ic_launcher.png b/src/en/readmangatoday/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d09530c7d..000000000 Binary files a/src/en/readmangatoday/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmangatoday/res/mipmap-xhdpi/ic_launcher.png b/src/en/readmangatoday/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8550870eb..000000000 Binary files a/src/en/readmangatoday/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmangatoday/res/mipmap-xxhdpi/ic_launcher.png b/src/en/readmangatoday/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7fe1b755f..000000000 Binary files a/src/en/readmangatoday/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmangatoday/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/readmangatoday/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 64ecd487d..000000000 Binary files a/src/en/readmangatoday/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmangatoday/res/web_hi_res_512.png b/src/en/readmangatoday/res/web_hi_res_512.png deleted file mode 100644 index 997617e7f..000000000 Binary files a/src/en/readmangatoday/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/readmangatoday/src/eu/kanade/tachiyomi/extension/en/readmangatoday/Readmangatoday.kt b/src/en/readmangatoday/src/eu/kanade/tachiyomi/extension/en/readmangatoday/Readmangatoday.kt deleted file mode 100644 index 23a24d413..000000000 --- a/src/en/readmangatoday/src/eu/kanade/tachiyomi/extension/en/readmangatoday/Readmangatoday.kt +++ /dev/null @@ -1,239 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.readmangatoday - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.Calendar - -class Readmangatoday : ParsedHttpSource() { - - override val id: Long = 8 - - override val name = "ReadMangaToday" - - override val baseUrl = "https://www.readmng.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient get() = network.cloudflareClient - - /** - * Search only returns data with user-agent and x-requeted-with set - * Referer needed due to some chapters linking images from other domains - */ - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("X-Requested-With", "XMLHttpRequest") - add("Referer", baseUrl) - } - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/hot-manga/$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest-releases/$page", headers) - } - - override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box" - - override fun latestUpdatesSelector() = "div.hot-manga > div.style-grid > div.box" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("div.title > h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - manga.thumbnail_url = element.select("img").attr("src") - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" - - override fun latestUpdatesNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val builder = okhttp3.FormBody.Builder() - builder.add("manga-name", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is TextField -> builder.add(filter.key, filter.state) - is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) - is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) - is GenreList -> filter.state.forEach { genre -> - when (genre.state) { - Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString()) - Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString()) - } - } - } - } - return POST("$baseUrl/service/advanced_search", headers, builder.build()) - } - - override fun searchMangaSelector() = "div.style-list > div.box" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("div.title > h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun searchMangaNextPageSelector() = "div.next-page > a.next" - - override fun mangaDetailsParse(document: Document): SManga { - val detailElement = document.select("div.movie-meta").first() - val genreElement = detailElement.select("dl.dl-horizontal > dd:eq(5) a") - - val manga = SManga.create() - manga.author = document.select("ul.cast-list li.director > ul a").first()?.text() - manga.artist = document.select("ul.cast-list li:not(.director) > ul a").first()?.text() - manga.description = detailElement.select("li.movie-detail").first()?.text() - manga.status = detailElement.select("dl.dl-horizontal > dd:eq(3)").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = detailElement.select("img.img-responsive").first()?.attr("src") - - val genres = mutableListOf<String>() - genreElement?.forEach { genres.add(it.text()) } - manga.genre = genres.joinToString(", ") - - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "ul.chp_lst > li" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.select("span.val").text() - chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - val dateWords: List<String> = date.split(" ") - - if (dateWords.size == 3) { - val timeAgo = Integer.parseInt(dateWords[0]) - val calendar = Calendar.getInstance() - - when { - dateWords[1].contains("Minute") -> { - calendar.add(Calendar.MINUTE, -timeAgo) - } - dateWords[1].contains("Hour") -> { - calendar.add(Calendar.HOUR_OF_DAY, -timeAgo) - } - dateWords[1].contains("Day") -> { - calendar.add(Calendar.DAY_OF_YEAR, -timeAgo) - } - dateWords[1].contains("Week") -> { - calendar.add(Calendar.WEEK_OF_YEAR, -timeAgo) - } - dateWords[1].contains("Month") -> { - calendar.add(Calendar.MONTH, -timeAgo) - } - dateWords[1].contains("Year") -> { - calendar.add(Calendar.YEAR, -timeAgo) - } - } - - return calendar.timeInMillis - } - - return 0L - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET("$baseUrl/${chapter.url}/all-pages", headers) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.content-list > img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - private class Status : Filter.TriState("Completed") - private class Genre(name: String, val id: Int) : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Type : Filter.Select<String>("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - override fun getFilterList() = FilterList( - TextField("Author", "author-name"), - TextField("Artist", "artist-name"), - Type(), - Status(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n') - // https://www.readmng.com/advanced-search - private fun getGenreList() = listOf( - Genre("Action", 2), - Genre("Adventure", 4), - Genre("Comedy", 5), - Genre("Doujinshi", 6), - Genre("Drama", 7), - Genre("Ecchi", 8), - Genre("Fantasy", 9), - Genre("Gender Bender", 10), - Genre("Harem", 11), - Genre("Historical", 12), - Genre("Horror", 13), - Genre("Josei", 14), - Genre("Lolicon", 15), - Genre("Martial Arts", 16), - Genre("Mature", 17), - Genre("Mecha", 18), - Genre("Mystery", 19), - Genre("One shot", 20), - Genre("Psychological", 21), - Genre("Romance", 22), - Genre("School Life", 23), - Genre("Sci-fi", 24), - Genre("Seinen", 25), - Genre("Shotacon", 26), - Genre("Shoujo", 27), - Genre("Shoujo Ai", 28), - Genre("Shounen", 29), - Genre("Shounen Ai", 30), - Genre("Slice of Life", 31), - Genre("Smut", 32), - Genre("Sports", 33), - Genre("Supernatural", 34), - Genre("Tragedy", 35), - Genre("Yaoi", 36), - Genre("Yuri", 37) - ) -} diff --git a/src/en/readmanhwa/AndroidManifest.xml b/src/en/readmanhwa/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/readmanhwa/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/readmanhwa/build.gradle b/src/en/readmanhwa/build.gradle deleted file mode 100644 index 3b1246877..000000000 --- a/src/en/readmanhwa/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ReadManhwa' - pkgNameSuffix = 'en.readmanhwa' - extClass = '.ReadManhwa' - extVersionCode = 8 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/readmanhwa/res/mipmap-hdpi/ic_launcher.png b/src/en/readmanhwa/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 58d765a39..000000000 Binary files a/src/en/readmanhwa/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmanhwa/res/mipmap-mdpi/ic_launcher.png b/src/en/readmanhwa/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e27bcd309..000000000 Binary files a/src/en/readmanhwa/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmanhwa/res/mipmap-xhdpi/ic_launcher.png b/src/en/readmanhwa/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6869ed766..000000000 Binary files a/src/en/readmanhwa/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmanhwa/res/mipmap-xxhdpi/ic_launcher.png b/src/en/readmanhwa/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8a38dfcb5..000000000 Binary files a/src/en/readmanhwa/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmanhwa/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/readmanhwa/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1c842780c..000000000 Binary files a/src/en/readmanhwa/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/readmanhwa/res/web_hi_res_512.png b/src/en/readmanhwa/res/web_hi_res_512.png deleted file mode 100644 index 2c4e51d3c..000000000 Binary files a/src/en/readmanhwa/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/readmanhwa/src/eu/kanade/tachiyomi/extension/en/readmanhwa/ReadManhwa.kt b/src/en/readmanhwa/src/eu/kanade/tachiyomi/extension/en/readmanhwa/ReadManhwa.kt deleted file mode 100644 index 91ff2492a..000000000 --- a/src/en/readmanhwa/src/eu/kanade/tachiyomi/extension/en/readmanhwa/ReadManhwa.kt +++ /dev/null @@ -1,406 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.readmanhwa - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.PreferenceScreen -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -@Nsfw -class ReadManhwa : ConfigurableSource, HttpSource() { - - override val name = "ReadManhwa" - - override val baseUrl = "https://www.readmanhwa.com" - - override val lang = "en" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = headersBuilder(true) - - private fun headersBuilder(enableNsfw: Boolean) = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - .add("X-NSFW", enableNsfw.toString()) - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - val mangas = jsonObject["data"].asJsonArray.map { json -> - SManga.create().apply { - title = json["title"].string - thumbnail_url = json["image_url"].string - url = json["slug"].string - } - } - - return MangasPage(mangas, jsonObject["current_page"].int < jsonObject["last_page"].int) - } - private fun getMangaUrl(url: String): String { - return HttpUrl.parse(url)!!.newBuilder() - .setQueryParameter("nsfw", isNSFWEnabledInPref().toString()).toString() - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET(getMangaUrl("$baseUrl/api/comics?per_page=36&page=$page&q=&sort=popularity&order=desc&duration=all"), headers) - } - - override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(getMangaUrl("$baseUrl/api/comics?per_page=36&page=$page&q=&sort=uploaded_at&order=desc&duration=day"), headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val enableNsfw = (filters.find { it is NSFWFilter } as? Filter.CheckBox)?.state ?: true - - val url = HttpUrl.parse("$baseUrl/api/comics")!!.newBuilder() - .addQueryParameter("per_page", "36") - .addQueryParameter("page", page.toString()) - .addQueryParameter("q", query) - .addQueryParameter("nsfw", enableNsfw.toString()) - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - - val genreInclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } - } - if (genreInclude.isNotEmpty()) { - genreInclude.forEach { genre -> - url.addQueryParameter("tags[]", genre) - } - } - } - is StatusFilter -> { - val statusInclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - statusInclude.add(it.id) - } - } - if (statusInclude.isNotEmpty()) { - statusInclude.forEach { status -> - url.addQueryParameter("statuses[]", status) - } - } - } - is OrderBy -> { - val orderby = if (filter.state!!.ascending) "asc" else "desc" - val sort = arrayOf("uploaded_at", "title", "pages", "favorites", "popularity")[filter.state!!.index] - url.addQueryParameter("sort", sort) - url.addQueryParameter("order", orderby) - } - is DurationFilter -> url.addQueryParameter("duration", filter.toUriPart()) - } - } - return GET(url.toString(), headersBuilder(enableNsfw).build()) - } - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Details - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = - client.newCall(apiMangaDetailsRequest(manga)).asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET(getMangaUrl("$baseUrl/en/webtoon/${manga.url}"), headers) - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${manga.url}"), headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - return SManga.create().apply { - description = jsonObject["description"].nullString - status = jsonObject["status"].nullString.toStatus() - thumbnail_url = jsonObject["image_url"].nullString - genre = try { jsonObject["tags"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } - artist = try { jsonObject["artists"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } - author = try { jsonObject["authors"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("complete", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga.url) - } - } - - override fun chapterListRequest(manga: SManga): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${manga.url}/chapters"), headers) - } - - private fun chapterListParse(response: Response, titleSlug: String): List<SChapter> { - return gson.fromJson<JsonArray>(response.body()!!.string()).map { json -> - SChapter.create().apply { - name = json["name"].string - url = "$titleSlug/${json["slug"].string}" - date_upload = json["added_at"].string.let { dateString -> - if (dateString.contains("ago")) { - val trimmedDate = dateString.substringBefore(" ago").removeSuffix("s").split(" ") - val calendar = Calendar.getInstance() - when (trimmedDate[1]) { - "day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) }.timeInMillis - "hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) }.timeInMillis - "minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) }.timeInMillis - "second" -> calendar.apply { add(Calendar.SECOND, -trimmedDate[0].toInt()) }.timeInMillis - else -> 0L - } - } else { - SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(dateString)?.time ?: 0 - } - } - } - } - } - - override fun chapterListParse(response: Response): List<SChapter> = throw UnsupportedOperationException("Not used") - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${chapter.url}/images"), headers) - } - - override fun pageListParse(response: Response): List<Page> { - return gson.fromJson<JsonObject>(response.body()!!.string())["images"].asJsonArray.mapIndexed { i, json -> - Page(i, "", json["source_url"].string) - } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - NSFWFilter().apply { state = isNSFWEnabledInPref() }, - GenreFilter(getGenreList()), - StatusFilter(getStatusList()), - DurationFilter(getDurationList()), - OrderBy() - ) - - private class NSFWFilter : Filter.CheckBox("Show NSFW", true) - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("GENRES", genres) - private class Status(name: String, val id: String = name) : Filter.TriState(name) - private class StatusFilter(status: List<Status>) : Filter.Group<Status>("STATUS", status) - private class DurationFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("DURATION", pairs) - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private fun getGenreList() = listOf( - Genre("Action", "14"), - Genre("Adventure", "6"), - Genre("All Ages", "73"), - Genre("Angst", "50"), - Genre("BL", "20"), - Genre("Boxing", "58"), - Genre("College", "82"), - Genre("Comedy", "1"), - Genre("Comic", "70"), - Genre("Completed", "53"), - Genre("Cooking", "67"), - Genre("Crime", "18"), - Genre("Cultivation", "37"), - Genre("Demons", "65"), - Genre("Drama", "2"), - Genre("Ecchi", "46"), - Genre("Fantasy", "8"), - Genre("Gender Bender", "35"), - Genre("GL", "42"), - Genre("Goshiwon", "80"), - Genre("Gossip", "12"), - Genre("Harem", "7"), - Genre("Historical", "33"), - Genre("Horror", "19"), - Genre("Incest", "10"), - Genre("Isekai", "28"), - Genre("Josei", "48"), - Genre("Long Strip", "78"), - Genre("M", "43"), - Genre("Magic", "59"), - Genre("Magical", "69"), - Genre("Magical Girls", "77"), - Genre("Manga", "56"), - Genre("Manhua", "38"), - Genre("Manhwa", "40"), - Genre("Manhwa18", "81"), - Genre("Martial arts", "26"), - Genre("Mature", "30"), - Genre("Mecha", "54"), - Genre("Medical", "24"), - Genre("Moder", "64"), - Genre("Modern", "51"), - Genre("Monster/Tentacle", "57"), - Genre("Music", "75"), - Genre("Mystery", "15"), - Genre("NTR", "32"), - Genre("Office", "84"), - Genre("Office Life", "79"), - Genre("One shot", "61"), - Genre("Philosophical", "44"), - Genre("Post Apocalyptic", "49"), - Genre("Psychological", "16"), - Genre("Revenge", "74"), - Genre("Reverse harem", "72"), - Genre("Romance", "3"), - Genre("Rpg", "41"), - Genre("School LIfe", "11"), - Genre("Sci Fi", "9"), - Genre("Seinen", "31"), - Genre("Shoujo", "36"), - Genre("Shoujo Ai", "62"), - Genre("Shounen", "29"), - Genre("Shounen Ai", "63"), - Genre("Slice of Life", "4"), - Genre("Smut", "13"), - Genre("Sports", "5"), - Genre("Super power", "71"), - Genre("Superhero", "45"), - Genre("Supernatural", "22"), - Genre("Suspense", "47"), - Genre("Thriller", "17"), - Genre("Time Travel", "55"), - Genre("TimeTravel", "52"), - Genre("ToonPoint", "83"), - Genre("Tragedy", "23"), - Genre("Uncensored", "85"), - Genre("Vampire", "68"), - Genre("Vanilla", "34"), - Genre("Web Comic", "76"), - Genre("Webtoon", "39"), - Genre("Webtoons", "60"), - Genre("Yaoi", "21"), - Genre("Youkai", "66"), - Genre("Yuri", "25") - ) - - private fun getStatusList() = listOf( - Status("Ongoing", "ongoing"), - Status("Complete", "complete"), - Status("On Hold", "onhold"), - Status("Canceled", "canceled") - ) - - private fun getDurationList() = arrayOf( - Pair("All time", "all"), - Pair("Year", "year"), - Pair("Month", "month"), - Pair("Week", "week"), - Pair("Day", "day") - ) - - private class OrderBy : Filter.Sort( - "Order by", - arrayOf("Date", "Title", "Pages", "Favorites", "Popularity"), - Selection(0, false) - ) - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val nsfw = CheckBoxPreference(screen.context).apply { - key = NSFW - title = NSFW_TITLE - setDefaultValue(NSFW_DEFAULT) - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as Boolean - preferences.edit().putBoolean(NSFW, selected).commit() - } - } - screen.addPreference(nsfw) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val nsfw = androidx.preference.CheckBoxPreference(screen.context).apply { - key = NSFW - title = NSFW_TITLE - setDefaultValue(NSFW_DEFAULT) - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as Boolean - preferences.edit().putBoolean(NSFW, selected).commit() - } - } - screen.addPreference(nsfw) - } - - private fun isNSFWEnabledInPref(): Boolean { - return preferences.getBoolean(NSFW, NSFW_DEFAULT) - } - - companion object { - private const val NSFW = "NSFW" - private const val NSFW_TITLE = "Show NSFW" - private const val NSFW_DEFAULT = true - } -} diff --git a/src/en/schlockmercenary/AndroidManifest.xml b/src/en/schlockmercenary/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/schlockmercenary/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/schlockmercenary/build.gradle b/src/en/schlockmercenary/build.gradle deleted file mode 100644 index 0310d312f..000000000 --- a/src/en/schlockmercenary/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Schlock Mercenary' - pkgNameSuffix = 'en.schlockmercenary' - extClass = '.Schlockmercenary' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/schlockmercenary/res/mipmap-hdpi/ic_launcher.png b/src/en/schlockmercenary/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a9da83bf4..000000000 Binary files a/src/en/schlockmercenary/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/schlockmercenary/res/mipmap-mdpi/ic_launcher.png b/src/en/schlockmercenary/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 051be4d1b..000000000 Binary files a/src/en/schlockmercenary/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/schlockmercenary/res/mipmap-xhdpi/ic_launcher.png b/src/en/schlockmercenary/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a59e9a799..000000000 Binary files a/src/en/schlockmercenary/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/schlockmercenary/res/mipmap-xxhdpi/ic_launcher.png b/src/en/schlockmercenary/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c6bd4d2f9..000000000 Binary files a/src/en/schlockmercenary/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/schlockmercenary/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/schlockmercenary/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7f09dd789..000000000 Binary files a/src/en/schlockmercenary/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/schlockmercenary/res/web_hi_res_512.png b/src/en/schlockmercenary/res/web_hi_res_512.png deleted file mode 100644 index 3cf4c70b7..000000000 Binary files a/src/en/schlockmercenary/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/schlockmercenary/src/eu/kanade/tachiyomi/extension/en/schlockmercenary/Schlockmercenary.kt b/src/en/schlockmercenary/src/eu/kanade/tachiyomi/extension/en/schlockmercenary/Schlockmercenary.kt deleted file mode 100644 index 69723371c..000000000 --- a/src/en/schlockmercenary/src/eu/kanade/tachiyomi/extension/en/schlockmercenary/Schlockmercenary.kt +++ /dev/null @@ -1,191 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.schlockmercenary - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.lang.UnsupportedOperationException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Date -import java.util.GregorianCalendar -import java.util.Locale - -class Schlockmercenary : ParsedHttpSource() { - - override val name = "Schlock Mercenary" - - override val baseUrl = "https://www.schlockmercenary.com" - - override val lang = "en" - - override val supportsLatest = false - - private var chapterCount = 1 - - // Books - - override fun popularMangaRequest(page: Int): Request = GET("${baseUrl}$archiveUrl") - - override fun popularMangaSelector(): String = "div.archive-book" - - override fun popularMangaFromElement(element: Element): SManga { - val book = element.select("h4 > a").first() - val thumb = ( - baseUrl + ( - element.select("img").first()?.attr("src") - ?: defaultThumbnailUrl - ) - ).substringBefore("?") - return SManga.create().apply { - url = book.attr("href") - title = book.text() - artist = "Howard Tayler" - author = "Howard Tayler" - // Schlock Mercenary finished as of July 2020 - status = SManga.COMPLETED - description = element.select("p").first()?.text() ?: "" - thumbnail_url = thumb - } - } - - // Chapters - - override fun chapterListSelector() = "ul.chapters > li:not(ul > li > ul > li) > a" - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - val requestUrl = "${baseUrl}$archiveUrl" - return client.newCall(GET(requestUrl)) - .asObservableSuccess() - .map { response -> - getChaptersForBook(response, manga) - } - } - - private fun getChaptersForBook(response: Response, manga: SManga): List<SChapter> { - val document = response.asJsoup() - val sanitizedTitle = manga.title.replace("""([",'])""".toRegex(), "\\\\$1") - val book = document.select(popularMangaSelector() + ":contains($sanitizedTitle)") - val chapters = mutableListOf<SChapter>() - chapterCount = 1 - book.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - return chapters - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.url = element.attr("href") - chapter.name = element.text() - chapter.chapter_number = chapterCount++.toFloat() - chapter.date_upload = chapter.url.takeLast(10).let { - SimpleDateFormat(dateFormat, Locale.getDefault()).parse(it)!!.time - } - return chapter - } - - // Pages - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - val requestUrl = "${baseUrl}$archiveUrl" - return client.newCall(GET(requestUrl)) - .asObservableSuccess() - .map { response -> - getPagesForChapter(response, chapter) - } - } - - private fun getPagesForChapter(response: Response, chapter: SChapter): List<Page> { - val document = response.asJsoup() - - /** - * To find the end page, first, the next chapter start must be found, - * then subtract one day off of the next chapter start. - * If no chapter start is found, grab the next book start. - * If no next book exists, assume one page long. - */ - val currentChapter = document.select(chapterListSelector() + "[href=${chapter.url}]").first()!! - val start = chapterFromElement(currentChapter).date_upload - // Find next chapter start - var nextChapter = currentChapter.parent()?.nextElementSibling()?.select("a")?.first() - var end = start + 1 - // Chapter is the last in the book - - if (nextChapter == null) { - // Grab next book start. - nextChapter = currentChapter.parents()[2]?.nextElementSibling()?.select(chapterListSelector())?.first() - } - - if (nextChapter != null) { - end = chapterFromElement(nextChapter).date_upload - } - - return generatePageListBetweenDates(start, end) - } - - private fun generatePageListBetweenDates(start: Long, end: Long): List<Page> { - val pages = mutableListOf<Page>() - val calendar = GregorianCalendar() - calendar.time = Date(start) - - while (calendar.time.before(Date(end))) { - val result = calendar.time - val formatter = SimpleDateFormat(dateFormat, Locale.getDefault()) - val today = formatter.format(result) - getImageUrlsForDay(today).forEach { pages.add(Page(pages.size, "", it)) } - calendar.add(Calendar.DATE, 1) - } - - return pages - } - - private fun getImageUrlsForDay(day: String): List<String> { - val requestUrl = "$baseUrl/$day" - val document = client.newCall(GET(requestUrl)).execute().asJsoup() - val urls = document.select("div#strip-$day > img").map { it.attr("abs:src") } - return urls - } - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector(): String? = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun popularMangaNextPageSelector(): String? = null - - override fun searchMangaSelector(): String = throw UnsupportedOperationException("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun mangaDetailsParse(document: Document): SManga = throw UnsupportedOperationException("Not used") - - override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used") - - companion object { - const val defaultThumbnailUrl = "/static/img/logo.b6dacbb8.jpg" - const val archiveUrl = "/archives/" - const val dateFormat = "yyyy-MM-dd" - } -} diff --git a/src/en/silentmangaaudition/AndroidManifest.xml b/src/en/silentmangaaudition/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/silentmangaaudition/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/silentmangaaudition/build.gradle b/src/en/silentmangaaudition/build.gradle deleted file mode 100644 index 949a9e957..000000000 --- a/src/en/silentmangaaudition/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Silent Manga Audition' - pkgNameSuffix = 'en.silentmangaaudition' - extClass = '.SilentMangaAudition' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/silentmangaaudition/res/mipmap-hdpi/ic_launcher.png b/src/en/silentmangaaudition/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index bf019004e..000000000 Binary files a/src/en/silentmangaaudition/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/silentmangaaudition/res/mipmap-mdpi/ic_launcher.png b/src/en/silentmangaaudition/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7d9e1638d..000000000 Binary files a/src/en/silentmangaaudition/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/silentmangaaudition/res/mipmap-xhdpi/ic_launcher.png b/src/en/silentmangaaudition/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c76615281..000000000 Binary files a/src/en/silentmangaaudition/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/silentmangaaudition/res/mipmap-xxhdpi/ic_launcher.png b/src/en/silentmangaaudition/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 95d03405d..000000000 Binary files a/src/en/silentmangaaudition/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/silentmangaaudition/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/silentmangaaudition/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e2c66b1a0..000000000 Binary files a/src/en/silentmangaaudition/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/silentmangaaudition/res/web_hi_res_512.png b/src/en/silentmangaaudition/res/web_hi_res_512.png deleted file mode 100644 index 4dee00de8..000000000 Binary files a/src/en/silentmangaaudition/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/silentmangaaudition/src/eu/kanade/tachiyomi/extension/en/silentmangaaudition/ExtensionData.kt b/src/en/silentmangaaudition/src/eu/kanade/tachiyomi/extension/en/silentmangaaudition/ExtensionData.kt deleted file mode 100644 index af1d2ff2b..000000000 --- a/src/en/silentmangaaudition/src/eu/kanade/tachiyomi/extension/en/silentmangaaudition/ExtensionData.kt +++ /dev/null @@ -1,125 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.silentmangaaudition - -data class SmaEntry( - val name: String, - val url: String, - val chapterListUrl: String, - val thumbnailUrl: String -) - -val SMA_ENTRIES = listOf( - SmaEntry( - "SMA-14 “Creature, Spirits & Monsters”", - "/sma14-silent-manga-audition-2020-award-winners", - "/v/sma14/blooming-flower-by-blackwink/", - "https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma14/header_sp.png" - ), - SmaEntry( - "SMA-13 “Together for peace”", - "/sma13-silent-manga-audition-2020-award-winners/", - "/v/sma13/homeless-by-simone-sanseverino/?lang=en", - "https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma13/header_sp.png" - ), - SmaEntry( - "SMA-12 “New beginning”", - "/sma12-silent-manga-audition-2019-award-winners/", - "/v/sma12/never-late-by-lucas-marques-and-priscilla-miranda/?lang=en", - "https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma12/header_sp.png" - ), - SmaEntry( - "SMA-EX5 “Kumamoto + Do Your Best!”", - "/smaex5-silent-manga-audition-2019-award-winners/", - "/v/smaex5/fish-by-youngman/", - "https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/smaex5/top_banner_sp.jpg" - ), - SmaEntry( - "SMA11 “Promise”", - "/sma11-silent-manga-audition-2019-award-winners/", - "/v/sma11/reborn-by-riza-al-assami/?lang=en", - "https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma11/top_banner_sp.jpg" - ), - SmaEntry( - "SMA-EX4 “Kit Kat ROUND”", - "/smaex4-2018award/", - "/v/smaex4/lucky-charm-by-harihtaroon/", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/smaex4/smaex4_main01.png" - ), - SmaEntry( - "SMA10 “Effort / Friendship / Victory”", - "/sma10-2018award-2/", - "/v/sma10/run-by-riza-al-assami/?lang=en", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/sma10/sma10main_3.png" - ), - SmaEntry( - "SMA-EX3 “Kumamoto + Wasamon”", - "/smaex3-2018award/", - "/v/smaex3/to-the-sky-by-zevania-and-nattorin/", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/smaex3/smaex3_main01.png" - ), - SmaEntry( - "SMA9 “Fairness / Respect / Teamwork”", - "/sma9-2018award/", - "/v/sma9/fisherman-tales-by-joao-eddie/", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma9/sma9_theme.png" - ), - SmaEntry( - "SMA8 “Fair Play”", - "/sma8-2017award/", - "/v/sma8/checkmate-by-sideburn004/", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma8/sma8_theme.png" - ), - SmaEntry( - "SMA7 “Unforgettable Taste”", - "/sma7-2017award/", - "/v/sma7/our-promised-land-by-nattorin-and-zevania/", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma7/sma7_theme.png" - ), - SmaEntry( - "SMA-EX2 “Kumamoto + Smile”", - "http://data.smacmag.net/sma/smaex2-2017award/", - "https://smacmag.net/v/sma2/drawing-a-smile-out-by-dee-juusan/", - "http://data.smacmag.net/sma/smaex2-2017award/images/smaex2_title.png" - ), - SmaEntry( - "SMA6 “Childhood”", - "/sma6-2016award/", - "/v/sma6/forbidden-by-yos/13828", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma6/SMA06_themes.png" - ), - SmaEntry( - "SMA5 “Friend-ship + Communication Tool”", - "/sma05-2016award/", - "/v/sma5/im-happy-by-ds-studio/11915", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma5/sma5_theme.png" - ), - SmaEntry( - "SMA-EX1 “Fukushima Sakuramori”", - "/smaex1-2016award/", - "/v/smaex1/seeds-by-jim/9574", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/smaex1/smaex1_theme.png" - ), - SmaEntry( - "SMA4 “A Charming Gift”", - "/sma04-2015award/", - "/v/sma4/birdy-by-kalongzz/", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma4/sma4_theme.png" - ), - SmaEntry( - "SMA3 “Mother”", - "/sma03-2015award/", - "/v/sma3/homesick-alien-by-ichirou/4390", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma3/sma3_theme.png" - ), - SmaEntry( - "SMA2 “The Finest Smile”", - "/sma02-2014award/", - "/v/sma2/fathers-gift-by-ichirou/1775", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma2/sma2_theme.png" - ), - SmaEntry( - "SMA1 “Love Letter”", - "/sma01-2013award/", - "/v/sma1/excuse-me-by-alex-irzaqi/", - "https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma1/sma1_theme.png" - ) -) diff --git a/src/en/silentmangaaudition/src/eu/kanade/tachiyomi/extension/en/silentmangaaudition/SilentMangaAudition.kt b/src/en/silentmangaaudition/src/eu/kanade/tachiyomi/extension/en/silentmangaaudition/SilentMangaAudition.kt deleted file mode 100644 index 087a878e8..000000000 --- a/src/en/silentmangaaudition/src/eu/kanade/tachiyomi/extension/en/silentmangaaudition/SilentMangaAudition.kt +++ /dev/null @@ -1,135 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.silentmangaaudition - -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element -import rx.Observable - -class SilentMangaAudition : HttpSource() { - - override val name = "Silent Manga Audition" - - override val baseUrl = "https://manga-audition.com" - - override val lang = "en" - - override val supportsLatest = false - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Referer", baseUrl) - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val entries = SMA_ENTRIES.mapIndexed { i, entry -> entry.toSManga(i) } - return Observable.just(MangasPage(entries, false)) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - val filteredEntries = SMA_ENTRIES - .mapIndexed { i, entry -> entry.toSManga(i) } - .filter { - it.title.contains(query, true) || - it.description!!.contains(query, true) - } - - return Observable.just(MangasPage(filteredEntries, false)) - } - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - val index = manga.url.substringAfterLast(",").toInt() - val entry = SMA_ENTRIES[index] - - // Fill all the data again because in backup only the title and the url are stored. - return Observable.just(entry.toSManga(index)) - } - - override fun mangaDetailsRequest(manga: SManga): Request { - val url = manga.url.substringBefore(",") - return GET(if (url.startsWith("/")) baseUrl + url else url, headers) - } - - override fun chapterListRequest(manga: SManga): Request { - val url = manga.url - .substringAfter(",") - .substringBefore(",") - - return GET(SMACMAG_URL + url, headers) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return response.asJsoup() - .select(chapterListSelector()) - .map { chapterFromElement(it) } - } - - private fun chapterListSelector(): String = "ol.playlist li a" - - private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.select("span.ttl")!!.text() - scanlator = element.select("span.name")!!.text() - url = element.attr("abs:href") - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET(chapter.url, headers) - } - - override fun pageListParse(response: Response): List<Page> { - val chapterUrl = response.request().url().toString() - - return response.asJsoup() - .select("div.swiper-wrapper div.swiper-slide img.swiper-lazy") - .mapIndexed { i, element -> Page(i, chapterUrl, element.attr("data-src")) } - } - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun SmaEntry.toSManga(index: Int): SManga = SManga.create().apply { - title = name - author = "Various artists" - status = SManga.COMPLETED - description = "The theme is… " + name.substringAfter(" ") + "." - thumbnail_url = thumbnailUrl - url = "${this@toSManga.url},$chapterListUrl,$index" - } - - override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException("Not used") - - companion object { - private const val SMACMAG_URL = "https://smacmag.net" - - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" - } -} diff --git a/src/en/sleepypandascans/AndroidManifest.xml b/src/en/sleepypandascans/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/sleepypandascans/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/sleepypandascans/build.gradle b/src/en/sleepypandascans/build.gradle deleted file mode 100644 index fd56a2dd8..000000000 --- a/src/en/sleepypandascans/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Sleepy Panda Scans' - pkgNameSuffix = 'en.sleepypandascans' - extClass = '.SleepyPandaScans' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/sleepypandascans/res/mipmap-hdpi/ic_launcher.png b/src/en/sleepypandascans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index fb9a9fbc8..000000000 Binary files a/src/en/sleepypandascans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/sleepypandascans/res/mipmap-mdpi/ic_launcher.png b/src/en/sleepypandascans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ede174ba6..000000000 Binary files a/src/en/sleepypandascans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/sleepypandascans/res/mipmap-xhdpi/ic_launcher.png b/src/en/sleepypandascans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3c3837425..000000000 Binary files a/src/en/sleepypandascans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/sleepypandascans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/sleepypandascans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9fae4f91f..000000000 Binary files a/src/en/sleepypandascans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/sleepypandascans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/sleepypandascans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0a42b27d5..000000000 Binary files a/src/en/sleepypandascans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/sleepypandascans/res/web_hi_res_512.png b/src/en/sleepypandascans/res/web_hi_res_512.png deleted file mode 100644 index 3d3913313..000000000 Binary files a/src/en/sleepypandascans/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/sleepypandascans/src/eu/kanade/tachiyomi/extension/en/sleepypandascans/SleepyPandaScans.kt b/src/en/sleepypandascans/src/eu/kanade/tachiyomi/extension/en/sleepypandascans/SleepyPandaScans.kt deleted file mode 100644 index bc03e672c..000000000 --- a/src/en/sleepypandascans/src/eu/kanade/tachiyomi/extension/en/sleepypandascans/SleepyPandaScans.kt +++ /dev/null @@ -1,156 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.sleepypandascans - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.Calendar - -class SleepyPandaScans : ParsedHttpSource() { - - override val name = "Sleepy Panda Scans" - - override val baseUrl = "https://sleepypandascans.co" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/Series", headers) - } - - override fun popularMangaSelector() = "div.card.card-cascade" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - url = element.select("a").attr("href") - title = element.select("h6").text() - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl) - } - - // Only add manga to MangasPage if its title is not one we've added already - override fun latestUpdatesParse(response: Response): MangasPage { - val mangas = response.asJsoup().select(latestUpdatesSelector()) - .distinctBy { it.select(".card-title").text() } - .map { latestUpdatesFromElement(it) } - - return MangasPage(mangas, false) - } - - // Updates less than a day old are patron only, ignore them - override fun latestUpdatesSelector() = "div.card.card-cascade:not(div.amber)" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - url = element.select("a").attr("href") - .replace("Reader", "Series").substringBeforeLast("/") - title = element.select("h5").text() - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - // Source's website doesn't appear to have a search function; so searching locally - private lateinit var searchQuery: String - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - searchQuery = query - return popularMangaRequest(1) - } - - override fun searchMangaParse(response: Response): MangasPage { - val mangas = response.asJsoup().select(searchMangaSelector()) - .filter { it.text().contains(searchQuery, ignoreCase = true) } - .map { searchMangaFromElement(it) } - - return MangasPage(mangas, false) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - // Manga summary page - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.card-body") - - return SManga.create().apply { - title = infoElement.select("h4").text() - description = infoElement.select("p").text() - thumbnail_url = client.newCall(popularMangaRequest(1)).execute().asJsoup().select(popularMangaSelector()) - .first { it.select(".card-title").text() == title }?.select("img")?.attr("abs:src") - } - } - - // Chapters - - // Chapters less than a day old are patron only, ignore them - override fun chapterListSelector() = "div.list-group a:not(a[data-target])" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - url = element.attr("href") - name = element.ownText() - date_upload = parseRelativeDate(element.select("span").text().substringBefore(" ago")) - } - } - - // Subtract relative date (e.g. posted 3 days ago) - private fun parseRelativeDate(date: String): Long { - val calendar = Calendar.getInstance() - - if (date.contains("yesterday")) { - calendar.apply { add(Calendar.DAY_OF_MONTH, -1) } - } else { - val trimmedDate = date.replace("one", "1").removeSuffix("s").split(" ") - - when (trimmedDate[1]) { - "year" -> calendar.apply { add(Calendar.YEAR, -trimmedDate[0].toInt()) } - "month" -> calendar.apply { add(Calendar.MONTH, -trimmedDate[0].toInt()) } - "week" -> calendar.apply { add(Calendar.WEEK_OF_MONTH, -trimmedDate[0].toInt()) } - "day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) } - } - } - - return calendar.timeInMillis - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.view img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/en/swordscomic/AndroidManifest.xml b/src/en/swordscomic/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/swordscomic/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/swordscomic/build.gradle b/src/en/swordscomic/build.gradle deleted file mode 100644 index 9ce2c3c3f..000000000 --- a/src/en/swordscomic/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Swords Comic' - pkgNameSuffix = 'en.swordscomic' - extClass = '.SwordsComic' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/swordscomic/res/mipmap-hdpi/ic_launcher.png b/src/en/swordscomic/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7e8fce3e3..000000000 Binary files a/src/en/swordscomic/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/swordscomic/res/mipmap-mdpi/ic_launcher.png b/src/en/swordscomic/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1f8d5ca83..000000000 Binary files a/src/en/swordscomic/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/swordscomic/res/mipmap-xhdpi/ic_launcher.png b/src/en/swordscomic/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 34457d6fb..000000000 Binary files a/src/en/swordscomic/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/swordscomic/res/mipmap-xxhdpi/ic_launcher.png b/src/en/swordscomic/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 81a97b9f4..000000000 Binary files a/src/en/swordscomic/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/swordscomic/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/swordscomic/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a3af32410..000000000 Binary files a/src/en/swordscomic/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/swordscomic/res/web_hi_res_512.png b/src/en/swordscomic/res/web_hi_res_512.png deleted file mode 100644 index 1b429f6f1..000000000 Binary files a/src/en/swordscomic/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/swordscomic/src/eu/kanade/tachiyomi/extension/en/swordscomic/SwordsComic.kt b/src/en/swordscomic/src/eu/kanade/tachiyomi/extension/en/swordscomic/SwordsComic.kt deleted file mode 100644 index 88274ff36..000000000 --- a/src/en/swordscomic/src/eu/kanade/tachiyomi/extension/en/swordscomic/SwordsComic.kt +++ /dev/null @@ -1,94 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.swordscomic - -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale - -class SwordsComic : HttpSource() { - - override val name = "Swords Comic" - - override val baseUrl = "https://swordscomic.com" - - override val lang = "en" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - private fun createManga(): SManga { - return SManga.create().apply { - title = "Swords Comic" - url = "/archive/pages/" - author = "Matthew Wills" - artist = author - description = "A webcomic about swords and the heroes who wield them" - thumbnail_url = "https://swordscomic.com/media/ArgoksEdgeEmote.png" - } - } - - // Popular - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return Observable.just(MangasPage(listOf(createManga()), false)) - } - - override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") - - override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Details - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return Observable.just(createManga().apply { initialized = true }) - } - - override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException("Not used") - - // Chapters - - override fun chapterListParse(response: Response): List<SChapter> { - return response.asJsoup().select("a.archive-tile-short") - .map { element -> - SChapter.create().apply { - name = element.select("strong").text() - setUrlWithoutDomain(element.attr("href")) - date_upload = element.select("small").text() - .let { SimpleDateFormat("dd MMM yyyy", Locale.US).parse(it)?.time ?: 0L } - } - } - .reversed() - } - - // Pages - - override fun pageListParse(response: Response): List<Page> { - return listOf(Page(0, "", response.asJsoup().select("img#comic-image").attr("abs:src"))) - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/en/tapastic/AndroidManifest.xml b/src/en/tapastic/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/tapastic/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/tapastic/build.gradle b/src/en/tapastic/build.gradle deleted file mode 100644 index 91cdc4657..000000000 --- a/src/en/tapastic/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Tapas' - pkgNameSuffix = 'en.tapastic' - extClass = '.Tapastic' - extVersionCode = 10 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/tapastic/res/mipmap-hdpi/ic_launcher.png b/src/en/tapastic/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c1b0d1ce7..000000000 Binary files a/src/en/tapastic/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tapastic/res/mipmap-mdpi/ic_launcher.png b/src/en/tapastic/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7fb9d7e17..000000000 Binary files a/src/en/tapastic/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tapastic/res/mipmap-xhdpi/ic_launcher.png b/src/en/tapastic/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 18c520d8e..000000000 Binary files a/src/en/tapastic/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tapastic/res/mipmap-xxhdpi/ic_launcher.png b/src/en/tapastic/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ad0e42dda..000000000 Binary files a/src/en/tapastic/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tapastic/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/tapastic/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0829333f3..000000000 Binary files a/src/en/tapastic/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tapastic/res/web_hi_res_512.png b/src/en/tapastic/res/web_hi_res_512.png deleted file mode 100644 index ff6176814..000000000 Binary files a/src/en/tapastic/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt b/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt deleted file mode 100644 index 6a637c0c5..000000000 --- a/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt +++ /dev/null @@ -1,326 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.tapastic - -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import com.github.salomonbrys.kotson.bool -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale - -class Tapastic : ConfigurableSource, ParsedHttpSource() { - - // Preferences Code - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val chapterListPref = androidx.preference.ListPreference(screen.context).apply { - key = SHOW_LOCKED_CHAPTERS_Title - title = SHOW_LOCKED_CHAPTERS_Title - entries = prefsEntries - entryValues = prefsEntryValues - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SHOW_LOCKED_CHAPTERS, entry).commit() - } - } - screen.addPreference(chapterListPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val chapterListPref = ListPreference(screen.context).apply { - key = SHOW_LOCKED_CHAPTERS_Title - title = SHOW_LOCKED_CHAPTERS_Title - entries = prefsEntries - entryValues = prefsEntryValues - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SHOW_LOCKED_CHAPTERS, entry).commit() - } - } - screen.addPreference(chapterListPref) - } - - private fun chapterListPref() = preferences.getString(SHOW_LOCKED_CHAPTERS, "free") - - companion object { - private const val SHOW_LOCKED_CHAPTERS_Title = "Tapas requires login/payment for some chapters" - private const val SHOW_LOCKED_CHAPTERS = "tapas_locked_chapters" - private val prefsEntries = arrayOf("Show all chapters (including pay-to-read)", "Only show free chapters") - private val prefsEntryValues = arrayOf("all", "free") - } - - // Info - override val lang = "en" - override val supportsLatest = true - override val name = "Tapas" // originally Tapastic - override val baseUrl = "https://tapas.io" - override val id = 3825434541981130345 - - // Popular - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/comics?b=POPULAR&g=&f=NONE&pageNumber=$page&pageSize=20&") - - override fun popularMangaNextPageSelector() = "div[data-has-next=true]" - override fun popularMangaSelector() = "li.js-list-item" - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - url = element.select(".item__thumb a").attr("href") - title = element.select(".item__thumb img").attr("alt") - thumbnail_url = element.select(".item__thumb img").attr("src") - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/comics?b=FRESH&g=&f=NONE&pageNumber=$page&pageSize=20&") - - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // If there is any search text, use text search, otherwise use filter search - val uri = if (query.isNotBlank()) { - Uri.parse("$baseUrl/search") - .buildUpon() - .appendQueryParameter("t", "COMICS") - .appendQueryParameter("q", query) - } else { - val uri = Uri.parse("$baseUrl/comics").buildUpon() - // Append uri filters - filters.forEach { - if (it is UriFilter) - it.addToUri(uri) - } - uri - } - // Append page number - uri.appendQueryParameter("pageNumber", page.toString()) - return GET(uri.toString()) - } - - override fun searchMangaNextPageSelector() = - "${popularMangaNextPageSelector()}, a.paging__button--next" - - override fun searchMangaSelector() = "${popularMangaSelector()}, .search-item-wrap" - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - url = element.select(".item__thumb a, .title-section .title a").attr("href") - title = element.select(".item__thumb img").firstOrNull()?.attr("alt") ?: element.select(".title-section .title a").text() - thumbnail_url = element.select(".item__thumb img, .thumb-wrap img").attr("src") - } - - // Details - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl + "${manga.url}/info") - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - genre = document.select("div.info-detail__row a.genre-btn").joinToString { it.text() } - title = document.select("div.title-wrapper a.title").text() - thumbnail_url = document.select("div.thumb-wrapper img").attr("abs:src") - author = document.select("ul.creator-section a.name").joinToString { it.text() } - artist = author - description = document.select("div.row-body span.description__body").text() - } - - // Chapters - - /** - * Checklist: Paginated chapter lists, locked chapters, future chapters, early-access chapters (app only?), chapter order - */ - - private val gson by lazy { Gson() } - - private fun Element.isLockedChapter(): Boolean { - return this.hasClass("js-have-to-sign") - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val mangaId = document.select("div.info-body__bottom a").attr("data-id") - val chapters = mutableListOf<SChapter>() - - // recursively build the chapter list - fun parseChapters(page: Int) { - val url = "$baseUrl/series/$mangaId/episodes?page=$page&sort=NEWEST&init_load=0&large=true&last_access=0&" - val json = gson.fromJson<JsonObject>(client.newCall(GET(url, headers)).execute().body()!!.string())["data"] - - Jsoup.parse(json["body"].string).select(chapterListSelector()) - .let { list -> - // show/don't show locked chapters based on user's preferences - if (chapterListPref() == "free") list.filterNot { it.isLockedChapter() } else list - } - .map { chapters.add(chapterFromElement(it)) } - - if (json["pagination"]["has_next"].bool) parseChapters(json["pagination"]["page"].int) - } - - parseChapters(1) - return chapters - } - - override fun chapterListSelector() = "li a:not(.js-early-access):not(.js-coming-soon)" - - private val datePattern = Regex("""\w\w\w \d\d, \d\d\d\d""") - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val episode = element.select("p.scene").text() - val chName = element.select("span.title__body").text() - name = (if (element.isLockedChapter()) "\uD83D\uDD12 " else "") + "$episode | $chName" - setUrlWithoutDomain(element.attr("href")) - date_upload = datePattern.find(element.select("p.additional").text())?.value.toDate() - } - - private fun String?.toDate(): Long { - this ?: return 0L - return SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(this)?.time ?: 0L - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("img.content__img").mapIndexed { i, img -> - Page(i, "", img.let { if (it.hasAttr("data-src")) it.attr("abs:data-src") else it.attr("abs:src") }) - } - } - - override fun imageUrlParse(document: Document) = - throw UnsupportedOperationException("This method should not be called!") - - // Filters - - override fun getFilterList() = FilterList( - // Tapastic does not support genre filtering and text search at the same time - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - FilterFilter(), - GenreFilter(), - StatusFilter(), - Filter.Separator(), - Filter.Header("Sort is ignored when filter is active!"), - SortFilter() - ) - - private class FilterFilter : UriSelectFilter( - "Filter", - "b", - arrayOf( - Pair("ALL", "None"), - Pair("POPULAR", "Popular"), - Pair("TRENDING", "Trending"), - Pair("FRESH", "Fresh"), - Pair("BINGE", "Binge"), - Pair("ORIGINAL", "Tapas Originals") - ), - firstIsUnspecified = false, - defaultValue = 1 - ) - - private class GenreFilter : UriSelectFilter( - "Genre", - "g", - arrayOf( - Pair("", "Any"), - Pair("7", "Action"), - Pair("22", "Boys Love"), - Pair("2", "Comedy"), - Pair("8", "Drama"), - Pair("3", "Fantasy"), - Pair("24", "Girls Love"), - Pair("9", "Gaming"), - Pair("6", "Horror"), - Pair("25", "LGBTQ+"), - Pair("10", "Mystery"), - Pair("5", "Romance"), - Pair("4", "Science Fiction"), - Pair("1", "Slice of Life") - ) - ) - - private class StatusFilter : UriSelectFilter( - "Status", - "f", - arrayOf( - Pair("NONE", "All"), - Pair("F2R", "Free to read"), - Pair("PRM", "Premium") - ) - ) - - private class SortFilter : UriSelectFilter( - "Sort", - "s", - arrayOf( - Pair("DATE", "Date"), - Pair("LIKE", "Likes"), - Pair("SUBSCRIBE", "Subscribers") - ) - ) - - /** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ - // vals: <name, display> - private open class UriSelectFilter( - displayName: String, - val uriParam: String, - val vals: Array<Pair<String, String>>, - val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), - UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) - uri.appendQueryParameter(uriParam, vals[state].first) - } - } - - /** - * Represents a filter that is able to modify a URI. - */ - private interface UriFilter { - fun addToUri(uri: Uri.Builder) - } -} diff --git a/src/en/thepropertyofhate/AndroidManifest.xml b/src/en/thepropertyofhate/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/thepropertyofhate/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/thepropertyofhate/build.gradle b/src/en/thepropertyofhate/build.gradle deleted file mode 100644 index 8ea03923b..000000000 --- a/src/en/thepropertyofhate/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'The Property of Hate' - pkgNameSuffix = 'en.thepropertyofhate' - extClass = '.ThePropertyOfHate' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/thepropertyofhate/res/mipmap-hdpi/ic_launcher.png b/src/en/thepropertyofhate/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ad459b8f9..000000000 Binary files a/src/en/thepropertyofhate/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/thepropertyofhate/res/mipmap-mdpi/ic_launcher.png b/src/en/thepropertyofhate/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3fdd2df83..000000000 Binary files a/src/en/thepropertyofhate/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/thepropertyofhate/res/mipmap-xhdpi/ic_launcher.png b/src/en/thepropertyofhate/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index def3562c5..000000000 Binary files a/src/en/thepropertyofhate/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/thepropertyofhate/res/mipmap-xxhdpi/ic_launcher.png b/src/en/thepropertyofhate/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2f37d6846..000000000 Binary files a/src/en/thepropertyofhate/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/thepropertyofhate/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/thepropertyofhate/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 37c14e0c7..000000000 Binary files a/src/en/thepropertyofhate/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/thepropertyofhate/res/web_hi_res_512.png b/src/en/thepropertyofhate/res/web_hi_res_512.png deleted file mode 100644 index 0f6c1479e..000000000 Binary files a/src/en/thepropertyofhate/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/thepropertyofhate/src/eu/kanade/tachiyomi/extension/en/thepropertyofhate/ThePropertyOfHate.kt b/src/en/thepropertyofhate/src/eu/kanade/tachiyomi/extension/en/thepropertyofhate/ThePropertyOfHate.kt deleted file mode 100644 index 780a85f43..000000000 --- a/src/en/thepropertyofhate/src/eu/kanade/tachiyomi/extension/en/thepropertyofhate/ThePropertyOfHate.kt +++ /dev/null @@ -1,115 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.thepropertyofhate - -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import rx.Observable - -/** - * @author Aria Moradi <aria.moradi007@gmail.com> - */ - -class ThePropertyOfHate : HttpSource() { - - override val name = "The Property of Hate" - - override val baseUrl = "http://jolleycomics.com" - - val firstChapterUrl = "/TPoH/The Hook/" - - override val lang = "en" - - override val supportsLatest: Boolean = false - - // the one and only manga entry - fun manga(): SManga { - return SManga.create().apply { - title = "The Property of Hate" - thumbnail_url = "https://pbs.twimg.com/media/DOBCcMiWkAA8Hvu.jpg" - artist = "Sarah Jolley" - author = "Sarah Jolley" - status = SManga.UNKNOWN - url = baseUrl - } - } - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - return Observable.just(MangasPage(listOf(manga()), false)) - } - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun popularMangaParse(response: Response): MangasPage = throw Exception("Not used") - - // latest Updates not used - - override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - // the manga is one and only, but still write the data again to avoid bugs in backup restore - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return Observable.just(manga()) - } - - override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used") - - // chapter list - - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + firstChapterUrl, headers) // no real base url for this comic so must read the first chapter's link - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - - val chapters = mutableListOf<SChapter>( - SChapter.create().apply() { // must hard code the first one - url = firstChapterUrl - name = "The Hook" - } - ) - - document.select("select > option").forEach { option -> - if (!option.text().startsWith("-")) // ignore "jump to entry" option - chapters.add( - SChapter.create().apply { - url = option.attr("value") - name = option.text() - } - ) - } - - return chapters.reversed() - } - - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - - // parse the options for this chapter to get page links - val pages = document.select("select > optgroup > option").mapIndexed { pageNum, option -> - Page(pageNum, baseUrl + option.attr("value")) - } - - return pages - } - - override fun imageUrlParse(response: Response): String { - val document = response.asJsoup() - - return baseUrl + document.select(".comic_comic > img").first().attr("src") - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = throw Exception("Search functionality is not available.") - - override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") -} diff --git a/src/en/timelessleaf/AndroidManifest.xml b/src/en/timelessleaf/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/timelessleaf/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/timelessleaf/build.gradle b/src/en/timelessleaf/build.gradle deleted file mode 100644 index 85c9d6a77..000000000 --- a/src/en/timelessleaf/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'TimelessLeaf' - pkgNameSuffix = 'en.timelessleaf' - extClass = '.TimelessLeaf' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/timelessleaf/res/mipmap-hdpi/ic_launcher.png b/src/en/timelessleaf/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8da394388..000000000 Binary files a/src/en/timelessleaf/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/timelessleaf/res/mipmap-mdpi/ic_launcher.png b/src/en/timelessleaf/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ceb364f53..000000000 Binary files a/src/en/timelessleaf/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/timelessleaf/res/mipmap-xhdpi/ic_launcher.png b/src/en/timelessleaf/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e319d8e85..000000000 Binary files a/src/en/timelessleaf/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/timelessleaf/res/mipmap-xxhdpi/ic_launcher.png b/src/en/timelessleaf/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f83051598..000000000 Binary files a/src/en/timelessleaf/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/timelessleaf/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/timelessleaf/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 8407b2cbf..000000000 Binary files a/src/en/timelessleaf/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/timelessleaf/res/web_hi_res_512.png b/src/en/timelessleaf/res/web_hi_res_512.png deleted file mode 100644 index a7bda4534..000000000 Binary files a/src/en/timelessleaf/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/timelessleaf/src/eu/kanade/tachiyomi/extension/en/timelessleaf/TimelessLeaf.kt b/src/en/timelessleaf/src/eu/kanade/tachiyomi/extension/en/timelessleaf/TimelessLeaf.kt deleted file mode 100644 index 5687b3ba2..000000000 --- a/src/en/timelessleaf/src/eu/kanade/tachiyomi/extension/en/timelessleaf/TimelessLeaf.kt +++ /dev/null @@ -1,161 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.timelessleaf - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Locale - -/** - * @author Aria Moradi <aria.moradi007@gmail.com> - */ - -class TimelessLeaf : HttpSource() { - - override val name = "TimelessLeaf" - - override val baseUrl = "https://timelessleaf.com" - - override val lang = "en" - - override val supportsLatest: Boolean = false - - private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd", Locale.US) - - // popular manga - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/manga/") - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - // scraping post links - val articleLinks = document.select(".site-main article a") - - // scraping menus, ignore the ones that are not manga entries - val pagesWeDontWant = listOf( - "dropped", - "more manga", - "recent" - ).joinToString(prefix = "(?i)", separator = "|").toRegex() - - // all mangas are in sub menus, go straight for that to deal with less menu items - val menuLinks = document.select(".sub-menu a").filterNot { element -> - element.text().toLowerCase(Locale.ROOT).contains(pagesWeDontWant) - } - - // combine the two lists - val combinedLinks = articleLinks.map { el -> - Pair(el.text(), el.attr("href")) - }.toMutableList().apply { - val titleList = this.map { it.first } - menuLinks.forEach { el -> - val title = el.text() - // ignore duplicates - if (titleList.none { str -> str.startsWith(title, ignoreCase = true) }) - add(Pair(title, el.attr("href"))) - } - }.sortedBy { pair -> pair.first } - - return MangasPage( - combinedLinks.map { p -> - SManga.create().apply { - title = p.first - setUrlWithoutDomain(p.second) - } - }, - false - ) - } - - // manga details - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - return SManga.create().apply { - // prefer srcset for higher res images, if not available use src - thumbnail_url = document.select(".site-main img").attr("srcset").substringBefore(" ") - if (thumbnail_url == "") - thumbnail_url = document.select(".site-main img").attr("abs:src") - description = document.select(".page-content p:not(:has(a)):not(:contains(chapter)):not(:has(strong))") - .text().substringAfter("Summary: ") - } - } - - // chapter list - - override fun chapterListParse(response: Response): List<SChapter> { - // some chapters are not hosted at TimelessLeaf itself, so can't do anything about them -> ignore - val hostedHere = response.asJsoup().select(".site-main a").filter { el -> - el.attr("href").startsWith(baseUrl) - } - - return hostedHere.map { el -> - SChapter.create().apply { - setUrlWithoutDomain(el.attr("href")) - // taking timeStamp from url - date_upload = parseChapterDate(el.attr("href").substringAfter("com/").substringAfter("php/")) - name = el.text() - } - }.asReversed() - } - - private fun parseChapterDate(date: String): Long { - return try { - dateFormat.parse(date)?.time ?: 0 - } catch (_: Exception) { - 0L - } - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH).parse(date)?.time ?: 0L - } - - // page list - - override fun pageListParse(response: Response): List<Page> { - return response.asJsoup() - .let { document -> - document.select(".site-main article .gallery-item img") - .let { if (it.isNullOrEmpty()) document.select("div.entry-content img") else it } - } - .mapIndexed { index, el -> - Page(index, "", el.attr("abs:src")) - } - } - - // search manga, implementing a local search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(1) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - val allManga = popularMangaParse(response) - val filtered = allManga.mangas.filter { manga -> manga.title.contains(query, ignoreCase = true) } - MangasPage(filtered, false) - } - } - - override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used.") - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used.") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used.") - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.") -} diff --git a/src/en/tsumino/AndroidManifest.xml b/src/en/tsumino/AndroidManifest.xml deleted file mode 100644 index 920c8690f..000000000 --- a/src/en/tsumino/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".en.tsumino.TsuminoUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="www.tsumino.com" - android:pathPattern="/entry/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/en/tsumino/build.gradle b/src/en/tsumino/build.gradle deleted file mode 100644 index 2411757a7..000000000 --- a/src/en/tsumino/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Tsumino' - pkgNameSuffix = 'en.tsumino' - extClass = '.Tsumino' - extVersionCode = 4 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/tsumino/res/mipmap-hdpi/ic_launcher.png b/src/en/tsumino/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index fad9e2ce3..000000000 Binary files a/src/en/tsumino/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tsumino/res/mipmap-mdpi/ic_launcher.png b/src/en/tsumino/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7d5ebe101..000000000 Binary files a/src/en/tsumino/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tsumino/res/mipmap-xhdpi/ic_launcher.png b/src/en/tsumino/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 790fda0a5..000000000 Binary files a/src/en/tsumino/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tsumino/res/mipmap-xxhdpi/ic_launcher.png b/src/en/tsumino/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c576238ae..000000000 Binary files a/src/en/tsumino/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tsumino/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/tsumino/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 99543019c..000000000 Binary files a/src/en/tsumino/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/tsumino/res/web_hi_res_512.png b/src/en/tsumino/res/web_hi_res_512.png deleted file mode 100644 index 69069ff19..000000000 Binary files a/src/en/tsumino/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/Tsumino.kt b/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/Tsumino.kt deleted file mode 100644 index ce19449e0..000000000 --- a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/Tsumino.kt +++ /dev/null @@ -1,267 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.tsumino - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getArtists -import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getChapter -import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getCollection -import eu.kanade.tachiyomi.extension.en.tsumino.TsuminoUtils.Companion.getDesc -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -@Nsfw -class Tsumino : ParsedHttpSource() { - - override val name = "Tsumino" - - override val baseUrl = "https://www.tsumino.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - override fun latestUpdatesSelector() = "Not needed" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/Search/Operate/?PageNumber=$page&Sort=Newest") - - override fun latestUpdatesParse(response: Response): MangasPage { - val allManga = mutableListOf<SManga>() - val body = response.body()!!.string() - val jsonManga = gson.fromJson<JsonObject>(body)["data"].asJsonArray - for (i in 0 until jsonManga.size()) { - val manga = SManga.create() - manga.url = "/entry/" + jsonManga[i]["entry"]["id"].asString - manga.title = jsonManga[i]["entry"]["title"].asString - manga.thumbnail_url = jsonManga[i]["entry"]["thumbnailUrl"].asString - allManga.add(manga) - } - - val currentPage = gson.fromJson<JsonObject>(body)["pageNumber"].asString - val totalPage = gson.fromJson<JsonObject>(body)["pageCount"].asString - val hasNextPage = currentPage.toInt() != totalPage.toInt() - - return MangasPage(allManga, hasNextPage) - } - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = "Not needed" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/Search/Operate/?PageNumber=$page&Sort=Popularity") - - override fun popularMangaParse(response: Response): MangasPage = latestUpdatesParse(response) - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // Taken from github.com/NerdNumber9/TachiyomiEH - val f = filters + getFilterList() - val advSearch = f.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter -> - val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank) - splitState.map { - AdvSearchEntry(filter.type, it.removePrefix("-"), it.startsWith("-")) - } - } - val body = FormBody.Builder() - .add("PageNumber", page.toString()) - .add("Text", query) - .add("Sort", SortType.values()[f.filterIsInstance<SortFilter>().first().state].name) - .add("List", "0") - .add("Length", LengthType.values()[f.filterIsInstance<LengthFilter>().first().state].id.toString()) - .add("MinimumRating", f.filterIsInstance<MinimumRatingFilter>().first().state.toString()) - .apply { - advSearch.forEachIndexed { index, entry -> - add("Tags[$index][Type]", entry.type.toString()) - add("Tags[$index][Text]", entry.text) - add("Tags[$index][Exclude]", entry.exclude.toString()) - } - - if (f.filterIsInstance<ExcludeParodiesFilter>().first().state) - add("Exclude[]", "6") - } - .build() - - return POST("$baseUrl/Search/Operate/", headers, body) - } - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/entry/$id", headers) - - private fun searchMangaByIdParse(response: Response, id: String): MangasPage { - val details = mangaDetailsParse(response) - details.url = "/entry/$id" - return MangasPage(listOf(details), false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(searchMangaByIdRequest(id)) - .asObservableSuccess() - .map { response -> searchMangaByIdParse(response, id) } - } else { - super.fetchSearchManga(page, query, filters) - } - } - - override fun searchMangaParse(response: Response): MangasPage = latestUpdatesParse(response) - - override fun searchMangaSelector() = latestUpdatesSelector() - - override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun mangaDetailsRequest(manga: SManga): Request { - if (manga.url.startsWith("http")) { - return GET(manga.url, headers) - } - return super.mangaDetailsRequest(manga) - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.book-page-container") - val manga = SManga.create() - - manga.title = infoElement.select("#Title").text() - manga.artist = getArtists(document) - manga.author = manga.artist - manga.status = SManga.COMPLETED - manga.thumbnail_url = infoElement.select("img").attr("src") - manga.description = getDesc(document) - manga.genre = document.select("#Tag a").joinToString { it.text() } - - return manga - } - override fun chapterListRequest(manga: SManga): Request { - if (manga.url.startsWith("http")) { - return GET(manga.url, headers) - } - return super.chapterListRequest(manga) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val collection = document.select(chapterListSelector()) - return if (collection.isNotEmpty()) { - getCollection(document, chapterListSelector()) - } else { - getChapter(document, response) - } - } - - override fun chapterListSelector() = ".book-collection-table a" - - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - override fun pageListRequest(chapter: SChapter): Request { - if (chapter.url.startsWith("http")) { - return GET(chapter.url, headers) - } - return super.pageListRequest(chapter) - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val numPages = document.select("h1").text().split(" ").last() - - if (numPages.isNotEmpty()) { - for (i in 1 until numPages.toInt() + 1) { - val data = document.select("#image-container").attr("data-cdn") - .replace("[PAGE]", i.toString()) - pages.add(Page(i, "", data)) - } - } else { - throw UnsupportedOperationException("Error: Open in WebView and solve the Captcha!") - } - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - data class AdvSearchEntry(val type: Int, val text: String, val exclude: Boolean) - - override fun getFilterList() = FilterList( - Filter.Header("Separate tags with commas (,)"), - Filter.Header("Prepend with dash (-) to exclude"), - TagFilter(), - CategoryFilter(), - CollectionFilter(), - GroupFilter(), - ArtistFilter(), - ParodyFilter(), - CharactersFilter(), - UploaderFilter(), - - Filter.Separator(), - - SortFilter(), - LengthFilter(), - MinimumRatingFilter(), - ExcludeParodiesFilter() - ) - - class TagFilter : AdvSearchEntryFilter("Tags", 1) - class CategoryFilter : AdvSearchEntryFilter("Categories", 2) - class CollectionFilter : AdvSearchEntryFilter("Collections", 3) - class GroupFilter : AdvSearchEntryFilter("Groups", 4) - class ArtistFilter : AdvSearchEntryFilter("Artists", 5) - class ParodyFilter : AdvSearchEntryFilter("Parodies", 6) - class CharactersFilter : AdvSearchEntryFilter("Characters", 7) - class UploaderFilter : AdvSearchEntryFilter("Uploaders", 8) - open class AdvSearchEntryFilter(name: String, val type: Int) : Filter.Text(name) - - class SortFilter : Filter.Select<SortType>("Sort by", SortType.values()) - class LengthFilter : Filter.Select<LengthType>("Length", LengthType.values()) - class MinimumRatingFilter : Filter.Select<String>("Minimum rating", (0..5).map { "$it stars" }.toTypedArray()) - class ExcludeParodiesFilter : Filter.CheckBox("Exclude parodies") - - enum class SortType { - Popularity, - Newest, - Oldest, - Alphabetical, - Rating, - Pages, - Views, - Random, - Comments, - } - - enum class LengthType(val id: Int) { - Any(0), - Short(1), - Medium(2), - Long(3) - } - - companion object { - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUrlActivity.kt b/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUrlActivity.kt deleted file mode 100644 index 247de0a68..000000000 --- a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUrlActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.tsumino - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://www.tsumino.com/entry/xxxxx intents and redirects them to - * the main Tachiyomi process. - */ -class TsuminoUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${Tsumino.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("TsuminoUrlActivity", e.toString()) - } - } else { - Log.e("TsuminoUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUtils.kt b/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUtils.kt deleted file mode 100644 index c62deff06..000000000 --- a/src/en/tsumino/src/eu/kanade/tachiyomi/extension/en/tsumino/TsuminoUtils.kt +++ /dev/null @@ -1,97 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.tsumino - -import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.Response -import org.jsoup.nodes.Document - -class TsuminoUtils { - companion object { - fun getArtists(document: Document): String { - val stringBuilder = StringBuilder() - val artists = document.select("#Artist a") - - artists.forEach { - stringBuilder.append(it.text()) - - if (it != artists.last()) - stringBuilder.append(", ") - } - - return stringBuilder.toString() - } - - private fun getGroups(document: Document): String? { - val stringBuilder = StringBuilder() - val groups = document.select("#Group a") - - groups.forEach { - stringBuilder.append(it.text()) - - if (it != groups.last()) - stringBuilder.append(", ") - } - - return if (stringBuilder.toString().isEmpty()) null else stringBuilder.toString() - } - - fun getDesc(document: Document): String { - val stringBuilder = StringBuilder() - val pages = document.select("#Pages").text() - val parodies = document.select("#Parody a") - val characters = document.select("#Character a") - - stringBuilder.append("Pages: $pages") - - if (parodies.size > 0) { - stringBuilder.append("\n\n") - stringBuilder.append("Parodies: ") - - parodies.forEach { - stringBuilder.append(it.text()) - - if (it != parodies.last()) - stringBuilder.append(", ") - } - } - - if (characters.size > 0) { - stringBuilder.append("\n\n") - stringBuilder.append("Characters: ") - - characters.forEach { - stringBuilder.append(it.text()) - - if (it != characters.last()) - stringBuilder.append(", ") - } - } - - return stringBuilder.toString() - } - - fun getCollection(document: Document, selector: String): List<SChapter> { - return document.select(selector).map { element -> - SChapter.create().apply { - val chapterNum = element.select("span")[0].text() - val chapterName = element.select("span")[1].text() - name = "$chapterNum. $chapterName" - scanlator = getGroups(document) - url = element.attr("href").replace("entry", "Read/Index") - } - }.reversed() - } - - fun getChapter(document: Document, response: Response): List<SChapter> { - val chapterList = mutableListOf<SChapter>() - val chapter = SChapter.create().apply { - name = "Chapter" - scanlator = getGroups(document) - chapter_number = 1f - url = response.request().url().encodedPath() - .replace("entry", "Read/Index") - } - chapterList.add(chapter) - return chapterList - } - } -} diff --git a/src/es/leermangasxyz/AndroidManifest.xml b/src/en/twodgirlstech/AndroidManifest.xml similarity index 100% rename from src/es/leermangasxyz/AndroidManifest.xml rename to src/en/twodgirlstech/AndroidManifest.xml diff --git a/src/en/twodgirlstech/build.gradle b/src/en/twodgirlstech/build.gradle new file mode 100644 index 000000000..c4406a515 --- /dev/null +++ b/src/en/twodgirlstech/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'twodgirlstech' + pkgNameSuffix = 'en.twodgirlstech' + extClass = '.TwoDGirlsTech' + extVersionCode = 9 + libVersion = '1.3' +} + +apply from: "$rootDir/common.gradle" \ No newline at end of file diff --git a/src/en/xkcd/src/eu/kanade/tachiyomi/extension/en/xkcd/Xkcd.kt b/src/en/twodgirlstech/src/eu/kanade/tachiyomi/extension/en/twodgirlstech/TwoDGirlsTech.kt similarity index 59% rename from src/en/xkcd/src/eu/kanade/tachiyomi/extension/en/xkcd/Xkcd.kt rename to src/en/twodgirlstech/src/eu/kanade/tachiyomi/extension/en/twodgirlstech/TwoDGirlsTech.kt index bb2480f4b..8e7c58859 100644 --- a/src/en/xkcd/src/eu/kanade/tachiyomi/extension/en/xkcd/Xkcd.kt +++ b/src/en/twodgirlstech/src/eu/kanade/tachiyomi/extension/en/twodgirlstech/TwoDGirlsTech.kt @@ -1,11 +1,11 @@ -package eu.kanade.tachiyomi.extension.en.xkcd +package eu.kanade.tachiyomi.extension.en.twodgirlstech import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.AnimesPage 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.SAnime +import eu.kanade.tachiyomi.source.model.SEpisode import eu.kanade.tachiyomi.source.online.ParsedHttpSource import okhttp3.Request import org.jsoup.nodes.Document @@ -14,46 +14,46 @@ import rx.Observable import java.text.SimpleDateFormat import java.util.Locale -class Xkcd : ParsedHttpSource() { +class TwoDGirlsTech : ParsedHttpSource() { - override val name = "xkcd" + override val name = "2dgirlstech" - override val baseUrl = "https://xkcd.com" + override val baseUrl = "https://2dgirls.tech" override val lang = "en" override val supportsLatest = false - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - val manga = SManga.create() - manga.setUrlWithoutDomain("/archive") - manga.title = "xkcd" - manga.artist = "Randall Munroe" - manga.author = "Randall Munroe" - manga.status = SManga.ONGOING - manga.description = "A webcomic of romance, sarcasm, math and language" - manga.thumbnail_url = thumbnailUrl + override fun fetchPopularAnime(page: Int): Observable<AnimesPage> { + val anime = SAnime.create() + anime.setUrlWithoutDomain("/archive") + anime.title = "xkcd" + anime.artist = "Randall Munroe" + anime.author = "Randall Munroe" + anime.status = SAnime.ONGOING + anime.description = "A webcomic of romance, sarcasm, math and language" + anime.thumbnail_url = thumbnailUrl - return Observable.just(MangasPage(arrayListOf(manga), false)) + return Observable.just(AnimesPage(arrayListOf(anime), false)) } - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false)) + override fun fetchSearchAnime(page: Int, query: String, filters: FilterList): Observable<AnimesPage> = Observable.just(AnimesPage(emptyList(), false)) - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = fetchPopularManga(1) - .map { it.mangas.first().apply { initialized = true } } + override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> = fetchPopularAnime(1) + .map { it.animes.first().apply { initialized = true } } - override fun chapterListSelector() = "div#middleContainer.box a" + override fun episodeListSelector() = "div#middleContainer.box a" - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.url = element.attr("href") - val number = chapter.url.removeSurrounding("/") - chapter.chapter_number = number.toFloat() - chapter.name = number + " - " + element.text() - chapter.date_upload = element.attr("title").let { + override fun episodeFromElement(element: Element): SEpisode { + val episode = SEpisode.create() + episode.url = element.attr("href") + val number = episode.url.removeSurrounding("/") + episode.episode_number = number.toFloat() + episode.name = number + " - " + element.text() + episode.date_upload = element.attr("title").let { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(it)?.time ?: 0L } - return chapter + return episode } override fun pageListParse(document: Document): List<Page> { @@ -113,27 +113,27 @@ class Xkcd : ParsedHttpSource() { override fun imageUrlParse(document: Document) = throw Exception("Not used") - override fun popularMangaSelector(): String = throw Exception("Not used") + override fun popularAnimeSelector(): String = throw Exception("Not used") - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not used") + override fun searchAnimeFromElement(element: Element): SAnime = throw Exception("Not used") - override fun searchMangaNextPageSelector(): String? = throw Exception("Not used") + override fun searchAnimeNextPageSelector(): String? = throw Exception("Not used") - override fun searchMangaSelector(): String = throw Exception("Not used") + override fun searchAnimeSelector(): String = throw Exception("Not used") - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") + override fun popularAnimeRequest(page: Int): Request = throw Exception("Not used") - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") + override fun searchAnimeRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used") - override fun popularMangaNextPageSelector(): String? = throw Exception("Not used") + override fun popularAnimeNextPageSelector(): String? = throw Exception("Not used") - override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used") + override fun popularAnimeFromElement(element: Element): SAnime = throw Exception("Not used") - override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used") + override fun animeDetailsParse(document: Document): SAnime = throw Exception("Not used") override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used") - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used") + override fun latestUpdatesFromElement(element: Element): SAnime = throw Exception("Not used") override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") diff --git a/src/en/vgperson/AndroidManifest.xml b/src/en/vgperson/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/vgperson/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/vgperson/build.gradle b/src/en/vgperson/build.gradle deleted file mode 100644 index 5a2f69c0b..000000000 --- a/src/en/vgperson/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'vgperson' - pkgNameSuffix = 'en.vgperson' - extClass = '.Vgperson' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/vgperson/res/mipmap-hdpi/ic_launcher.png b/src/en/vgperson/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a8e2402ac..000000000 Binary files a/src/en/vgperson/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vgperson/res/mipmap-mdpi/ic_launcher.png b/src/en/vgperson/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0a925b7dd..000000000 Binary files a/src/en/vgperson/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vgperson/res/mipmap-xhdpi/ic_launcher.png b/src/en/vgperson/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 44363b173..000000000 Binary files a/src/en/vgperson/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vgperson/res/mipmap-xxhdpi/ic_launcher.png b/src/en/vgperson/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2126db0ec..000000000 Binary files a/src/en/vgperson/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vgperson/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/vgperson/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3df99c376..000000000 Binary files a/src/en/vgperson/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vgperson/res/web_hi_res_512.png b/src/en/vgperson/res/web_hi_res_512.png deleted file mode 100644 index 070bdc94d..000000000 Binary files a/src/en/vgperson/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/vgperson/src/eu/kanade/tachiyomi/extension/en/vgperson/Vgperson.kt b/src/en/vgperson/src/eu/kanade/tachiyomi/extension/en/vgperson/Vgperson.kt deleted file mode 100644 index 2f3db3df6..000000000 --- a/src/en/vgperson/src/eu/kanade/tachiyomi/extension/en/vgperson/Vgperson.kt +++ /dev/null @@ -1,139 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.vgperson - -import android.os.Build.VERSION -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.nodes.TextNode -import rx.Observable - -class Vgperson : ParsedHttpSource() { - - override val name = "vgperson" - - override val lang = "en" - - override val supportsLatest = false - - override val baseUrl = "https://vgperson.com/other/mangaviewer.php" - - private val userAgent = "Mozilla/5.0 " + - "(Android ${VERSION.RELEASE}; Mobile) " + - "Tachiyomi/${BuildConfig.VERSION_NAME}" - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", userAgent) - add("Referer", baseUrl) - } - - override fun popularMangaSelector() = ".content a[href^=?m]" - - override fun popularMangaNextPageSelector(): String? = null - - override fun popularMangaRequest(page: Int) = GET(baseUrl, headers) - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - title = element.text() - url = element.attr("href") - thumbnail_url = getCover(title) - } - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = - client.newCall(mangaDetailsRequest(manga)).asObservableSuccess().map { - mangaDetailsParse(it).apply { - url = manga.url - title = manga.title - thumbnail_url = manga.thumbnail_url - initialized = true - } - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - status = when (document.select(".chaptername").first().text()) { - "(Complete)" -> SManga.COMPLETED - "(Series in Progress)" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - description = document.select(".content").first() - .childNodes().drop(5).takeWhile { - it.nodeName() != "table" - }.joinToString("") { - if (it is TextNode) it.text() - else when ((it as Element).tagName()) { - "br" -> "\n" - else -> it.text() - } - } - } - - override fun chapterListSelector() = ".chaptertable tbody tr" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - element.select("td > a").first().let { - name = it.text() - url = it.attr("href") - } - // append the name if it exists & remove the occasional hyphen - element.select(".chaptername")?.first()?.let { - name += " - ${it.text().substringAfter("- ")}" - } - // hardcode special chapter numbers for Three Days of Happiness - chapter_number = url.substringAfterLast("c=").toFloatOrNull() - ?: 16.5f + "0.${url.substringAfterLast("b=")}".toFloat() - scanlator = "vgperson" - date_upload = 0L - } - - override fun chapterListParse(response: Response) = - super.chapterListParse(response).sortedByDescending(SChapter::chapter_number) - - override fun pageListParse(document: Document) = - document.select("img").mapIndexed { i, img -> Page(i, "", img.attr("src")) } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = fetchPopularManga(1) - .map { mp -> MangasPage(mp.mangas.filter { it.title.contains(query, ignoreCase = true) }, false) } - - // get known manga covers from imgur - private fun getCover(title: String) = when (title) { - "The Festive Monster's Cheerful Failure" -> "kEK10GL.png" - "Azure and Claude" -> "buXnlmh.jpg" - "Three Days of Happiness" -> "kL5dvnp.jpg" - else -> null - }?.let { "https://i.imgur.com/$it" } - - override fun latestUpdatesSelector() = "" - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int) = - throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesFromElement(element: Element) = - throw UnsupportedOperationException("This method should not be called!") - - override fun searchMangaSelector() = "" - - override fun searchMangaNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException("This method should not be called!") - - override fun searchMangaFromElement(element: Element) = - throw UnsupportedOperationException("This method should not be called!") - - override fun searchMangaParse(response: Response) = - throw UnsupportedOperationException("This method should not be called!") - - override fun imageUrlParse(document: Document) = - throw UnsupportedOperationException("This method should not be called!") -} diff --git a/src/en/vizshonenjump/AndroidManifest.xml b/src/en/vizshonenjump/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/vizshonenjump/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/vizshonenjump/build.gradle b/src/en/vizshonenjump/build.gradle deleted file mode 100644 index d3e574ae7..000000000 --- a/src/en/vizshonenjump/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'VIZ Shonen Jump' - pkgNameSuffix = 'en.vizshonenjump' - extClass = '.VizShonenJump' - extVersionCode = 6 - libVersion = '1.2' -} - -dependencies { - implementation 'com.drewnoakes:metadata-extractor:2.14.0' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/vizshonenjump/res/mipmap-hdpi/ic_launcher.png b/src/en/vizshonenjump/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2733918dc..000000000 Binary files a/src/en/vizshonenjump/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vizshonenjump/res/mipmap-mdpi/ic_launcher.png b/src/en/vizshonenjump/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b0cf36ad5..000000000 Binary files a/src/en/vizshonenjump/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vizshonenjump/res/mipmap-xhdpi/ic_launcher.png b/src/en/vizshonenjump/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 0b666f7f4..000000000 Binary files a/src/en/vizshonenjump/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vizshonenjump/res/mipmap-xxhdpi/ic_launcher.png b/src/en/vizshonenjump/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 33f1b25fa..000000000 Binary files a/src/en/vizshonenjump/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vizshonenjump/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/vizshonenjump/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4cfe7c8b9..000000000 Binary files a/src/en/vizshonenjump/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/vizshonenjump/res/web_hi_res_512.png b/src/en/vizshonenjump/res/web_hi_res_512.png deleted file mode 100644 index 2bf7def62..000000000 Binary files a/src/en/vizshonenjump/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizImageInterceptor.kt b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizImageInterceptor.kt deleted file mode 100644 index 053ccac48..000000000 --- a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizImageInterceptor.kt +++ /dev/null @@ -1,170 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.vizshonenjump - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect -import com.drew.imaging.ImageMetadataReader -import com.drew.metadata.exif.ExifSubIFDDirectory -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.Response -import okhttp3.ResponseBody -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.InputStream - -class VizImageInterceptor : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - val response = chain.proceed(chain.request()) - - if (chain.request().url().queryParameter(SIGNATURE) == null) - return response - - val image = decodeImage(response.body()!!.byteStream()) - val body = ResponseBody.create(MEDIA_TYPE, image) - return response.newBuilder() - .body(body) - .build() - } - - private fun decodeImage(image: InputStream): ByteArray { - // See: https://stackoverflow.com/a/5924132 - // See: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/2678#issuecomment-645857603 - val byteOutputStream = ByteArrayOutputStream() - image.copyTo(byteOutputStream) - - val byteInputStreamForImage = ByteArrayInputStream(byteOutputStream.toByteArray()) - val byteInputStreamForMetadata = ByteArrayInputStream(byteOutputStream.toByteArray()) - - val imageData = getImageData(byteInputStreamForMetadata) - ?: return byteOutputStream.toByteArray() - - val input = BitmapFactory.decodeStream(byteInputStreamForImage) - val width = input.width - val height = input.height - val newWidth = (width - WIDTH_CUT).coerceAtLeast(imageData.width) - val newHeight = (height - HEIGHT_CUT).coerceAtLeast(imageData.height) - val blockWidth = newWidth / CELL_WIDTH_COUNT - val blockHeight = newHeight / CELL_HEIGHT_COUNT - - val result = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888) - val canvas = Canvas(result) - - // Draw the borders. - - // Top border. - canvas.drawImage( - from = input, - srcX = 0, - srcY = 0, - dstX = 0, - dstY = 0, - width = newWidth, - height = blockHeight - ) - // Left border. - canvas.drawImage( - from = input, - srcX = 0, - srcY = blockHeight + 10, - dstX = 0, - dstY = blockHeight, - width = blockWidth, - height = newHeight - 2 * blockHeight - ) - // Bottom border. - canvas.drawImage( - from = input, - srcX = 0, - srcY = (CELL_HEIGHT_COUNT - 1) * (blockHeight + 10), - dstX = 0, - dstY = (CELL_HEIGHT_COUNT - 1) * blockHeight, - width = newWidth, - height = height - (CELL_HEIGHT_COUNT - 1) * (blockHeight + 10) - ) - // Right border. - canvas.drawImage( - from = input, - srcX = (CELL_WIDTH_COUNT - 1) * (blockWidth + 10), - srcY = blockHeight + 10, - dstX = (CELL_WIDTH_COUNT - 1) * blockWidth, - dstY = blockHeight, - width = blockWidth + (newWidth - CELL_WIDTH_COUNT * blockWidth), - height = newHeight - 2 * blockHeight - ) - - // Draw the inner cells. - for ((m, y) in imageData.key.iterator().withIndex()) { - canvas.drawImage( - from = input, - srcX = (m % INNER_CELL_COUNT + 1) * (blockWidth + 10), - srcY = (m / INNER_CELL_COUNT + 1) * (blockHeight + 10), - dstX = (y % INNER_CELL_COUNT + 1) * blockWidth, - dstY = (y / INNER_CELL_COUNT + 1) * blockHeight, - width = blockWidth, - height = blockHeight - ) - } - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - return output.toByteArray() - } - - private fun Canvas.drawImage( - from: Bitmap, - srcX: Int, - srcY: Int, - dstX: Int, - dstY: Int, - width: Int, - height: Int - ) { - val srcRect = Rect(srcX, srcY, srcX + width, srcY + height) - val dstRect = Rect(dstX, dstY, dstX + width, dstY + height) - drawBitmap(from, srcRect, dstRect, null) - } - - private fun getImageData(inputStream: InputStream): ImageData? { - val metadata = ImageMetadataReader.readMetadata(inputStream) - - val sizeDir = metadata.directories.firstOrNull { - it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) && - it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_HEIGHT) - } - val metaWidth = sizeDir?.getInt(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) ?: COMMON_WIDTH - val metaHeight = sizeDir?.getInt(ExifSubIFDDirectory.TAG_IMAGE_HEIGHT) ?: COMMON_HEIGHT - - val keyDir = metadata.directories.firstOrNull { - it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_UNIQUE_ID) - } - val metaUniqueId = keyDir?.getString(ExifSubIFDDirectory.TAG_IMAGE_UNIQUE_ID) - ?: return null - - return ImageData(metaWidth, metaHeight, metaUniqueId) - } - - private data class ImageData(val width: Int, val height: Int, val uniqueId: String) { - val key: List<Int> by lazy { - uniqueId.split(":") - .map { it.toInt(16) } - } - } - - companion object { - private const val SIGNATURE = "Signature" - private val MEDIA_TYPE = MediaType.parse("image/png") - - private const val CELL_WIDTH_COUNT = 10 - private const val CELL_HEIGHT_COUNT = 15 - private const val INNER_CELL_COUNT = CELL_WIDTH_COUNT - 2 - - private const val WIDTH_CUT = 90 - private const val HEIGHT_CUT = 140 - - private const val COMMON_WIDTH = 800 - private const val COMMON_HEIGHT = 1200 - } -} diff --git a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt b/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt deleted file mode 100644 index 4f2163f40..000000000 --- a/src/en/vizshonenjump/src/eu/kanade/tachiyomi/extension/en/vizshonenjump/VizShonenJump.kt +++ /dev/null @@ -1,288 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.vizshonenjump - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -class VizShonenJump : ParsedHttpSource() { - - override val name = "VIZ Shonen Jump" - - override val baseUrl = "https://www.viz.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor(VizImageInterceptor()) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", "$baseUrl/shonenjump") - - private var mangaList: List<SManga>? = null - - override fun popularMangaRequest(page: Int): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl) - .build() - - return GET("$baseUrl/shonenjump", newHeaders, CacheControl.FORCE_NETWORK) - } - - override fun popularMangaParse(response: Response): MangasPage { - val mangasPage = super.popularMangaParse(response) - - if (mangasPage.mangas.isEmpty()) - throw Exception(COUNTRY_NOT_SUPPORTED) - - mangaList = mangasPage.mangas.sortedBy { it.title } - - return mangasPage - } - - override fun popularMangaSelector(): String = - "section.section_chapters div.o_sort_container div.o_sortable > a" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("div.pad-x-rg").first().text() - thumbnail_url = element.select("div.pos-r img.disp-bl").first() - ?.attr("data-original") - url = element.attr("href") - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesParse(response: Response): MangasPage { - val mangasPage = super.latestUpdatesParse(response) - - if (mangasPage.mangas.isEmpty()) - throw Exception(COUNTRY_NOT_SUPPORTED) - - mangaList = mangasPage.mangas.sortedBy { it.title } - - return mangasPage - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return super.fetchSearchManga(page, query, filters) - .map { - val filteredMangas = it.mangas.filter { m -> m.title.contains(query, true) } - MangasPage(filteredMangas, it.hasNextPage) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - popularMangaRequest(page) - - override fun searchMangaParse(response: Response): MangasPage { - val mangasPage = super.searchMangaParse(response) - - if (mangasPage.mangas.isEmpty()) - throw Exception(COUNTRY_NOT_SUPPORTED) - - return mangasPage - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga { - val seriesIntro = document.select("section#series-intro").first() - - // Get the thumbnail url from the manga list (if available), - // or fetch it for the first time (in backup restore, for example). - if (mangaList == null) { - val request = popularMangaRequest(1) - val response = client.newCall(request).execute() - // Call popularMangaParse to fill the manga list. - popularMangaParse(response) - } - - val mangaUrl = document.location().substringAfter(baseUrl) - val mangaFromList = mangaList!!.firstOrNull { it.url == mangaUrl } - - return SManga.create().apply { - author = seriesIntro.select("div.type-rg span").firstOrNull()?.text() - ?.replace("Created by ", "") - artist = author - status = SManga.ONGOING - description = seriesIntro.select("h4").firstOrNull()?.text() - thumbnail_url = mangaFromList?.thumbnail_url ?: "" - } - } - - override fun chapterListParse(response: Response): List<SChapter> { - val allChapters = super.chapterListParse(response) - - val newHeaders = headersBuilder() - .add("X-Requested-With", "XMLHttpRequest") - .set("Referer", response.request().url().toString()) - .build() - - val loginCheckRequest = GET(REFRESH_LOGIN_LINKS_URL, newHeaders) - val document = client.newCall(loginCheckRequest).execute().asJsoup() - val isLoggedIn = document.select("div#o_account-links-content").first()!! - .attr("logged_in")!!.toBoolean() - - if (isLoggedIn) { - return allChapters.map { oldChapter -> - oldChapter.apply { - url = url.substringAfter("'").substringBeforeLast("'") - } - } - } - - return allChapters.filter { !it.url.startsWith("javascript") } - .sortedByDescending { it.chapter_number } - } - - override fun chapterListSelector() = - "section.section_chapters div.o_sortable > a.o_chapter-container, " + - "section.section_chapters div.o_sortable div.o_chapter-vol-container tr.o_chapter a.o_chapter-container.pad-r-0" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val isVolume = element.select("div:nth-child(1) table").first() == null - - if (isVolume) { - name = element.text() - } else { - val leftSide = element.select("div:nth-child(1) table").first()!! - val rightSide = element.select("div:nth-child(2) table").first()!! - - name = rightSide.select("td").first()!!.text() - date_upload = leftSide.select("td[align=right]").first()!!.text().toDate() - } - - chapter_number = name.substringAfter("Ch. ").toFloatOrNull() ?: -1F - scanlator = "VIZ Media" - url = element.attr("data-target-url") - } - - override fun pageListRequest(chapter: SChapter): Request { - val mangaUrl = chapter.url - .substringBefore("-chapter") - .replace("jump/", "jump/chapters/") - - val newHeaders = headersBuilder() - .set("Referer", baseUrl + mangaUrl) - .build() - - return GET(baseUrl + chapter.url, newHeaders) - } - - override fun pageListParse(document: Document): List<Page> { - val pageCount = document.select("script:containsData(var pages)").first().data() - .substringAfter("= ") - .substringBefore(";") - .toInt() - val mangaId = document.location() - .substringAfterLast("/") - .substringBefore("?") - - return IntRange(1, pageCount) - .map { - val imageUrl = HttpUrl.parse("$baseUrl/manga/get_manga_url")!!.newBuilder() - .addQueryParameter("device_id", "3") - .addQueryParameter("manga_id", mangaId) - .addQueryParameter("page", it.toString()) - .addEncodedQueryParameter("referer", document.location()) - .toString() - - Page(it, imageUrl) - } - } - - override fun imageUrlRequest(page: Page): Request { - val url = HttpUrl.parse(page.url)!! - val referer = url.queryParameter("referer")!! - val newUrl = url.newBuilder() - .removeAllEncodedQueryParameters("referer") - .toString() - - val newHeaders = headersBuilder() - .add("X-Requested-With", "XMLHttpRequest") - .set("Referer", referer) - .build() - - return GET(newUrl, newHeaders) - } - - override fun imageUrlParse(response: Response): String { - val cdnUrl = response.body()!!.string() - val referer = response.request().header("Referer")!! - - return HttpUrl.parse(cdnUrl)!!.newBuilder() - .addEncodedQueryParameter("referer", referer) - .toString() - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imageUrl = HttpUrl.parse(page.imageUrl!!)!! - val referer = imageUrl.queryParameter("referer")!! - val newImageUrl = imageUrl.newBuilder() - .removeAllEncodedQueryParameters("referer") - .toString() - - val newHeaders = headersBuilder() - .set("Referer", referer) - .build() - - return GET(newImageUrl, newHeaders) - } - - private fun String.toDate(): Long { - return try { - DATE_FORMATTER.parse(this)!!.time - } catch (e: ParseException) { - 0L - } - } - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" - - private val DATE_FORMATTER by lazy { - SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH) - } - - private const val COUNTRY_NOT_SUPPORTED = "Your country is not supported, try using a VPN." - - private const val REFRESH_LOGIN_LINKS_URL = "https://www.viz.com/account/refresh_login_links" - } -} diff --git a/src/en/webcomics/AndroidManifest.xml b/src/en/webcomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/webcomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/webcomics/build.gradle b/src/en/webcomics/build.gradle deleted file mode 100644 index 9e56cd75f..000000000 --- a/src/en/webcomics/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Webcomics' - pkgNameSuffix = 'en.webcomics' - extClass = '.Webcomics' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/webcomics/res/mipmap-hdpi/ic_launcher.png b/src/en/webcomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 963a598f9..000000000 Binary files a/src/en/webcomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webcomics/res/mipmap-mdpi/ic_launcher.png b/src/en/webcomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5594e1daf..000000000 Binary files a/src/en/webcomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webcomics/res/mipmap-xhdpi/ic_launcher.png b/src/en/webcomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c0dd86396..000000000 Binary files a/src/en/webcomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webcomics/res/mipmap-xxhdpi/ic_launcher.png b/src/en/webcomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index acf32fecf..000000000 Binary files a/src/en/webcomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webcomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/webcomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index df9b90559..000000000 Binary files a/src/en/webcomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webcomics/res/web_hi_res_512.png b/src/en/webcomics/res/web_hi_res_512.png deleted file mode 100644 index 347f81251..000000000 Binary files a/src/en/webcomics/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/webcomics/src/eu/kanade/tachiyomi/extension/en/webcomics/Webcomics.kt b/src/en/webcomics/src/eu/kanade/tachiyomi/extension/en/webcomics/Webcomics.kt deleted file mode 100644 index ac477b586..000000000 --- a/src/en/webcomics/src/eu/kanade/tachiyomi/extension/en/webcomics/Webcomics.kt +++ /dev/null @@ -1,186 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.webcomics - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class Webcomics : ParsedHttpSource() { - - override val name = "Webcomics" - - override val baseUrl = "https://www.webcomicsapp.com" - - override val lang = "en" - - override val supportsLatest = true - - override fun popularMangaSelector() = "section.mangas div div.col-md-3" - - override fun latestUpdatesSelector() = "section.mangas div div.col-md-3" - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "https://www.webcomicsapp.com") - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/popular.html", headers) - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/latest.html", headers) - - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.select("h5").text() - } - return manga - } - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - - override fun popularMangaNextPageSelector() = null - - override fun latestUpdatesNextPageSelector() = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("section.book-info-left > .wrap") - - val manga = SManga.create() - manga.genre = infoElement.select(".labels > label").joinToString(", ") { it.text() } - manga.description = infoElement.select("p.p-description").text() - manga.thumbnail_url = infoElement.select("img").first()?.attr("src") - infoElement.select("p.p-schedule:first-of-type").text().let { - if (it.contains("IDK")) manga.status = SManga.COMPLETED else manga.status = SManga.ONGOING - } - return manga - } - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element): SManga { - val infoElement = element.select(".col-md-5") - val manga = SManga.create() - infoElement.let { - manga.title = it.select(".wiki-book-title").text().trim() - manga.setUrlWithoutDomain(it.select("a").first().attr("href")) - } - return manga - } - - override fun searchMangaSelector() = ".wiki-book-list > .row" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/wiki.html?search=$query&page=$page")?.newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreFilter -> { - val genre = getGenreList()[filter.state] - url?.addQueryParameter("category", genre) - } - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - var nextPage = true - val mangas = document.select(searchMangaSelector()).filter { - val shouldFilter = it.select(".col-md-2 > a").first().text() == "READ" - if (nextPage) { - nextPage = shouldFilter - } - shouldFilter - }.map { element -> - searchMangaFromElement(element) - } - - return MangasPage(mangas, if (nextPage) hasNextPage(document) else false) - } - - private fun hasNextPage(document: Document): Boolean { - return !document.select(".pagination .page-item.active + .page-item").isEmpty() - } - - override fun chapterListSelector() = "section.book-info-left > .wrap > ul > li" - - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - - /* Source only allows 20 chapters to be readable on their website, trying to read past - that results in a page list empty error; so might as well not grab them. */ - return if (document.select("${chapterListSelector()}:nth-child(21)").isEmpty()) { - document.select(chapterListSelector()).asReversed().map { chapterFromElement(it) } - } else { - val chapters = mutableListOf<SChapter>() - for (i in 1..20) - document.select("${chapterListSelector()}:nth-child($i)").map { chapters.add(chapterFromElement(it)) } - // Add a chapter notifying the user of the situation - val lockedNotification = SChapter.create() - lockedNotification.name = "[Attention] Additional chapters are restricted by the source to their own app" - lockedNotification.url = "wiki.html" - chapters.add(lockedNotification) - chapters.reversed() - } - } - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a") - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text().trim() - return chapter - } - - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + "/" + manga.url, headers) - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + "/" + chapter.url, headers) - - override fun pageListParse(document: Document) = document - .select("section.book-reader .img-list > li > img") - .mapIndexed { - i, element -> - Page(i, "", element.attr("data-original")) - } - - override fun imageUrlParse(document: Document) = "" - - private class GenreFilter(genres: Array<String>) : Filter.Select<String>("Genre", genres) - - override fun getFilterList() = FilterList( - GenreFilter(getGenreList()) - ) - - // [...$('.row.wiki-book-nav .col-md-8 ul a')].map(el => `"${el.textContent.trim()}"`).join(',\n') - // https://www.webcomicsapp.com/wiki.html - private fun getGenreList() = arrayOf( - "All", - "Fantasy", - "Comedy", - "Drama", - "Modern", - "Action", - "Monster", - "Romance", - "Boys'Love", - "Harem", - "Thriller", - "Historical", - "Sci-fi", - "Slice of Life" - ) -} diff --git a/src/en/webnovel/AndroidManifest.xml b/src/en/webnovel/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/webnovel/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/webnovel/build.gradle b/src/en/webnovel/build.gradle deleted file mode 100644 index 67150fc07..000000000 --- a/src/en/webnovel/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Webnovel.com' - pkgNameSuffix = 'en.webnovel' - extClass = '.Webnovel' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/webnovel/res/mipmap-hdpi/ic_launcher.png b/src/en/webnovel/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ddb847248..000000000 Binary files a/src/en/webnovel/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webnovel/res/mipmap-mdpi/ic_launcher.png b/src/en/webnovel/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index afa5ebdac..000000000 Binary files a/src/en/webnovel/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webnovel/res/mipmap-xhdpi/ic_launcher.png b/src/en/webnovel/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 368702634..000000000 Binary files a/src/en/webnovel/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webnovel/res/mipmap-xxhdpi/ic_launcher.png b/src/en/webnovel/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b644ecd35..000000000 Binary files a/src/en/webnovel/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webnovel/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/webnovel/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5a856a950..000000000 Binary files a/src/en/webnovel/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/webnovel/res/web_hi_res_512.png b/src/en/webnovel/res/web_hi_res_512.png deleted file mode 100644 index d379af3b4..000000000 Binary files a/src/en/webnovel/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/webnovel/src/eu/kanade/tachiyomi/extension/en/webnovel/Webnovel.kt b/src/en/webnovel/src/eu/kanade/tachiyomi/extension/en/webnovel/Webnovel.kt deleted file mode 100644 index 5d935d12b..000000000 --- a/src/en/webnovel/src/eu/kanade/tachiyomi/extension/en/webnovel/Webnovel.kt +++ /dev/null @@ -1,214 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.webnovel - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Webnovel : ParsedHttpSource() { - - override val name = "Webnovel.com" - - override val baseUrl = "https://www.webnovel.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM dd,yyyy", Locale.US) - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0 ") - add("Referer", baseUrl) - } - - // popular - override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/0_comic_page$page", headers) - - override fun popularMangaSelector() = "a.g_thumb, div.j_bookList .g_book_item a:has(img)" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = element.attr("abs:href").substringAfter(baseUrl) - manga.title = element.attr("title") - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - - override fun popularMangaNextPageSelector() = "[rel=next]" - - // latest - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/category/0_comic_page$page?orderBy=5", headers) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filters = if (filters.isEmpty()) getFilterList() else filters - val genre = filters.findInstance<GenreList>()?.toUriPart() - val order = filters.findInstance<OrderByFilter>()?.toUriPart() - val status = filters.findInstance<StatusFilter>()?.toUriPart() - - return when { - query!!.isNotEmpty() -> GET("$baseUrl/search?keywords=$query&type=2&pageIndex=$page", headers) - else -> GET("$baseUrl/category/$genre" + "_comic_page1?&orderBy=$order&bookStatus=$status") - } - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // manga details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - thumbnail_url = document.select("i.g_thumb img:first-child").attr("abs:src") - title = document.select("h2").text() - description = document.select(".j_synopsis p").text() - } - - // chapters - override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/catalog", headers) - - override fun chapterListSelector() = ".volume-item li a" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = if (element.select("svg").hasAttr("class")) { "\uD83D\uDD12 " } else { "" } + - element.attr("title") - date_upload = parseChapterDate(element.select(".oh small").text()) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - fun parseChapterDate(date: String): Long { - return if (date.contains("ago")) { - val value = date.split(' ')[0].toInt() - when { - "min" in date -> Calendar.getInstance().apply { - add(Calendar.MINUTE, value * -1) - }.timeInMillis - "hour" in date -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, value * -1) - }.timeInMillis - "day" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * -1) - }.timeInMillis - "week" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * 7 * -1) - }.timeInMillis - "month" in date -> Calendar.getInstance().apply { - add(Calendar.MONTH, value * -1) - }.timeInMillis - "year" in date -> Calendar.getInstance().apply { - add(Calendar.YEAR, value * -1) - }.timeInMillis - else -> { - 0L - } - } - } else { - try { - dateFormat.parse(date)?.time ?: 0 - } catch (_: Exception) { - 0L - } - } - } - - // pages - override fun pageListParse(document: Document): List<Page> { - return document.select("#comicPageContainer img").mapIndexed { i, element -> - Page(i, "", element.attr("data-original")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - // filter - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - StatusFilter(), - OrderByFilter(), - GenreList() - ) - - private class StatusFilter : UriPartFilter( - "Status", - arrayOf( - Pair("0", "All"), - Pair("1", "Ongoing"), - Pair("2", "Completed") - ) - ) - - private class OrderByFilter : UriPartFilter( - "Order By", - arrayOf( - Pair("1", "Default"), - Pair("1", "Popular"), - Pair("2", "Recommendation"), - Pair("3", "Collection"), - Pair("4", "Rates"), - Pair("5", "Updated") - ) - ) - private class GenreList : UriPartFilter( - "Select Genre", - arrayOf( - Pair("0", "All"), - Pair("60002", "Action"), - Pair("60014", "Adventure"), - Pair("60011", "Comedy"), - Pair("60009", "Cooking"), - Pair("60027", "Diabolical"), - Pair("60024", "Drama"), - Pair("60006", "Eastern"), - Pair("60022", "Fantasy"), - Pair("60017", "Harem"), - Pair("60018", "History"), - Pair("60015", "Horror"), - Pair("60013", "Inspiring"), - Pair("60029", "LGBT+"), - Pair("60016", "Magic"), - Pair("60008", "Mystery"), - Pair("60003", "Romance"), - Pair("60007", "School"), - Pair("60004", "Sci-fi"), - Pair("60019", "Slice of Life"), - Pair("60023", "Sports"), - Pair("60012", "Transmigration"), - Pair("60005", "Urban"), - Pair("60010", "Wuxia") - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first - } - - private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T -} diff --git a/src/en/wecomics/AndroidManifest.xml b/src/en/wecomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/wecomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/wecomics/build.gradle b/src/en/wecomics/build.gradle deleted file mode 100644 index cea83453f..000000000 --- a/src/en/wecomics/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'WeComics' - pkgNameSuffix = 'en.wecomics' - extClass = '.WeComics' - extVersionCode = 1 - libVersion = '1.2' -} - -dependencies { - implementation 'org.xxtea:xxtea-java:1.0.5' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/wecomics/res/mipmap-hdpi/ic_launcher.png b/src/en/wecomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2148d01ec..000000000 Binary files a/src/en/wecomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wecomics/res/mipmap-mdpi/ic_launcher.png b/src/en/wecomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 63305b1c9..000000000 Binary files a/src/en/wecomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wecomics/res/mipmap-xhdpi/ic_launcher.png b/src/en/wecomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 06e2936c0..000000000 Binary files a/src/en/wecomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wecomics/res/mipmap-xxhdpi/ic_launcher.png b/src/en/wecomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 650a571a5..000000000 Binary files a/src/en/wecomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wecomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/wecomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6eb69c424..000000000 Binary files a/src/en/wecomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wecomics/src/eu/kanade/tachiyomi/extension/en/wecomics/WeComics.kt b/src/en/wecomics/src/eu/kanade/tachiyomi/extension/en/wecomics/WeComics.kt deleted file mode 100644 index 0c91dcd8e..000000000 --- a/src/en/wecomics/src/eu/kanade/tachiyomi/extension/en/wecomics/WeComics.kt +++ /dev/null @@ -1,188 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.wecomics - -import android.util.Base64 -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.Request -import okhttp3.Response -import org.xxtea.XXTEA -import rx.Observable -import java.net.URLEncoder - -class WeComics : HttpSource() { - - override val name = "WeComics" - - override val baseUrl = "https://m.wecomics.com" - - override val lang = "en" - - override val supportsLatest = true - - private val gson = Gson() - - private fun getMangaId(url: String): String? = - Regex("""^/comic/index/id/\d+\?id=(\d+)""").find(url)?.groupValues?.get(1) - - private fun getChapterId(url: String): Pair<String, String> { - val pattern = Regex("""^/chapter/index\?id=(\d+)&cid=(\d+)""") - val matches = pattern.find(url)?.groupValues!! - return Pair(matches[1], matches[2]) - } - - private fun Int.toStatus() = when (this) { - 1 -> SManga.ONGOING - 2 -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Popular - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/h5/rank/getAllComicList/page/$page?plain=1") - - override fun popularMangaParse(response: Response): MangasPage { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - val mangas = jsonObject["data"]["comic_list"].asJsonArray.map { - SManga.create().apply { - url = "/comic/index/id/${it["comic_id"].asInt}?id=${it["comic_id"].asInt}" - title = it["title"].asString - author = it["artist_name"][0].asString.split(",,").joinToString() - description = it["brief_intrd"].asString - genre = it["tag"].asJsonArray.joinToString { it["name"].asString } - status = it["finish_state"].asInt.toStatus() - thumbnail_url = it["cover_v_url"].asString - } - } - return MangasPage(mangas, jsonObject["data"]["has_next_page"].asInt == 1) - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/h5/rank/getNewComicList/page/$page?plain=1", headers) - - override fun latestUpdatesParse(response: Response): MangasPage = - popularMangaParse(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val queryEncoded = URLEncoder.encode(query, "UTF-8") - return GET("$baseUrl/h5/search/smart/word/$queryEncoded?plain=1", headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - return MangasPage( - jsonObject["data"].asJsonArray.map { - SManga.create().apply { - url = "/comic/index/id/${it["comic_id"].asInt}?id=${it["comic_id"].asInt}" - title = it["title"].asString - author = it["artist_name"][0].asString.split(",,").joinToString() - status = SManga.UNKNOWN - thumbnail_url = it["cover_v_url"].asString - } - }, - false - ) - } - - // Details - - // mangaDetailsRequest is used for WebView - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - // For WebView - override fun mangaDetailsRequest(manga: SManga): Request = - GET("${baseUrl}${manga.url}&type=search", headers) - - override fun mangaDetailsParse(response: Response): SManga { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - val it = jsonObject["data"]["comic"].asJsonObject - return SManga.create().apply { - url = "/comic/index/id/${it["comic_id"].asInt}?id=${it["comic_id"].asInt}" - title = it["title"].asString - author = it["artist_name"][0].asString.split(",,").joinToString() - description = it["brief_intrd"].asString - genre = it["tag"].asJsonArray.joinToString { it["name"].asString } - status = it["finish_state"].asInt.toStatus() - thumbnail_url = it["cover_v_url"].asString - } - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request = - GET("https://m.wecomics.com/h5/comic/detail/id/${getMangaId(manga.url)}?plain=1", headers) - - override fun chapterListParse(response: Response): List<SChapter> { - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - val mangaId = jsonObject["data"]["comic"]["comic_id"].asInt - - return jsonObject["data"]["chapter_list"].asJsonArray.map { - SChapter.create().apply { - url = "/chapter/index?id=$mangaId&cid=${it["chapter_id"]}" - name = it["title"].asString - date_upload = it["publish_time"].asLong * 1000 - chapter_number = it["seq_no"].asFloat - if (it["vip_state"].asInt == 2) scanlator = "Premium" - } - } - } - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - val (mangaId, chapterId) = getChapterId(chapter.url) - return GET("$baseUrl/h5/comic/getPictureList/id/$mangaId/cid/$chapterId?plain=1", headers) - } - - override fun pageListParse(response: Response): List<Page> { - val url = response.request().url().toString() - - // Error code 401 when not logged in and data is empty when logged in, - // assuming this is populated after a purchase - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - if (jsonObject["error_code"].asInt != 2 && - jsonObject["data"]["chapter"]["data"].asString != "" - ) - throw Exception("Chapter is currently not available.") - - val data = jsonObject["data"]["chapter"]["data"].asString - val key = data.substring(0, 8) - val encrypted = Base64.decode(data.substring(8), Base64.DEFAULT) - val chData = XXTEA.decryptToString(encrypted, key) - - val jsonObjectInner = gson.fromJson<JsonObject>(chData) - val cdnUrl = jsonObjectInner["cdn_base_url"].asString - - // The inner JSON contains a list of parts of files, - // the parts appear to be split at a fixed size - return jsonObjectInner["picture_list"].asJsonArray.mapIndexed { i, it -> - Page(i, url, cdnUrl + it["picture_url"].asString) - } - } - - override fun imageUrlParse(response: Response): String = - throw UnsupportedOperationException("Not used") -} diff --git a/src/en/wutopia/AndroidManifest.xml b/src/en/wutopia/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/wutopia/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/wutopia/build.gradle b/src/en/wutopia/build.gradle deleted file mode 100644 index 5b5a4bbf6..000000000 --- a/src/en/wutopia/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Wutopia' - pkgNameSuffix = 'en.wutopia' - extClass = '.Wutopia' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/wutopia/res/mipmap-hdpi/ic_launcher.png b/src/en/wutopia/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 80f19c03b..000000000 Binary files a/src/en/wutopia/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wutopia/res/mipmap-mdpi/ic_launcher.png b/src/en/wutopia/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ed01adf6d..000000000 Binary files a/src/en/wutopia/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wutopia/res/mipmap-xhdpi/ic_launcher.png b/src/en/wutopia/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bf2852b7f..000000000 Binary files a/src/en/wutopia/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wutopia/res/mipmap-xxhdpi/ic_launcher.png b/src/en/wutopia/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3bea8f853..000000000 Binary files a/src/en/wutopia/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wutopia/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/wutopia/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 75db5a774..000000000 Binary files a/src/en/wutopia/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/wutopia/res/web_hi_res_512.png b/src/en/wutopia/res/web_hi_res_512.png deleted file mode 100644 index 68df5c5d0..000000000 Binary files a/src/en/wutopia/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/wutopia/src/eu/kanade/tachiyomi/extension/en/wutopia/Wutopia.kt b/src/en/wutopia/src/eu/kanade/tachiyomi/extension/en/wutopia/Wutopia.kt deleted file mode 100644 index c16b9e204..000000000 --- a/src/en/wutopia/src/eu/kanade/tachiyomi/extension/en/wutopia/Wutopia.kt +++ /dev/null @@ -1,213 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.wutopia - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import com.github.salomonbrys.kotson.bool -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import okhttp3.CacheControl -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class Wutopia : ConfigurableSource, HttpSource() { - - override val name = "Wutopia" - - override val baseUrl = "https://www.wutopiacomics.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Content-Type", "application/x-www-form-urlencoded") - .add("platform", "10") - - // Popular - - override fun popularMangaRequest(page: Int): Request { - val body = RequestBody.create(null, "pageNo=$page&pageSize=15&cartoonTypeId=&isFinish=&payState=&order=0") - return POST("$baseUrl/mobile/cartoon-collection/search-fuzzy", headers, body) - } - - override fun popularMangaParse(response: Response): MangasPage { - val json = gson.fromJson<JsonObject>(response.body()!!.string()) - - val mangas = json["list"].asJsonArray.map { - SManga.create().apply { - title = it["name"].asString - url = it["id"].asString - thumbnail_url = it["picUrlWebp"].asString - } - } - - return MangasPage(mangas, json["hasNext"].asBoolean) - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - val body = RequestBody.create(null, "type=8&pageNo=$page&pageSize=15") - return POST("$baseUrl/mobile/home-page/query", headers, body) - } - - override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val body = RequestBody.create(null, "pageNo=$page&pageSize=15&keyword=$query") - return POST("$baseUrl/mobile/cartoon-collection/search-fuzzy", headers, body) - } - - override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) - - // Details - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(apiMangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun apiMangaDetailsRequest(manga: SManga): Request { - val body = RequestBody.create(null, "id=${manga.url}&linkId=0") - return POST("$baseUrl/mobile/cartoon-collection/get", headers, body) - } - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET("$baseUrl/#/mobile/cartoon/detail-cartoon/${manga.url}") - } - - override fun mangaDetailsParse(response: Response): SManga { - return gson.fromJson<JsonObject>(response.body()!!.string())["cartoon"].let { json -> - SManga.create().apply { - thumbnail_url = json["acrossPicUrlWebp"].asString - author = json["author"].asString - genre = json["cartoonTypes"].asJsonArray.joinToString { it["name"].asString } - description = json["content"].asString - title = json["name"].asString - status = json["isFinishStr"].asString.toStatus() - } - } - } - - private fun String.toStatus() = when (this) { - "完结" -> SManga.COMPLETED - "连载" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request { - val body = RequestBody.create(null, "id=${manga.url}&pageSize=99999&pageNo=1&sort=0&linkId=0") - return POST("$baseUrl/mobile/cartoon-collection/list-chapter", headers, body, CacheControl.FORCE_NETWORK) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return gson.fromJson<JsonObject>(response.body()!!.string())["list"].asJsonArray - .let { json -> - if (chapterListPref() == "free") json.filter { it["isPayed"].bool } else json - } - .map { json -> - SChapter.create().apply { - url = json["id"].asString - name = json["name"].asString.let { if (it.isNotEmpty()) it else "Chapter " + json["chapterIndex"].asString } - date_upload = json["modifyTime"].asLong - } - }.reversed() - } - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - val body = RequestBody.create(null, "id=${chapter.url}&linkId=0") - return POST("$baseUrl/mobile/chapter/get", headers, body) - } - - override fun pageListParse(response: Response): List<Page> { - return gson.fromJson<JsonObject>(response.body()!!.string())["chapter"]["picList"].asJsonArray.mapIndexed { i, json -> - Page(i, "", json["picUrl"].asString) - } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Preferences - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val chapterListPref = androidx.preference.ListPreference(screen.context).apply { - key = SHOW_LOCKED_CHAPTERS_Title - title = SHOW_LOCKED_CHAPTERS_Title - entries = prefsEntries - entryValues = prefsEntryValues - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SHOW_LOCKED_CHAPTERS, entry).commit() - } - } - screen.addPreference(chapterListPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val chapterListPref = ListPreference(screen.context).apply { - key = SHOW_LOCKED_CHAPTERS_Title - title = SHOW_LOCKED_CHAPTERS_Title - entries = prefsEntries - entryValues = prefsEntryValues - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SHOW_LOCKED_CHAPTERS, entry).commit() - } - } - screen.addPreference(chapterListPref) - } - - private fun chapterListPref() = preferences.getString(SHOW_LOCKED_CHAPTERS, "free") - - companion object { - private const val SHOW_LOCKED_CHAPTERS_Title = "Wutopia requires login/payment for some chapters" - private const val SHOW_LOCKED_CHAPTERS = "WUTOPIA_LOCKED_CHAPTERS" - private val prefsEntries = arrayOf("Show all chapters (including pay-to-read)", "Only show free chapters") - private val prefsEntryValues = arrayOf("all", "free") - } -} diff --git a/src/en/xkcd/AndroidManifest.xml b/src/en/xkcd/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/xkcd/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/en/xkcd/build.gradle b/src/en/xkcd/build.gradle deleted file mode 100644 index 5287edba3..000000000 --- a/src/en/xkcd/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'xkcd' - pkgNameSuffix = 'en.xkcd' - extClass = '.Xkcd' - extVersionCode = 9 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/xkcd/res/mipmap-hdpi/ic_launcher.png b/src/en/xkcd/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 621a1b37b..000000000 Binary files a/src/en/xkcd/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/xkcd/res/mipmap-mdpi/ic_launcher.png b/src/en/xkcd/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2013e6c5a..000000000 Binary files a/src/en/xkcd/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/xkcd/res/mipmap-xhdpi/ic_launcher.png b/src/en/xkcd/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 86f96dba1..000000000 Binary files a/src/en/xkcd/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/xkcd/res/mipmap-xxhdpi/ic_launcher.png b/src/en/xkcd/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fd271d743..000000000 Binary files a/src/en/xkcd/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/xkcd/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/xkcd/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index da2e30e23..000000000 Binary files a/src/en/xkcd/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/xkcd/res/web_hi_res_512.png b/src/en/xkcd/res/web_hi_res_512.png deleted file mode 100644 index 80e6abdfc..000000000 Binary files a/src/en/xkcd/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/doujinyang/AndroidManifest.xml b/src/es/doujinyang/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/es/doujinyang/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/doujinyang/build.gradle b/src/es/doujinyang/build.gradle deleted file mode 100644 index 10abe7578..000000000 --- a/src/es/doujinyang/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Doujin-Yang' - pkgNameSuffix = 'es.doujinyang' - extClass = '.DoujinYang' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/doujinyang/res/mipmap-hdpi/ic_launcher.png b/src/es/doujinyang/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ec64c4536..000000000 Binary files a/src/es/doujinyang/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/doujinyang/res/mipmap-mdpi/ic_launcher.png b/src/es/doujinyang/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b77d6b578..000000000 Binary files a/src/es/doujinyang/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/doujinyang/res/mipmap-xhdpi/ic_launcher.png b/src/es/doujinyang/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 44663fb8d..000000000 Binary files a/src/es/doujinyang/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/doujinyang/res/mipmap-xxhdpi/ic_launcher.png b/src/es/doujinyang/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 25bbcecc5..000000000 Binary files a/src/es/doujinyang/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/doujinyang/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/doujinyang/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0c70a707f..000000000 Binary files a/src/es/doujinyang/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/doujinyang/res/web_hi_res_512.png b/src/es/doujinyang/res/web_hi_res_512.png deleted file mode 100644 index 0dbb13940..000000000 Binary files a/src/es/doujinyang/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/doujinyang/src/eu/kanade/tachiyomi/extension/es/doujinyang/DoujinYang.kt b/src/es/doujinyang/src/eu/kanade/tachiyomi/extension/es/doujinyang/DoujinYang.kt deleted file mode 100644 index 34279b400..000000000 --- a/src/es/doujinyang/src/eu/kanade/tachiyomi/extension/es/doujinyang/DoujinYang.kt +++ /dev/null @@ -1,342 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.doujinyang - -import android.net.Uri -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -@Nsfw -class DoujinYang : ParsedHttpSource() { - - override val name = "Doujin-Yang" - override val baseUrl = "https://doujin-es.com" - override val lang = "es" - override val supportsLatest = true - - override fun popularMangaSelector() = "article[id=item]" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = "article[id=item]" - override fun chapterListSelector() = throw Exception("Not Used") - - override fun popularMangaNextPageSelector() = "a[href*=directorio]:containsOwn(Última)" - override fun latestUpdatesNextPageSelector() = "nav#paginacion a:contains(Última)" - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/directorio/?orden=visitas&p=$page", headers) - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/reciente/doujin?p=$page", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = if (query.isNotBlank()) { - Uri.parse(baseUrl).buildUpon() - .appendQueryParameter("s", query) - } else { - val uri = Uri.parse("$baseUrl/directorio").buildUpon() - // Append uri filters - filters.forEach { - if (it is UriFilter) - it.addToUri(uri) - } - uri.appendQueryParameter("p", page.toString()) - } - return GET(uri.toString(), headers) - } - - // override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - // override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - - override fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + manga.url, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()) - .distinctBy { it.select("a").first().attr("abs:href") } - .map { latestUpdatesFromElement(it) } - val hasNextPage = latestUpdatesNextPageSelector().let { selector -> - document.select(selector).first() - } != null - return MangasPage(mangas, hasNextPage) - } - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").first().attr("abs:href")) - manga.title = element.select("a").first().text().trim() - return manga - } - - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").first().attr("abs:href")) - manga.title = element.select("h2").text().trim() - // manga.thumbnail_url = "https:" + element.select("img").attr("src") - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - return response.asJsoup().select("div#c_list a").map { element -> - SChapter.create().apply { - name = element.select("h3").text() - setUrlWithoutDomain(element.attr("abs:href")) - } - } - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd kk:mm:ss", Locale.US).parse(date)?.time ?: 0 - } - - override fun chapterFromElement(element: Element) = throw Exception("Not used") - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = document.select("img[src*=cover]").attr("abs:src") - manga.description = document.select("div[id=sinopsis]").last().ownText() - manga.author = document.select("div[id=info-i]").text().let { - if (it.contains("Autor", true)) { - it.substringAfter("Autor:").substringBefore("Fecha:").trim() - } else "N/A" - } - manga.artist = manga.author - val glist = document.select("div[id=categ] a[href*=genero]").map { it.text() } - manga.genre = glist.joinToString(", ") - manga.status = when (document.select("span[id=desarrollo]")?.first()?.text()) { - "En desarrollo" -> SManga.ONGOING - // "Completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - return manga - } - - override fun pageListRequest(chapter: SChapter): Request { - return POST( - baseUrl + chapter.url, - headersBuilder().add("Content-Type", "application/x-www-form-urlencoded").build(), - RequestBody.create(null, "info") - ) - } - - override fun pageListParse(response: Response): List<Page> { - return response.body()!!.string().substringAfter(",[").substringBefore("]") - .replace(Regex("""[\\"]"""), "").split(",").let { list -> - val path = "https:" + list[0] - list.drop(1).mapIndexed { i, img -> Page(i, "", path + img) } - } - } - - override fun pageListParse(document: Document) = throw Exception("Not Used") - override fun imageUrlParse(document: Document) = throw Exception("Not Used") - - override fun getFilterList() = FilterList( - Filter.Header("NOTA: ¡La búsqueda de títulos no funciona!"), // "Title search not working" - Filter.Separator(), - GenreFilter(), - LetterFilter(), - StatusFilter(), - SortFilter() - ) - - class GenreFilter : UriPartFilter( - "Género", - "genero", - arrayOf( - Pair("all", "All"), - Pair("1", "Ahegao"), - Pair("379", "Alien"), - Pair("2", "Anal"), - Pair("490", "Android18"), - Pair("717", "Angel"), - Pair("633", "Asphyxiation"), - Pair("237", "Bandages"), - Pair("77", "Bbw"), - Pair("143", "Bdsm"), - Pair("23", "Blackmail"), - Pair("113", "Blindfold"), - Pair("24", "Blowjob"), - Pair("166", "Blowjobface"), - Pair("25", "Body Writing"), - Pair("314", "Bodymodification"), - Pair("806", "Bodystocking"), - Pair("366", "Bodysuit"), - Pair("419", "Bodyswap"), - Pair("325", "Bodywriting"), - Pair("5", "Bondage"), - Pair("51", "Bukkake"), - Pair("410", "Catgirl"), - Pair("61", "Chastitybelt"), - Pair("78", "Cheating"), - Pair("293", "Cheerleader"), - Pair("62", "Collar"), - Pair("120", "Compilation"), - Pair("74", "Condom"), - Pair("63", "Corruption"), - Pair("191", "Corset"), - Pair("234", "Cosplaying"), - Pair("389", "Cowgirl"), - Pair("256", "Crossdressing"), - Pair("179", "Crotchtattoo"), - Pair("689", "Crown"), - Pair("733", "Cumflation"), - Pair("385", "Cumswap"), - Pair("251", "Cunnilingus"), - Pair("75", "Darkskin"), - Pair("180", "Daughter"), - Pair("52", "Deepthroat"), - Pair("28", "Defloration"), - Pair("198", "Demon"), - Pair("145", "Demongirl"), - Pair("64", "Drugs"), - Pair("95", "Drunk"), - Pair("462", "Femalesonly"), - Pair("82", "Femdom"), - Pair("139", "Ffmthreesome"), - Pair("823", "Fftthreesome"), - Pair("55", "Full Color"), - Pair("181", "Fullbodytattoo"), - Pair("203", "Fullcensorship"), - Pair("111", "Fullcolor"), - Pair("114", "Gag"), - Pair("3", "Glasses"), - Pair("515", "Gloryhole"), - Pair("116", "Humanpet"), - Pair("32", "Humiliation"), - Pair("147", "Latex"), - Pair("12", "Maid"), - Pair("4", "Milf"), - Pair("245", "Military"), - Pair("414", "Milking"), - Pair("34", "Mind Control"), - Pair("68", "Mindbreak"), - Pair("124", "Mindcontrol"), - Pair("645", "Nun"), - Pair("312", "Nurse"), - Pair("272", "Robot"), - Pair("7", "Romance"), - Pair("761", "Sundress"), - Pair("412", "Tailplug"), - Pair("253", "Tutor"), - Pair("259", "Twins"), - Pair("207", "Twintails"), - Pair("840", "Valkyrie"), - Pair("530", "Vampire"), - Pair("16", "Yuri"), - Pair("273", "Zombie") - ) - ) - - class LetterFilter : UriPartFilter( - "Letra", - "letra", - arrayOf( - Pair("all", "All"), - Pair("a", "A"), - Pair("b", "B"), - Pair("c", "C"), - Pair("d", "D"), - Pair("e", "E"), - Pair("f", "F"), - Pair("g", "G"), - Pair("h", "H"), - Pair("i", "I"), - Pair("j", "J"), - Pair("k", "K"), - Pair("l", "L"), - Pair("m", "M"), - Pair("n", "N"), - Pair("o", "O"), - Pair("p", "P"), - Pair("q", "Q"), - Pair("r", "R"), - Pair("s", "S"), - Pair("t", "T"), - Pair("u", "U"), - Pair("v", "V"), - Pair("w", "W"), - Pair("x", "X"), - Pair("y", "Y"), - Pair("z", "Z") - ) - ) - - class StatusFilter : UriPartFilter( - "Estado", - "estado", - arrayOf( - Pair("all", "All"), - Pair("1", "En desarrollo"), - Pair("0", "Finalizado") - ) - ) - - class SortFilter : UriPartFilterreq( - "Sort", - "orden", - arrayOf( - Pair("visitas", "Visitas"), - Pair("desc", "Descendente"), - Pair("asc", "Ascendente"), - Pair("lanzamiento", "Lanzamiento"), - Pair("nombre", "Nombre") - ) - ) - - /** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ - // vals: <name, display> - open class UriPartFilter( - displayName: String, - private val uriParam: String, - private val vals: Array<Pair<String, String>>, - private val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), - UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) - uri.appendQueryParameter(uriParam, vals[state].first) - } - } - - open class UriPartFilterreq( - displayName: String, - private val uriParam: String, - private val vals: Array<Pair<String, String>> - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()), UriFilter { - override fun addToUri(uri: Uri.Builder) { - uri.appendQueryParameter(uriParam, vals[state].first) - } - } - - /** - * Represents a filter that is able to modify a URI. - */ - private interface UriFilter { - fun addToUri(uri: Uri.Builder) - } -} diff --git a/src/es/heavenmanga/AndroidManifest.xml b/src/es/heavenmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/es/heavenmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/heavenmanga/build.gradle b/src/es/heavenmanga/build.gradle deleted file mode 100644 index 114b90c0d..000000000 --- a/src/es/heavenmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HeavenManga' - pkgNameSuffix = 'es.heavenmanga' - extClass = '.HeavenManga' - extVersionCode = 5 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/heavenmanga/res/mipmap-hdpi/ic_launcher.png b/src/es/heavenmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 27183e6bf..000000000 Binary files a/src/es/heavenmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/heavenmanga/res/mipmap-mdpi/ic_launcher.png b/src/es/heavenmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 327f28479..000000000 Binary files a/src/es/heavenmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/heavenmanga/res/mipmap-xhdpi/ic_launcher.png b/src/es/heavenmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 83765e6a1..000000000 Binary files a/src/es/heavenmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/heavenmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/es/heavenmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 88e821198..000000000 Binary files a/src/es/heavenmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/heavenmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/heavenmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 41f49240f..000000000 Binary files a/src/es/heavenmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/heavenmanga/res/web_hi_res_512.png b/src/es/heavenmanga/res/web_hi_res_512.png deleted file mode 100644 index e34ec90c0..000000000 Binary files a/src/es/heavenmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/heavenmanga/src/eu/kanade/tachiyomi/extension/es/heavenmanga/HeavenManga.kt b/src/es/heavenmanga/src/eu/kanade/tachiyomi/extension/es/heavenmanga/HeavenManga.kt deleted file mode 100644 index c739bda97..000000000 --- a/src/es/heavenmanga/src/eu/kanade/tachiyomi/extension/es/heavenmanga/HeavenManga.kt +++ /dev/null @@ -1,316 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.heavenmanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class HeavenManga : ParsedHttpSource() { - - override val name = "HeavenManga" - - override val baseUrl = "https://heavenmanga.com" - - override val lang = "es" - - // latest is broken on the site, it's the same as popular so turning it off - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder { - return Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) Gecko/20100101 Firefox/75") - } - - override fun popularMangaSelector() = "div.page-item-detail" - - override fun latestUpdatesSelector() = "#container .ultimos_epis .not" - - override fun searchMangaSelector() = "div.c-tabs-item__content, ${popularMangaSelector()}" - - override fun chapterListSelector() = "div.listing-chapters_wrap tr" - - override fun popularMangaNextPageSelector() = "ul.pagination a[rel=next]" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/top?orderby=views&page=$page", headers) - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val searchUrl = "$baseUrl/buscar?query=$query" - - // Filter - val pageParameter = if (page > 1) "?page=$page" else "" - - if (query.isBlank()) { - val ext = ".html" - var name: String - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - if (filter.toUriPart().isNotBlank() && filter.state != 0) { - name = filter.toUriPart() - return GET("$baseUrl/genero/$name$ext$pageParameter", headers) - } - } - is AlphabeticoFilter -> { - if (filter.toUriPart().isNotBlank() && filter.state != 0) { - name = filter.toUriPart() - return GET("$baseUrl/letra/$name$ext$pageParameter", headers) - } - } - is ListaCompletasFilter -> { - if (filter.toUriPart().isNotBlank() && filter.state != 0) { - name = filter.toUriPart() - return GET("$baseUrl/$name$pageParameter", headers) - } - } - } - } - } - - return GET(searchUrl, headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - return if (response.request().url().toString().contains("query=")) super.searchMangaParse(response) - else popularMangaParse(response) - } - - // get contents of a url - private fun getUrlContents(url: String): Document = client.newCall(GET(url, headers)).execute().asJsoup() - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("div.manga-name").text() - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - element.select("a").let { - val latestChapter = getUrlContents(it.attr("href")) - val url = latestChapter.select(".rpwe-clearfix:last-child a") - setUrlWithoutDomain(url.attr("href")) - title = it.select("span span").text() - thumbnail_url = it.select("img").attr("src") - } - } - - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - element.select("h4 a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("img").attr("abs:data-src") - } - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - scanlator = element.select("span.pull-right").text() - } - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - document.select("div.tab-summary").let { info -> - genre = info.select("div.genres-content a").joinToString { it.text() } - thumbnail_url = info.select("div.summary_image img").attr("abs:data-src") - } - description = document.select("div.description-summary p").text() - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun pageListRequest(chapter: SChapter): Request { - return getUrlContents(baseUrl + chapter.url).select("a[id=leer]").attr("abs:href") - .let { GET(it, headers) } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("script:containsData(pUrl)").first().data() - .substringAfter("pUrl=[").substringBefore("\"},];").split("\"},") - .mapIndexed { i, string -> Page(i, "", string.substringAfterLast("\"")) } - } - - /** - * Array.from(document.querySelectorAll('.categorias a')).map(a => `Pair("${a.textContent}", "${a.getAttribute('href')}")`).join(',\n') - * on https://heavenmanga.com/top/ - * */ - private class GenreFilter : UriPartFilter( - "Géneros", - arrayOf( - Pair("Todo", ""), - Pair("Accion", "accion"), - Pair("Adulto", "adulto"), - Pair("Aventura", "aventura"), - Pair("Artes Marciales", "artes+marciales"), - Pair("Acontesimientos de la Vida", "acontesimientos+de+la+vida"), - Pair("Bakunyuu", "bakunyuu"), - Pair("Sci-fi", "sci-fi"), - Pair("Comic", "comic"), - Pair("Combate", "combate"), - Pair("Comedia", "comedia"), - Pair("Cooking", "cooking"), - Pair("Cotidiano", "cotidiano"), - Pair("Colegialas", "colegialas"), - Pair("Critica social", "critica+social"), - Pair("Ciencia ficcion", "ciencia+ficcion"), - Pair("Cambio de genero", "cambio+de+genero"), - Pair("Cosas de la Vida", "cosas+de+la+vida"), - Pair("Drama", "drama"), - Pair("Deporte", "deporte"), - Pair("Doujinshi", "doujinshi"), - Pair("Delincuentes", "delincuentes"), - Pair("Ecchi", "ecchi"), - Pair("Escolar", "escolar"), - Pair("Erotico", "erotico"), - Pair("Escuela", "escuela"), - Pair("Estilo de Vida", "estilo+de+vida"), - Pair("Fantasia", "fantasia"), - Pair("Fragmentos de la Vida", "fragmentos+de+la+vida"), - Pair("Gore", "gore"), - Pair("Gender Bender", "gender+bender"), - Pair("Humor", "humor"), - Pair("Harem", "harem"), - Pair("Haren", "haren"), - Pair("Hentai", "hentai"), - Pair("Horror", "horror"), - Pair("Historico", "historico"), - Pair("Josei", "josei"), - Pair("Loli", "loli"), - Pair("Light", "light"), - Pair("Lucha Libre", "lucha+libre"), - Pair("Manga", "manga"), - Pair("Mecha", "mecha"), - Pair("Magia", "magia"), - Pair("Maduro", "maduro"), - Pair("Manhwa", "manhwa"), - Pair("Manwha", "manwha"), - Pair("Mature", "mature"), - Pair("Misterio", "misterio"), - Pair("Mutantes", "mutantes"), - Pair("Novela", "novela"), - Pair("Orgia", "orgia"), - Pair("OneShot", "oneshot"), - Pair("OneShots", "oneshots"), - Pair("Psicologico", "psicologico"), - Pair("Romance", "romance"), - Pair("Recuentos de la vida", "recuentos+de+la+vida"), - Pair("Smut", "smut"), - Pair("Shojo", "shojo"), - Pair("Shonen", "shonen"), - Pair("Seinen", "seinen"), - Pair("Shoujo", "shoujo"), - Pair("Shounen", "shounen"), - Pair("Suspenso", "suspenso"), - Pair("School Life", "school+life"), - Pair("Sobrenatural", "sobrenatural"), - Pair("SuperHeroes", "superheroes"), - Pair("Supernatural", "supernatural"), - Pair("Slice of Life", "slice+of+life"), - Pair("Super Poderes", "ssuper+poderes"), - Pair("Terror", "terror"), - Pair("Torneo", "torneo"), - Pair("Tragedia", "tragedia"), - Pair("Transexual", "transexual"), - Pair("Vida", "vida"), - Pair("Vampiros", "vampiros"), - Pair("Violencia", "violencia"), - Pair("Vida Pasada", "vida+pasada"), - Pair("Vida Cotidiana", "vida+cotidiana"), - Pair("Vida de Escuela", "vida+de+escuela"), - Pair("Webtoon", "webtoon"), - Pair("Webtoons", "webtoons"), - Pair("Yaoi", "yaoi"), - Pair("Yuri", "yuri") - ) - ) - - /** - * Array.from(document.querySelectorAll('.letras a')).map(a => `Pair("${a.textContent}", "${a.getAttribute('href')}")`).join(',\n') - * on https://heavenmanga.com/top/ - * */ - private class AlphabeticoFilter : UriPartFilter( - "Alfabético", - arrayOf( - Pair("Todo", ""), - Pair("A", "a"), - Pair("B", "b"), - Pair("C", "c"), - Pair("D", "d"), - Pair("E", "e"), - Pair("F", "f"), - Pair("G", "g"), - Pair("H", "h"), - Pair("I", "i"), - Pair("J", "j"), - Pair("K", "k"), - Pair("L", "l"), - Pair("M", "m"), - Pair("N", "n"), - Pair("O", "o"), - Pair("P", "p"), - Pair("Q", "q"), - Pair("R", "r"), - Pair("S", "s"), - Pair("T", "t"), - Pair("U", "u"), - Pair("V", "v"), - Pair("W", "w"), - Pair("X", "x"), - Pair("Y", "y"), - Pair("Z", "z"), - Pair("0-9", "0-9") - ) - ) - - /** - * Array.from(document.querySelectorAll('#t li a')).map(a => `Pair("${a.textContent}", "${a.getAttribute('href')}")`).join(',\n') - * on https://heavenmanga.com/top/ - * */ - private class ListaCompletasFilter : UriPartFilter( - "Lista Completa", - arrayOf( - Pair("Todo", ""), - Pair("Lista Comis", "comic"), - Pair("Lista Novelas", "novela"), - Pair("Lista Adulto", "adulto") - ) - ) - - override fun getFilterList() = FilterList( - // Search and filter don't work at the same time - Filter.Header("NOTA: Los filtros se ignoran si se utiliza la búsqueda de texto."), - Filter.Header("Sólo se puede utilizar un filtro a la vez."), - Filter.Separator(), - GenreFilter(), - AlphabeticoFilter(), - ListaCompletasFilter() - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/es/ikuhentai/AndroidManifest.xml b/src/es/ikuhentai/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/es/ikuhentai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/ikuhentai/build.gradle b/src/es/ikuhentai/build.gradle deleted file mode 100755 index 39a925877..000000000 --- a/src/es/ikuhentai/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Ikuhentai' - pkgNameSuffix = 'es.ikuhentai' - extClass = '.Ikuhentai' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/ikuhentai/res/mipmap-hdpi/ic_launcher.png b/src/es/ikuhentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f7fc4caf8..000000000 Binary files a/src/es/ikuhentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/ikuhentai/res/mipmap-mdpi/ic_launcher.png b/src/es/ikuhentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4e38c54ff..000000000 Binary files a/src/es/ikuhentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/ikuhentai/res/mipmap-xhdpi/ic_launcher.png b/src/es/ikuhentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 18a1b211d..000000000 Binary files a/src/es/ikuhentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/ikuhentai/res/mipmap-xxhdpi/ic_launcher.png b/src/es/ikuhentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 71855487a..000000000 Binary files a/src/es/ikuhentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/ikuhentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/ikuhentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7b9e298e1..000000000 Binary files a/src/es/ikuhentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/ikuhentai/res/web_hi_res_512.png b/src/es/ikuhentai/res/web_hi_res_512.png deleted file mode 100644 index 7d7641987..000000000 Binary files a/src/es/ikuhentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/ikuhentai/src/eu/kanade/tachiyomi/extension/es/ikuhentai/Ikuhentai.kt b/src/es/ikuhentai/src/eu/kanade/tachiyomi/extension/es/ikuhentai/Ikuhentai.kt deleted file mode 100755 index 5015b36a1..000000000 --- a/src/es/ikuhentai/src/eu/kanade/tachiyomi/extension/es/ikuhentai/Ikuhentai.kt +++ /dev/null @@ -1,270 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.ikuhentai - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -@Nsfw -class Ikuhentai : ParsedHttpSource() { - override val name = "Ikuhentai" - override val baseUrl = "https://ikuhentai.net/" - override val lang = "es" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=views", headers) - } - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=latest", headers) - } - // LIST SELECTOR - override fun popularMangaSelector() = "div.c-tabs-item__content" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - - // ELEMENT - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - // NEXT SELECTOR - override fun popularMangaNextPageSelector() = "a.nextpostslink" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.col-4.col-sm-2.col-md-2 > div > a > img").attr("data-src") - element.select("div.tab-thumb > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder() - url.addQueryParameter("post_type", "wp-manga") - val pattern = "\\s+".toRegex() - val q = query.replace(pattern, "+") - if (query.isNotEmpty()) { - url.addQueryParameter("s", q) - } else { - url.addQueryParameter("s", "") - } - - var orderBy: String - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { -// is Status -> url.addQueryParameter("manga_status", arrayOf("", "completed", "ongoing")[filter.state]) - is GenreList -> { - val genreInclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } - } - if (genreInclude.isNotEmpty()) { - genreInclude.forEach { genre -> - url.addQueryParameter("genre[]", genre) - } - } - } - is StatusList -> { - val statuses = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - statuses.add(it.id) - } - } - if (statuses.isNotEmpty()) { - statuses.forEach { status -> - url.addQueryParameter("status[]", status) - } - } - } - - is SortBy -> { - orderBy = filter.toUriPart() - url.addQueryParameter("m_orderby", orderBy) - } - is TextField -> url.addQueryParameter(filter.key, filter.state) - } - } - - return GET(url.toString(), headers) - } - - // max 200 results - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.site-content").first() - - val manga = SManga.create() - manga.author = infoElement.select("div.author-content")?.text() - manga.artist = infoElement.select("div.artist-content")?.text() - - val genres = mutableListOf<String>() - infoElement.select("div.genres-content a").forEach { element -> - val genre = element.text() - genres.add(genre) - } - manga.genre = genres.joinToString(", ") - manga.status = parseStatus(infoElement.select("div.post-status > div:nth-child(2) > div.summary-content").text()) - - manga.description = document.select("div.description-summary")?.text() - manga.thumbnail_url = document.select("div.summary_image > a > img").attr("data-src") - - return manga - } - - private fun parseStatus(element: String): Int = when { - - element.toLowerCase().contains("ongoing") -> SManga.ONGOING - element.toLowerCase().contains("completado") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "li.wp-manga-chapter" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - var url = urlElement.attr("href") - url = url.replace("/p/1", "") - url += "?style=list" - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(url) - chapter.name = urlElement.text() - - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""Chapter\s([0-9]+)""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[1]?.value!!.toFloat() - } - } - } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - var i = 0 - document.select("div.reading-content * img").forEach { element -> - val url = element.attr("data-src") - i++ - if (url.isNotEmpty()) { - pages.add(Page(i, "", url)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - // private class Status : Filter.TriState("Completed") - private class TextField(name: String, val key: String) : Filter.Text(name) - private class SortBy : UriPartFilter( - "Ordenar por", - arrayOf( - Pair("Relevance", ""), - Pair("Latest", "latest"), - Pair("A-Z", "alphabet"), - Pair("Calificación", "rating"), - Pair("Tendencia", "trending"), - Pair("Más visto", "views"), - Pair("Nuevo", "new-manga") - ) - ) - - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - private class Status(name: String, val id: String = name) : Filter.TriState(name) - private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Estado", statuses) - - override fun getFilterList() = FilterList( -// TextField("Judul", "title"), - TextField("Autor", "author"), - TextField("Año de publicación", "release"), - SortBy(), - StatusList(getStatusList()), - GenreList(getGenreList()) - ) - private fun getStatusList() = listOf( - Status("Completado", "end"), - Status("En emisión", "on-going"), - Status("Cancelado", "canceled"), - Status("Pausado", "on-hold") - ) - private fun getGenreList() = listOf( - Genre("Ahegao", "ahegao"), - Genre("Anal", "anal"), - Genre("Bestiality", "bestialidad"), - Genre("Bondage", "bondage"), - Genre("Bukkake", "bukkake"), - Genre("Chicas monstruo", "chicas-monstruo"), - Genre("Chikan", "chikan"), - Genre("Colegialas", "colegialas"), - Genre("Comics porno", "comics-porno"), - Genre("Dark Skin", "dark-skin"), - Genre("Demonios", "demonios"), - Genre("Ecchi", "ecchi"), - Genre("Embarazadas", "embarazadas"), - Genre("Enfermeras", "enfermeras"), - Genre("Eroges", "eroges"), - Genre("Fantasía", "fantasia"), - Genre("Futanari", "futanari"), - Genre("Gangbang", "gangbang"), - Genre("Gemelas", "gemelas"), - Genre("Gender Bender", "gender-bender"), - Genre("Gore", "gore"), - Genre("Handjob", "handjob"), - Genre("Harem", "harem"), - Genre("Hipnosis", "hipnosis"), - Genre("Incesto", "incesto"), - Genre("Loli", "loli"), - Genre("Maids", "maids"), - Genre("Masturbación", "masturbacion"), - Genre("Milf", "milf"), - Genre("Mind Break", "mind-break"), - Genre("My Hero Academia", "my-hero-academia"), - Genre("Naruto", "naruto"), - Genre("Netorare", "netorare"), - Genre("Paizuri", "paizuri"), - Genre("Pokemon", "pokemon"), - Genre("Profesora", "profesora"), - Genre("Prostitución", "prostitucion"), - Genre("Romance", "romance"), - Genre("Straight Shota", "straight-shota"), - Genre("Tentáculos", "tentaculos"), - Genre("Virgen", "virgen"), - Genre("Yaoi", "yaoi"), - Genre("Yuri", "yuri") - ) - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/es/inmanga/AndroidManifest.xml b/src/es/inmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/es/inmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/inmanga/build.gradle b/src/es/inmanga/build.gradle deleted file mode 100644 index 73438dce5..000000000 --- a/src/es/inmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'InManga' - pkgNameSuffix = 'es.inmanga' - extClass = '.InManga' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/inmanga/res/mipmap-hdpi/ic_launcher.png b/src/es/inmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 5182f0543..000000000 Binary files a/src/es/inmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/inmanga/res/mipmap-mdpi/ic_launcher.png b/src/es/inmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3a03e9d3d..000000000 Binary files a/src/es/inmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/inmanga/res/mipmap-xhdpi/ic_launcher.png b/src/es/inmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 02428900c..000000000 Binary files a/src/es/inmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/inmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/es/inmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 555617b87..000000000 Binary files a/src/es/inmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/inmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/inmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9310fb036..000000000 Binary files a/src/es/inmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/inmanga/res/web_hi_res_512.png b/src/es/inmanga/res/web_hi_res_512.png deleted file mode 100644 index aef85dc65..000000000 Binary files a/src/es/inmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InManga.kt b/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InManga.kt deleted file mode 100644 index 292f95218..000000000 --- a/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InManga.kt +++ /dev/null @@ -1,193 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.inmanga - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class InManga : ParsedHttpSource() { - - override val name = "InManga" - - override val baseUrl = "https://inmanga.com" - - override val lang = "es" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val postHeaders = headers.newBuilder() - .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - .add("X-Requested-With", "XMLHttpRequest") - .build() - - private val gson = Gson() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - val skip = (page - 1) * 10 - val body = RequestBody.create(null, "filter%5Bgeneres%5D%5B%5D=-1&filter%5BqueryString%5D=&filter%5Bskip%5D=$skip&filter%5Btake%5D=10&filter%5Bsortby%5D=1&filter%5BbroadcastStatus%5D=0&filter%5BonlyFavorites%5D=false&d=") - - return POST("$baseUrl/manga/getMangasConsultResult", postHeaders, body) - } - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "body" - - // Latest - - // Search filtered by "Recién actualizado" - override fun latestUpdatesRequest(page: Int): Request { - val skip = (page - 1) * 10 - val body = RequestBody.create(null, "filter%5Bgeneres%5D%5B%5D=-1&filter%5BqueryString%5D=&filter%5Bskip%5D=$skip&filter%5Btake%5D=10&filter%5Bsortby%5D=3&filter%5BbroadcastStatus%5D=0&filter%5BonlyFavorites%5D=false&d=") - - return POST("$baseUrl/manga/getMangasConsultResult", postHeaders, body) - } - - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = "body" - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val skip = (page - 1) * 10 - val body = RequestBody.create(null, "filter%5Bgeneres%5D%5B%5D=-1&filter%5BqueryString%5D=$query&filter%5Bskip%5D=$skip&filter%5Btake%5D=10&filter%5Bsortby%5D=1&filter%5BbroadcastStatus%5D=0&filter%5BonlyFavorites%5D=false&d=") - - return POST("$baseUrl/manga/getMangasConsultResult", postHeaders, body) - } - - override fun searchMangaParse(response: Response): MangasPage { - val mangas = mutableListOf<SManga>() - val document = response.asJsoup() - - document.select(searchMangaSelector()).map { mangas.add(searchMangaFromElement(it)) } - - return MangasPage(mangas, document.select(searchMangaSelector()).count() == 10) - } - - override fun searchMangaSelector() = "body > a" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("h4.m0").text() - manga.thumbnail_url = element.select("img").attr("abs:data-src") - - return manga - } - - override fun searchMangaNextPageSelector(): String? = null - - // Manga summary page - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - - document.select("div.col-md-3 div.panel.widget").let { info -> - manga.thumbnail_url = info.select("img").attr("abs:src") - manga.status = parseStatus(info.select(" a.list-group-item:contains(estado) span").text()) - } - document.select("div.col-md-9").let { info -> - manga.title = info.select("h1").text() - manga.description = info.select("div.panel-body").text() - } - - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("En emisión") -> SManga.ONGOING - status.contains("Finalizado") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request { - return GET("$baseUrl/chapter/getall?mangaIdentification=${manga.url.substringAfterLast("/")}", headers) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = mutableListOf<SChapter>() - val data = response.body()!!.string().substringAfter("{\"data\":\"").substringBeforeLast("\"}") - .replace("\\", "") - - gson.fromJson<JsonObject>(data)["result"].asJsonArray.forEach { chapters.add(chapterFromJson(it)) } - - return chapters.sortedBy { it.chapter_number.toInt() }.reversed() - } - - override fun chapterListSelector() = "not using" - - private fun chapterFromJson(json: JsonElement): SChapter { - val chapter = SChapter.create() - - chapter.url = "/chapter/chapterIndexControls?identification=${json["Identification"].string}" - json["FriendlyChapterNumberUrl"].string.replace("-", ".").let { num -> - chapter.name = "Chapter $num" - chapter.chapter_number = num.toFloat() - } - chapter.date_upload = parseChapterDate(json["RegistrationDate"].string) ?: 0 - - return chapter - } - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - companion object { - val dateFormat by lazy { - SimpleDateFormat("yyyy-MM-dd", Locale.US) - } - } - - private fun parseChapterDate(string: String): Long? { - return dateFormat.parse(string)?.time ?: 0L - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val ch = document.select("[id=\"FriendlyChapterNumberUrl\"]").attr("value") - val title = document.select("[id=\"FriendlyMangaName\"]").attr("value") - - document.select("img.ImageContainer").forEachIndexed { i, img -> - pages.add(Page(i, "", "$baseUrl/images/manga/$title/chapter/$ch/page/${i + 1}/${img.attr("id")}")) - } - - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/es/kumanga/AndroidManifest.xml b/src/es/kumanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/es/kumanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/kumanga/build.gradle b/src/es/kumanga/build.gradle deleted file mode 100755 index d14eb5876..000000000 --- a/src/es/kumanga/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Kumanga' - pkgNameSuffix = 'es.kumanga' - extClass = '.Kumanga' - extVersionCode = 6 - libVersion = '1.2' -} - -dependencies { - compileOnly 'com.google.code.gson:gson:2.8.5' - compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/kumanga/res/mipmap-hdpi/ic_launcher.png b/src/es/kumanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c8a55e202..000000000 Binary files a/src/es/kumanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/kumanga/res/mipmap-mdpi/ic_launcher.png b/src/es/kumanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e731fc2a1..000000000 Binary files a/src/es/kumanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/kumanga/res/mipmap-xhdpi/ic_launcher.png b/src/es/kumanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 597f7a5a3..000000000 Binary files a/src/es/kumanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/kumanga/res/mipmap-xxhdpi/ic_launcher.png b/src/es/kumanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5987b29bf..000000000 Binary files a/src/es/kumanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/kumanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/kumanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c4da69246..000000000 Binary files a/src/es/kumanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/kumanga/res/web_hi_res_512.png b/src/es/kumanga/res/web_hi_res_512.png deleted file mode 100644 index 4c34dffe2..000000000 Binary files a/src/es/kumanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/kumanga/src/eu/kanade/tachiyomi/extension/es/kumanga/Kumanga.kt b/src/es/kumanga/src/eu/kanade/tachiyomi/extension/es/kumanga/Kumanga.kt deleted file mode 100755 index 4b92aa3a7..000000000 --- a/src/es/kumanga/src/eu/kanade/tachiyomi/extension/es/kumanga/Kumanga.kt +++ /dev/null @@ -1,307 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.kumanga - -import android.util.Base64 -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import kotlin.math.roundToInt - -class Kumanga : HttpSource() { - - override val client: OkHttpClient = network.cloudflareClient - .newBuilder() - .followRedirects(true) - .addInterceptor { chain -> - val originalRequest = chain.request() - if (originalRequest.url().toString().endsWith("token=")) { - getKumangaToken() - val url = originalRequest.url().toString() + kumangaToken - val newRequest = originalRequest.newBuilder().url(url).build() - chain.proceed(newRequest) - } else { - chain.proceed(originalRequest) - } - } - .build() - - override val name = "Kumanga" - - override val baseUrl = "https://www.kumanga.com" - - override val lang = "es" - - override val supportsLatest = false - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0") - - private val chapterImagesHeaders = headersBuilder() - .add("Referer", baseUrl) - .build() - - private var kumangaToken = "" - - private fun encodeAndReverse(dtValue: String): String { - return Base64.encodeToString(dtValue.toByteArray(), Base64.DEFAULT).reversed().trim() - } - - private fun decodeBase64(encodedString: String): String { - return Base64.decode(encodedString, Base64.DEFAULT).toString(charset("UTF-8")) - } - - private fun getKumangaToken(): String { - val body = client.newCall(GET("$baseUrl/mangalist?&page=1", headers)).execute().asJsoup() - var dt = body.select("#searchinput").attr("dt").toString() - var kumangaTokenKey = encodeAndReverse(encodeAndReverse(dt)).replace("=", "k").toLowerCase() - kumangaToken = body.select("div.input-group [type=hidden]").attr(kumangaTokenKey) - return kumangaToken - } - - private fun getMangaCover(mangaId: String) = "https://static.kumanga.com/manga_covers/$mangaId.jpg?w=201" - - private fun getMangaUrl(mangaId: String, mangaSlug: String, page: Int) = "/manga/$mangaId/p/$page/$mangaSlug#cl" - - private fun parseMangaFromJson(json: JsonElement) = SManga.create().apply { - title = json["name"].string - description = json["description"].string.replace("\\", "") - url = getMangaUrl(json["id"].string, json["slug"].string, 1) - thumbnail_url = getMangaCover(json["id"].string) - - val genresArray = json["categories"].array - genre = genresArray.joinToString { jsonObject -> - parseGenresFromJson(jsonObject) - } - } - - private fun parseJson(json: String): JsonElement { - return JsonParser().parse(json) - } - - private fun parseGenresFromJson(json: JsonElement) = json["name"].string - - override fun popularMangaRequest(page: Int): Request { - return POST("$baseUrl/backend/ajax/searchengine.php?page=$page&perPage=10&keywords=&retrieveCategories=true&retrieveAuthors=false&contentType=manga&token=$kumangaToken", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val res = response.body()!!.string() - val json = parseJson(res) - val data = json["contents"].array - val retrievedCount = json["retrievedCount"].int - val hasNextPage = retrievedCount == 10 - - val mangas = data.map { jsonObject -> - parseMangaFromJson(jsonObject) - } - - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") - - override fun latestUpdatesParse(response: Response) = throw Exception("Not Used") - - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val body = response.asJsoup() - - body.select("div#tab2").let { - status = parseStatus(it.select("span").text().orEmpty()) - author = it.select("p:nth-child(3) > a").text() - artist = it.select("p:nth-child(4) > a").text() - } - } - - private fun parseStatus(status: String) = when { - status.contains("Activo") -> SManga.ONGOING - status.contains("Finalizado") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun parseChapterDate(date: String): Long = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault()) - .parse(date)?.time ?: 0L - - private fun chapterSelector() = "div#accordion .title" - - private fun chapterFromElement(element: Element) = SChapter.create().apply { - element.select("a:has(i)").let { - setUrlWithoutDomain(it.attr("abs:href").replace("/c/", "/leer/")) - name = it.text() - date_upload = parseChapterDate(it.attr("title")) - } - scanlator = element.select("span.pull-right.greenSpan")?.text() - } - - override fun chapterListParse(response: Response): List<SChapter> = mutableListOf<SChapter>().apply { - var document = response.asJsoup() - val params = document.select("script:containsData(totCntnts)").toString() - - val numberChapters = params.substringAfter("totCntnts=").substringBefore(";").toIntOrNull() - val mangaId = params.substringAfter("mid=").substringBefore(";") - val mangaSlug = params.substringAfter("slg='").substringBefore("';") - - if (numberChapters != null) { - // Calculating total of pages, Kumanga shows 10 chapters per page, total_pages = #chapters / 10 - val numberOfPages = (numberChapters / 10.toDouble() + 0.4).roundToInt() - document.select(chapterSelector()).map { add(chapterFromElement(it)) } - var page = 2 - - while (page <= numberOfPages) { - document = client.newCall(GET(baseUrl + getMangaUrl(mangaId, mangaSlug, page))).execute().asJsoup() - document.select(chapterSelector()).map { add(chapterFromElement(it)) } - page++ - } - } else throw Exception("No fue posible obtener los capítulos") - } - - override fun pageListParse(response: Response): List<Page> = mutableListOf<Page>().apply { - val document = response.asJsoup() - var imagesJsonListStr = document.select("script:containsData(var pUrl=)").firstOrNull()?.data() - ?.substringAfter("var pUrl=") - ?.substringBefore(";") - ?: throw Exception("imagesJsonListStr null") - imagesJsonListStr = decodeBase64(decodeBase64(imagesJsonListStr).reversed().dropLast(10).drop(10)) - val imagesJsonList = parseJson(imagesJsonListStr).array - - imagesJsonList.forEach { - val fakeImageUrl = it["imgURL"].string.replace("\\", "") - val imageUrl = baseUrl + fakeImageUrl - - add(Page(size, "", imageUrl)) - } - } - - override fun imageRequest(page: Page) = GET(page.imageUrl!!, chapterImagesHeaders) - - override fun imageUrlParse(response: Response) = throw Exception("Not Used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/backend/ajax/searchengine.php?page=$page&perPage=10&keywords=$query&retrieveCategories=true&retrieveAuthors=false&contentType=manga&token=$kumangaToken")!!.newBuilder() - - filters.forEach { filter -> - when (filter) { - is TypeList -> { - filter.state - .filter { type -> type.state } - .forEach { type -> url.addQueryParameter("type_filter[]", type.id) } - } - is StatusList -> { - filter.state - .filter { status -> status.state } - .forEach { status -> url.addQueryParameter("status_filter[]", status.id) } - } - is GenreList -> { - filter.state - .filter { genre -> genre.state } - .forEach { genre -> url.addQueryParameter("category_filter[]", genre.id) } - } - } - } - - return POST(url.build().toString(), headers) - } - - override fun searchMangaParse(response: Response) = popularMangaParse(response) - - override fun getFilterList() = FilterList( - TypeList(getTypeList()), - Filter.Separator(), - StatusList(getStatusList()), - Filter.Separator(), - GenreList(getGenreList()) - ) - - private class Type(name: String, val id: String) : Filter.CheckBox(name) - private class TypeList(types: List<Type>) : Filter.Group<Type>("Filtrar por tipos", types) - - private class Status(name: String, val id: String) : Filter.CheckBox(name) - private class StatusList(status: List<Status>) : Filter.Group<Status>("Filtrar por estado", status) - - private class Genre(name: String, val id: String) : Filter.CheckBox(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres) - - private fun getTypeList() = listOf( - Type("Manga", "1"), - Type("Manhwa", "2"), - Type("Manhua", "3"), - Type("One shot", "4"), - Type("Doujinshi", "5") - ) - - private fun getStatusList() = listOf( - Status("Activo", "1"), - Status("Finalizado", "2"), - Status("Inconcluso", "3") - ) - - private fun getGenreList() = listOf( - Genre("Acción", "1"), - Genre("Artes marciales", "2"), - Genre("Automóviles", "3"), - Genre("Aventura", "4"), - Genre("Ciencia Ficción", "5"), - Genre("Comedia", "6"), - Genre("Demonios", "7"), - Genre("Deportes", "8"), - Genre("Doujinshi", "9"), - Genre("Drama", "10"), - Genre("Ecchi", "11"), - Genre("Espacio exterior", "12"), - Genre("Fantasía", "13"), - Genre("Gender bender", "14"), - Genre("Gore", "46"), - Genre("Harem", "15"), - Genre("Hentai", "16"), - Genre("Histórico", "17"), - Genre("Horror", "18"), - Genre("Josei", "19"), - Genre("Juegos", "20"), - Genre("Locura", "21"), - Genre("Magia", "22"), - Genre("Mecha", "23"), - Genre("Militar", "24"), - Genre("Misterio", "25"), - Genre("Música", "26"), - Genre("Niños", "27"), - Genre("Parodia", "28"), - Genre("Policía", "29"), - Genre("Psicológico", "30"), - Genre("Recuentos de la vida", "31"), - Genre("Romance", "32"), - Genre("Samurai", "33"), - Genre("Seinen", "34"), - Genre("Shoujo", "35"), - Genre("Shoujo Ai", "36"), - Genre("Shounen", "37"), - Genre("Shounen Ai", "38"), - Genre("Sobrenatural", "39"), - Genre("Súperpoderes", "41"), - Genre("Suspenso", "40"), - Genre("Terror", "47"), - Genre("Tragedia", "48"), - Genre("Vampiros", "42"), - Genre("Vida escolar", "43"), - Genre("Yaoi", "44"), - Genre("Yuri", "45") - ) -} diff --git a/src/es/lectormanga/AndroidManifest.xml b/src/es/lectormanga/AndroidManifest.xml deleted file mode 100644 index a19251e16..000000000 --- a/src/es/lectormanga/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".es.lectormanga.LectorMangaUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="lectormanga.com" - android:pathPattern="/gotobook/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/es/lectormanga/build.gradle b/src/es/lectormanga/build.gradle deleted file mode 100755 index daac6ae53..000000000 --- a/src/es/lectormanga/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'LectorManga' - pkgNameSuffix = 'es.lectormanga' - extClass = '.LectorManga' - extVersionCode = 16 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/lectormanga/res/mipmap-hdpi/ic_launcher.png b/src/es/lectormanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 282e40714..000000000 Binary files a/src/es/lectormanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/lectormanga/res/mipmap-mdpi/ic_launcher.png b/src/es/lectormanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index f2c1abc48..000000000 Binary files a/src/es/lectormanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/lectormanga/res/mipmap-xhdpi/ic_launcher.png b/src/es/lectormanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 8f646d3e7..000000000 Binary files a/src/es/lectormanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/lectormanga/res/mipmap-xxhdpi/ic_launcher.png b/src/es/lectormanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index f91cf54f8..000000000 Binary files a/src/es/lectormanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 69d25a3bd..000000000 Binary files a/src/es/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/lectormanga/res/web_hi_res_512.png b/src/es/lectormanga/res/web_hi_res_512.png deleted file mode 100644 index 85ab9a7e7..000000000 Binary files a/src/es/lectormanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt b/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt deleted file mode 100755 index d79fca478..000000000 --- a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt +++ /dev/null @@ -1,634 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.lectormanga - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale - -class LectorManga : ConfigurableSource, ParsedHttpSource() { - - override val name = "LectorManga" - - override val baseUrl = "https://lectormanga.com" - - override val lang = "es" - - override val supportsLatest = true - - private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + - "(KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" - - override fun headersBuilder(): Headers.Builder { - return Headers.Builder() - .add("User-Agent", userAgent) - .add("Referer", "$baseUrl/") - } - - private val imageCDNUrl = "https://img1.followmanga.com" - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private val webRateLimitInterceptor = SpecificHostRateLimitInterceptor( - HttpUrl.parse(baseUrl)!!, - preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60 - ) - - private val imageCDNRateLimitInterceptor = SpecificHostRateLimitInterceptor( - HttpUrl.parse(imageCDNUrl)!!, - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60 - ) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(webRateLimitInterceptor) - .addNetworkInterceptor(imageCDNRateLimitInterceptor) - .build() - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&type=&filter_by=title&page=$page", headers) - - override fun popularMangaNextPageSelector() = ".pagination .page-item:not(.disabled) a[rel='next']" - - override fun popularMangaSelector() = ".col-6 .card" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - title = element.select("a").text() - thumbnail_url = element.select("img").attr("src") - } - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&page=$page", headers) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/library")!!.newBuilder() - - url.addQueryParameter("title", query) - url.addQueryParameter("page", page.toString()) - - filters.forEach { filter -> - when (filter) { - is Types -> { - url.addQueryParameter("type", filter.toUriPart()) - } - is Demography -> { - url.addQueryParameter("demography", filter.toUriPart()) - } - is FilterBy -> { - url.addQueryParameter("filter_by", filter.toUriPart()) - } - is SortBy -> { - if (filter.state != null) { - url.addQueryParameter("order_item", SORTABLES[filter.state!!.index].second) - url.addQueryParameter( - "order_dir", - if (filter.state!!.ascending) { "asc" } else { "desc" } - ) - } - } - is WebcomicFilter -> { - url.addQueryParameter( - "webcomic", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - } - ) - } - is FourKomaFilter -> { - url.addQueryParameter( - "yonkoma", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - } - ) - } - is AmateurFilter -> { - url.addQueryParameter( - "amateur", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - } - ) - } - is EroticFilter -> { - url.addQueryParameter( - "erotic", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - } - ) - } - is GenreList -> { - filter.state - .filter { genre -> genre.state } - .forEach { genre -> url.addQueryParameter("genders[]", genre.id) } - } - } - } - - return GET(url.build().toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select("h1:has(small)").text() - genre = document.select("a.py-2").joinToString(", ") { - it.text() - } - description = document.select(".col-12.mt-2")?.text() - status = parseStatus(document.select(".status-publishing")?.text().orEmpty()) - thumbnail_url = document.select(".text-center img.img-fluid").attr("src") - } - - private fun parseStatus(status: String) = when { - status.contains("Publicándose") -> SManga.ONGOING - status.contains("Finalizado") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List<SChapter> = mutableListOf<SChapter>().apply { - val document = response.asJsoup() - - // One-shot - if (document.select("#chapters").isEmpty()) { - return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) } - } - - // Regular list of chapters - val chapterNames = document.select("#chapters h4.text-truncate") - val chapterNumbers = chapterNames.map { it.text().substringAfter("Capítulo").substringBefore("|").trim().toFloat() } - val chapterInfos = document.select("#chapters .chapter-list") - - chapterNames.forEachIndexed { index, _ -> - val scanlator = chapterInfos[index].select("li") - if (getScanlatorPref()) { - scanlator.forEach { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) } - } else { - scanlator.last { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) } - } - } - } - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - private fun oneShotChapterListSelector() = "div.chapter-list-element > ul.list-group li.list-group-item" - - private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply { - url = element.select("div.row > .text-right > a").attr("href") - name = "One Shot" - scanlator = element.select("div.col-md-6.text-truncate")?.text() - date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } - ?: 0 - } - - private fun regularChapterFromElement(chapterName: String, info: Element, number: Float) = SChapter.create().apply { - url = info.select("div.row > .text-right > a").attr("href") - name = chapterName - scanlator = info.select("div.col-md-6.text-truncate")?.text() - date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let { - parseChapterDate(it) - } ?: 0 - chapter_number = number - } - - private fun parseChapterDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) - .parse(date)?.time ?: 0 - } - - override fun pageListRequest(chapter: SChapter): Request { - val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri() - - // Get /cascade instead of /paginate to get all pages at once - val newUrl = if (getPageMethodPref() == "cascade" && currentUrl.contains("paginated")) { - currentUrl.substringBefore("paginated") + "cascade" - } else if (getPageMethodPref() == "paginated" && currentUrl.contains("cascade")) { - currentUrl.substringBefore("cascade") + "paginated" - } else currentUrl - - return GET(newUrl, headers) - } - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - if (getPageMethodPref() == "cascade") { - document.select("div.viewer-container img").forEach { - add( - Page( - size, - "", - it.let { - if (it.hasAttr("data-src")) - it.attr("abs:data-src") else it.attr("abs:src") - } - ) - ) - } - } else { - val pageList = document.select("#viewer-pages-select").first().select("option").map { it.attr("value").toInt() } - val url = document.baseUri().substringBefore("/paginated") // Accounts for url ending in number "/paginated/1" - pageList.forEach { - add(Page(it, "$url/paginated/$it")) - } - } - } - - // Note: At this moment (13/07/2020) it's necessary to make the image request without headers to prevent 403. - override fun imageRequest(page: Page) = GET(page.imageUrl!!) - - override fun imageUrlParse(document: Document): String = document.select("img.viewer-image").attr("src") - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/$MANGA_URL_CHUNK/$id", headers) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - - client.newCall(searchMangaByIdRequest(realQuery)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = "/$MANGA_URL_CHUNK/$realQuery" - MangasPage(listOf(details), false) - } - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - private class Types : UriPartFilter( - "Filtrar por tipo", - arrayOf( - Pair("Ver todos", ""), - Pair("Manga", "manga"), - Pair("Manhua", "manhua"), - Pair("Manhwa", "manhwa"), - Pair("Novela", "novel"), - Pair("One shot", "one_shot"), - Pair("Doujinshi", "doujinshi"), - Pair("Oel", "oel") - ) - ) - - private class Demography : UriPartFilter( - "Filtrar por demografía", - arrayOf( - Pair("Ver todas", ""), - Pair("Seinen", "seinen"), - Pair("Shoujo", "shoujo"), - Pair("Shounen", "shounen"), - Pair("Josei", "josei"), - Pair("Kodomo", "kodomo") - ) - ) - - private class FilterBy : UriPartFilter( - "Campo de orden", - arrayOf( - Pair("Título", "title"), - Pair("Autor", "author"), - Pair("Compañia", "company") - ) - ) - - class SortBy : Filter.Sort( - "Ordenar por", - SORTABLES.map { it.first }.toTypedArray(), - Selection(0, false) - ) - - private class WebcomicFilter : Filter.TriState("Webcomic") - - private class FourKomaFilter : Filter.TriState("Yonkoma") - - private class AmateurFilter : Filter.TriState("Amateur") - - private class EroticFilter : Filter.TriState("Erótico") - - private class Genre(name: String, val id: String) : Filter.CheckBox(name) - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres) - - override fun getFilterList() = FilterList( - Types(), - Demography(), - Filter.Separator(), - FilterBy(), - SortBy(), - Filter.Separator(), - WebcomicFilter(), - FourKomaFilter(), - AmateurFilter(), - EroticFilter(), - GenreList(getGenreList()) - ) - - // Array.from(document.querySelectorAll('#advancedSearch .custom-checkbox')) - // .map(a => `Genre("${a.querySelector('label').innerText}", "${a.querySelector('input').value}")`).join(',\n') - // on https://lectormanga.com/library - // Last revision 13/07/2020 - private fun getGenreList() = listOf( - Genre("Acción", "1"), - Genre("Aventura", "2"), - Genre("Comedia", "3"), - Genre("Drama", "4"), - Genre("Recuentos de la vida", "5"), - Genre("Ecchi", "6"), - Genre("Fantasia", "7"), - Genre("Magia", "8"), - Genre("Sobrenatural", "9"), - Genre("Horror", "10"), - Genre("Misterio", "11"), - Genre("Psicológico", "12"), - Genre("Romance", "13"), - Genre("Ciencia Ficción", "14"), - Genre("Thriller", "15"), - Genre("Deporte", "16"), - Genre("Girls Love", "17"), - Genre("Boys Love", "18"), - Genre("Harem", "19"), - Genre("Mecha", "20"), - Genre("Supervivencia", "21"), - Genre("Reencarnación", "22"), - Genre("Gore", "23"), - Genre("Apocalíptico", "24"), - Genre("Tragedia", "25"), - Genre("Vida Escolar", "26"), - Genre("Historia", "27"), - Genre("Militar", "28"), - Genre("Policiaco", "29"), - Genre("Crimen", "30"), - Genre("Superpoderes", "31"), - Genre("Vampiros", "32"), - Genre("Artes Marciales", "33"), - Genre("Samurái", "34"), - Genre("Género Bender", "35"), - Genre("Realidad Virtual", "36"), - Genre("Ciberpunk", "37"), - Genre("Musica", "38"), - Genre("Parodia", "39"), - Genre("Animación", "40"), - Genre("Demonios", "41"), - Genre("Familia", "42"), - Genre("Extranjero", "43"), - Genre("Niños", "44"), - Genre("Realidad", "45"), - Genre("Telenovela", "46"), - Genre("Guerra", "47"), - Genre("Oeste", "48") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - - val scanlatorPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SCANLATOR_PREF - title = SCANLATOR_PREF_TITLE - summary = SCANLATOR_PREF_SUMMARY - setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit() - } - } - - val pageMethodPref = androidx.preference.ListPreference(screen.context).apply { - key = PAGE_METHOD_PREF - title = PAGE_METHOD_PREF_TITLE - entries = arrayOf("Cascada", "Páginado") - entryValues = arrayOf("cascade", "paginated") - summary = PAGE_METHOD_PREF_SUMMARY - setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - // Rate limit - val apiRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = WEB_RATELIMIT_PREF - title = WEB_RATELIMIT_PREF_TITLE - summary = WEB_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(scanlatorPref) - screen.addPreference(pageMethodPref) - screen.addPreference(apiRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - - val scanlatorPref = CheckBoxPreference(screen.context).apply { - key = SCANLATOR_PREF - title = SCANLATOR_PREF_TITLE - summary = SCANLATOR_PREF_SUMMARY - setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit() - } - } - - val pageMethodPref = ListPreference(screen.context).apply { - key = PAGE_METHOD_PREF - title = PAGE_METHOD_PREF_TITLE - entries = arrayOf("Cascada", "Páginado") - entryValues = arrayOf("cascade", "paginated") - summary = PAGE_METHOD_PREF_SUMMARY - setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - // Rate limit - val apiRateLimitPreference = ListPreference(screen.context).apply { - key = WEB_RATELIMIT_PREF - title = WEB_RATELIMIT_PREF_TITLE - summary = WEB_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(scanlatorPref) - screen.addPreference(pageMethodPref) - screen.addPreference(apiRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - } - - private fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE) - - private fun getPageMethodPref() = preferences.getString(PAGE_METHOD_PREF, PAGE_METHOD_PREF_DEFAULT_VALUE) - - companion object { - private const val SCANLATOR_PREF = "scanlatorPref" - private const val SCANLATOR_PREF_TITLE = "Mostrar todos los scanlator" - private const val SCANLATOR_PREF_SUMMARY = "Se mostraran capítulos repetidos pero con diferentes Scanlators" - private const val SCANLATOR_PREF_DEFAULT_VALUE = true - - private const val PAGE_METHOD_PREF = "pageMethodPref" - private const val PAGE_METHOD_PREF_TITLE = "Método para descargar imágenes" - private const val PAGE_METHOD_PREF_SUMMARY = "Previene ser banneado por el servidor cuando se usa la configuración \"Cascada\" ya que esta reduce la cantidad de solicitudes.\nPuedes usar \"Páginado\" cuando las imágenes no carguen usando \"Cascada\".\nConfiguración actual: %s" - private const val PAGE_METHOD_PREF_DEFAULT_VALUE = "cascade" - - private const val WEB_RATELIMIT_PREF = "webRatelimitPreference" - // Ratelimit permits per second for main website - private const val WEB_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para el sitio web" - // This value affects network request amount to TMO url. Lower this value may reduce the chance to get HTTP 429 error, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s - private const val WEB_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red a la URL de TMO. Reducir este valor puede disminuir la posibilidad de obtener un error HTTP 429, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s" - private const val WEB_RATELIMIT_PREF_DEFAULT_VALUE = "10" - - private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference" - // Ratelimit permits per second for image CDN - private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para descarga de imágenes" - // This value affects network request amount for loading image. Lower this value may reduce the chance to get error when loading image, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s - private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red para descargar imágenes. Reducir este valor puede disminuir errores al cargar imagenes, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s" - private const val IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE = "10" - - private val ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray() - - const val PREFIX_ID_SEARCH = "id:" - const val MANGA_URL_CHUNK = "gotobook" - - private val SORTABLES = listOf( - Pair("Me gusta", "likes_count"), - Pair("Alfabético", "alphabetically"), - Pair("Puntuación", "score"), - Pair("Creación", "creation"), - Pair("Fecha estreno", "release_date") - ) - } -} diff --git a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorMangaUrlActivity.kt b/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorMangaUrlActivity.kt deleted file mode 100644 index 3cad95e69..000000000 --- a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorMangaUrlActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.lectormanga - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://lectormanga.com/gotobook/:id intents and redirects them to - * the main Tachiyomi process. - */ -class LectorMangaUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${LectorManga.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("LectorMangaUrlActivity", e.toString()) - } - } else { - Log.e("LectorMangaUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/es/leermangasxyz/build.gradle b/src/es/leermangasxyz/build.gradle deleted file mode 100644 index a55229356..000000000 --- a/src/es/leermangasxyz/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'LeerMangasXYZ' - pkgNameSuffix = 'es.leermangasxyz' - extClass = '.LeerMangasXYZ' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = false -} - -apply from: "$rootDir/common.gradle" \ No newline at end of file diff --git a/src/es/leermangasxyz/res/mipmap-hdpi/ic_launcher.png b/src/es/leermangasxyz/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 723fc01af..000000000 Binary files a/src/es/leermangasxyz/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/leermangasxyz/res/mipmap-mdpi/ic_launcher.png b/src/es/leermangasxyz/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 72b095790..000000000 Binary files a/src/es/leermangasxyz/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/leermangasxyz/res/mipmap-xhdpi/ic_launcher.png b/src/es/leermangasxyz/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5b36f39ca..000000000 Binary files a/src/es/leermangasxyz/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/leermangasxyz/res/mipmap-xxhdpi/ic_launcher.png b/src/es/leermangasxyz/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 62fb84765..000000000 Binary files a/src/es/leermangasxyz/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/leermangasxyz/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/leermangasxyz/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1e4d64ec7..000000000 Binary files a/src/es/leermangasxyz/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/leermangasxyz/src/eu/kanade/tachiyomi/extension/es/leermangasxyz/LeerMangasXYZ.kt b/src/es/leermangasxyz/src/eu/kanade/tachiyomi/extension/es/leermangasxyz/LeerMangasXYZ.kt deleted file mode 100644 index b0b6bba94..000000000 --- a/src/es/leermangasxyz/src/eu/kanade/tachiyomi/extension/es/leermangasxyz/LeerMangasXYZ.kt +++ /dev/null @@ -1,123 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.leermangasxyz - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.lang.RuntimeException -import java.net.URLEncoder - -open class LeerMangasXYZ : ParsedHttpSource() { - - override val baseUrl: String = "https://r1.leermanga.xyz" - - override val lang: String = "es" - - override val name: String = "LeerManga.xyz" - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used") - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override val supportsLatest: Boolean = false - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val row = element.select("td") - with(row[0]) { - chapter_number = text().toFloat() - date_upload = 0 - } - with(row[1]) { - name = text() - url = selectFirst("a").attr("href") - } - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = super.fetchChapterList(manga).map { - it.reversed() - } - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - setUrlWithoutDomain(document.baseUri()) - val rawStatus = document.selectFirst("td:contains(Status)").text() - println(" Status: $rawStatus") - status = getStatus(rawStatus.substringAfter("Status: ")) - - author = document.select("li[itemprop=author]")?.joinToString(separator = ", ") { it.text() } - - thumbnail_url = document.selectFirst("img.img-thumbnail").attr("abs:src") - - description = document.selectFirst("p[itemprop=description]").text() - - genre = document.select("span[itemprop=genre]")?.joinToString(", ") { it.text() } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = document.select(pageListSelector()).map { - Page( - imageUrl = it.attr("href"), - index = it.attr("data-ngdesc").substringAfter("Page ").toInt() - ) - } - if (pages.isNullOrEmpty()) - throw RuntimeException("Cannot fetch images from source") - return pages - } - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.selectFirst("img.card-img-top").attr("abs:src") - element.selectFirst("div.card-body").let { - val dc = it.selectFirst("h5.card-title a") - url = dc.attr("href") - title = dc.text() - } - } - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - with(element) { - thumbnail_url = selectFirst("img").attr("abs:src") - title = selectFirst("span[itemprop=name]").text() - url = selectFirst("div.col-4 a").attr("href") - } - } - - fun encodeString(str: String): String = URLEncoder.encode(str, "utf-8") - - private fun getStatus(str: String): Int = when (str) { - "Emitiéndose", "Ongoing", "En emisión" -> SManga.ONGOING - "Finalizado" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - // ========------- [[< Request >]]] =========-------- - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/search?query=${encodeString(query)}&page=$page") - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - // ------ ======== [[[ SELECTORS ]]] ======== ------- - - private fun pageListSelector() = "div[data-nanogallery2] a" - - override fun searchMangaSelector(): String = "div[itemtype*=ComicSeries]" - - override fun searchMangaNextPageSelector(): String = "CHANGE THIS" - - override fun popularMangaSelector(): String = "div.card-group div.card" - - override fun popularMangaNextPageSelector(): String = "CHANGE THIS" - - override fun chapterListSelector(): String = "table#chaptersTable tbody tr" -} - -fun Element.selectFirst(cssSelector: String) = this.select(cssSelector).first() diff --git a/src/es/mangamx/AndroidManifest.xml b/src/es/mangamx/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/es/mangamx/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/mangamx/build.gradle b/src/es/mangamx/build.gradle deleted file mode 100644 index d73ae5f31..000000000 --- a/src/es/mangamx/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaMx' - pkgNameSuffix = 'es.mangamx' - extClass = '.MangaMx' - extVersionCode = 10 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/mangamx/res/mipmap-hdpi/ic_launcher.png b/src/es/mangamx/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0e382f8ab..000000000 Binary files a/src/es/mangamx/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangamx/res/mipmap-mdpi/ic_launcher.png b/src/es/mangamx/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index bb570088f..000000000 Binary files a/src/es/mangamx/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangamx/res/mipmap-xhdpi/ic_launcher.png b/src/es/mangamx/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 814428b91..000000000 Binary files a/src/es/mangamx/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangamx/res/mipmap-xxhdpi/ic_launcher.png b/src/es/mangamx/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a63f718f5..000000000 Binary files a/src/es/mangamx/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangamx/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/mangamx/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3d6c88f7c..000000000 Binary files a/src/es/mangamx/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/mangamx/res/web_hi_res_512.png b/src/es/mangamx/res/web_hi_res_512.png deleted file mode 100644 index 3ef98e636..000000000 Binary files a/src/es/mangamx/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt deleted file mode 100644 index 2532f6ac4..000000000 --- a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt +++ /dev/null @@ -1,358 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.mangamx - -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.PreferenceScreen -import android.util.Base64 -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.nio.charset.Charset -import java.text.SimpleDateFormat -import java.util.Locale - -open class MangaMx : ConfigurableSource, ParsedHttpSource() { - - override val name = "MangaMx" - - override val baseUrl = "https://manga-mx.com" - - override val lang = "es" - - override val supportsLatest = true - - override val client = network.cloudflareClient - - private var csrfToken = "" - - override fun popularMangaRequest(page: Int) = GET( - "$baseUrl/directorio?genero=false" + - "&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page", - headers - ) - - override fun popularMangaNextPageSelector() = ".page-item a[rel=next]" - - override fun popularMangaSelector() = "#article-div a" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.select("img").attr("data-src") - title = element.select("div:eq(1)").text().trim() - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - csrfToken = document.select("meta[name=csrf-token]").attr("content") - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - - val hasNextPage = popularMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/recientes?p=$page", headers) - - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - - override fun latestUpdatesSelector() = "div._1bJU3" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img").attr("data-src") - element.select("div a").apply { - title = this.text().trim() - setUrlWithoutDomain(this.attr("href")) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotBlank()) { - val formBody = FormBody.Builder() - .add("buscar", query) - .add("_token", csrfToken) - .build() - val searchHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest") - .add("Referer", baseUrl).build() - return POST("$baseUrl/buscar", searchHeaders, formBody) - } else { - val uri = Uri.parse("$baseUrl/directorio").buildUpon() - - uri.appendQueryParameter( - "adulto", - if (hideNSFWContent()) { "0" } else { "1" } - ) - - for (filter in filters) { - when (filter) { - is StatusFilter -> uri.appendQueryParameter( - filter.name.toLowerCase(Locale.ROOT), - statusArray[filter.state].second - ) - is SortBy -> { - uri.appendQueryParameter("filtro", sortables[filter.state!!.index].second) - uri.appendQueryParameter( - "orden", - if (filter.state!!.ascending) { "asc" } else { "desc" } - ) - } - is TypeFilter -> uri.appendQueryParameter( - filter.name.toLowerCase(Locale.ROOT), - typedArray[filter.state].second - ) - is GenreFilter -> uri.appendQueryParameter( - "genero", - genresArray[filter.state].second - ) - } - } - uri.appendQueryParameter("p", page.toString()) - return GET(uri.toString(), headers) - } - } - - override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() - - override fun searchMangaSelector(): String = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaParse(response: Response): MangasPage { - if (!response.isSuccessful) throw Exception("Búsqueda fallida ${response.code()}") - if ("directorio" in response.request().url().toString()) { - val document = response.asJsoup() - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } else { - val body = response.body()!!.string() - if (body == "[]") throw Exception("Término de búsqueda demasiado corto") - val json = JsonParser().parse(body)["mangas"].asJsonArray - - val mangas = json.map { jsonElement -> searchMangaFromJson(jsonElement) } - val hasNextPage = false - return MangasPage(mangas, hasNextPage) - } - } - - private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply { - title = jsonElement["nombre"].string - setUrlWithoutDomain(jsonElement["url"].string) - thumbnail_url = jsonElement["img"].string.replace("/thumb", "/cover") - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = document.select("img[src*=cover]").attr("abs:src") - manga.description = document.select("div#sinopsis").last().ownText() - manga.author = document.select("div#info-i").text().let { - if (it.contains("Autor", true)) { - it.substringAfter("Autor:").substringBefore("Fecha:").trim() - } else "N/A" - } - manga.artist = manga.author - manga.genre = document.select("div#categ a").joinToString(", ") { it.text() } - manga.status = when (document.select("span#desarrollo")?.first()?.text()) { - "En desarrollo" -> SManga.ONGOING - // "Completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - return manga - } - - override fun chapterListSelector(): String = "div#c_list a" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.text().trim() - setUrlWithoutDomain(element.attr("href")) - chapter_number = element.select("span").attr("data-num").toFloat() - date_upload = parseDate(element.select("span").attr("datetime")) - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0 - } - - override fun pageListParse(document: Document): List<Page> { - val encoded = document.select("script:containsData(unicap)").firstOrNull() - ?.data()?.substringAfter("'")?.substringBefore("'")?.reversed() - ?: throw Exception("unicap not found") - val drop = encoded.length % 4 - val decoded = Base64.decode(encoded.dropLast(drop), Base64.DEFAULT).toString(Charset.defaultCharset()) - val path = decoded.substringBefore("||") - return decoded.substringAfter("[").substringBefore("]").split(",").mapIndexed { i, file -> - Page(i, "", path + file.removeSurrounding("\"")) - } - } - - override fun imageUrlParse(document: Document) = throw Exception("Not Used") - - override fun getFilterList() = FilterList( - Filter.Header("NOTA: Se ignoran si se usa el buscador"), - Filter.Separator(), - SortBy("Ordenar por", sortables), - StatusFilter("Estado", statusArray), - TypeFilter("Tipo", typedArray), - GenreFilter("Géneros", genresArray) - ) - - private class StatusFilter(name: String, values: Array<Pair<String, String>>) : - Filter.Select<String>(name, values.map { it.first }.toTypedArray()) - - private class TypeFilter(name: String, values: Array<Pair<String, String>>) : - Filter.Select<String>(name, values.map { it.first }.toTypedArray()) - - private class GenreFilter(name: String, values: Array<Pair<String, String>>) : - Filter.Select<String>(name, values.map { it.first }.toTypedArray()) - - class SortBy(name: String, values: Array<Pair<String, String>>) : Filter.Sort( - name, values.map { it.first }.toTypedArray(), - Selection(0, false) - ) - - private val statusArray = arrayOf( - Pair("Estado", "false"), - Pair("En desarrollo", "1"), - Pair("Completo", "0") - ) - - private val typedArray = arrayOf( - Pair("Todo", "false"), - Pair("Mangas", "0"), - Pair("Manhwas", "1"), - Pair("One Shot", "2"), - Pair("Manhuas", "3"), - Pair("Novelas", "4") - ) - - private val sortables = arrayOf( - Pair("Visitas", "visitas"), - Pair("Recientes", "id"), - Pair("Alfabético", "nombre"), - ) - - /** - * Url: https://manga-mx.com/directorio/ - * Last check: 27/03/2021 - * JS script: Array.from(document.querySelectorAll('select[name="genero"] option')) - * .map(a => `Pair("${a.innerText}", "${a.value}")`).join(',\n') - */ - private val genresArray = arrayOf( - Pair("Todos", "false"), - Pair("Comedia", "1"), - Pair("Drama", "2"), - Pair("Acción", "3"), - Pair("Escolar", "4"), - Pair("Romance", "5"), - Pair("Ecchi", "6"), - Pair("Aventura", "7"), - Pair("Shōnen", "8"), - Pair("Shōjo", "9"), - Pair("Deportes", "10"), - Pair("Psicológico", "11"), - Pair("Fantasía", "12"), - Pair("Mecha", "13"), - Pair("Gore", "14"), - Pair("Yaoi", "15"), - Pair("Yuri", "16"), - Pair("Misterio", "17"), - Pair("Sobrenatural", "18"), - Pair("Seinen", "19"), - Pair("Ficción", "20"), - Pair("Harem", "21"), - Pair("Webtoon", "25"), - Pair("Histórico", "27"), - Pair("Músical", "30"), - Pair("Ciencia ficción", "31"), - Pair("Shōjo-ai", "32"), - Pair("Josei", "33"), - Pair("Magia", "34"), - Pair("Artes Marciales", "35"), - Pair("Horror", "36"), - Pair("Demonios", "37"), - Pair("Supervivencia", "38"), - Pair("Recuentos de la vida", "39"), - Pair("Shōnen ai", "40"), - Pair("Militar", "41"), - Pair("Eroge", "42"), - Pair("Isekai", "43") - ) - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - - val contentPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = CONTENT_PREF - title = CONTENT_PREF_TITLE - summary = CONTENT_PREF_SUMMARY - setDefaultValue(CONTENT_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(CONTENT_PREF, checkValue).commit() - } - } - - screen.addPreference(contentPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - - val contentPref = CheckBoxPreference(screen.context).apply { - key = CONTENT_PREF - title = CONTENT_PREF_TITLE - summary = CONTENT_PREF_SUMMARY - setDefaultValue(CONTENT_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(CONTENT_PREF, checkValue).commit() - } - } - - screen.addPreference(contentPref) - } - - private fun hideNSFWContent(): Boolean = preferences.getBoolean(CONTENT_PREF, CONTENT_PREF_DEFAULT_VALUE) - - companion object { - private const val CONTENT_PREF = "showNSFWContent" - private const val CONTENT_PREF_TITLE = "Ocultar contenido +18" - private const val CONTENT_PREF_SUMMARY = "Ocultar el contenido erótico en explorar y buscar, no funciona en los mangas recientes." - private const val CONTENT_PREF_DEFAULT_VALUE = false - } -} diff --git a/src/es/tmohentai/AndroidManifest.xml b/src/es/tmohentai/AndroidManifest.xml deleted file mode 100644 index 47fe58746..000000000 --- a/src/es/tmohentai/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".es.tmohentai.TMOHentaiUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="tmohentai.com" - android:pathPattern="/contents/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/es/tmohentai/build.gradle b/src/es/tmohentai/build.gradle deleted file mode 100755 index 246a4e0d6..000000000 --- a/src/es/tmohentai/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'TMOHentai' - pkgNameSuffix = 'es.tmohentai' - extClass = '.TMOHentai' - extVersionCode = 5 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/tmohentai/res/mipmap-hdpi/ic_launcher.png b/src/es/tmohentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 719dfaf4f..000000000 Binary files a/src/es/tmohentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tmohentai/res/mipmap-mdpi/ic_launcher.png b/src/es/tmohentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1ee53e328..000000000 Binary files a/src/es/tmohentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tmohentai/res/mipmap-xhdpi/ic_launcher.png b/src/es/tmohentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 071e1cb0d..000000000 Binary files a/src/es/tmohentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tmohentai/res/mipmap-xxhdpi/ic_launcher.png b/src/es/tmohentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 04bd91798..000000000 Binary files a/src/es/tmohentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tmohentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/tmohentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9830ae8ed..000000000 Binary files a/src/es/tmohentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tmohentai/res/web_hi_res_512.png b/src/es/tmohentai/res/web_hi_res_512.png deleted file mode 100644 index b32899a92..000000000 Binary files a/src/es/tmohentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentai.kt b/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentai.kt deleted file mode 100755 index 4818d7ea2..000000000 --- a/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentai.kt +++ /dev/null @@ -1,356 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.tmohentai - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -@Nsfw -class TMOHentai : ConfigurableSource, ParsedHttpSource() { - - override val name = "TMOHentai" - - override val baseUrl = "https://tmohentai.com" - - override val lang = "es" - - override val supportsLatest = true - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/section/all?view=list&page=$page&order=popularity&order-dir=desc&search[searchText]=&search[searchBy]=name&type=all", headers) - - override fun popularMangaSelector() = "table > tbody > tr[data-toggle=popover]" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.select("tr").let { - title = it.attr("data-title") - thumbnail_url = it.attr("data-content").substringAfter("src=\"").substringBeforeLast("\"") - setUrlWithoutDomain(it.select("td.text-left > a").attr("href")) - } - } - - override fun popularMangaNextPageSelector() = "a[rel=next]" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/section/all?view=list&page=$page&order=publication_date&order-dir=desc&search[searchText]=&search[searchBy]=name&type=all", headers) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val parsedInformation = document.select("div.row > div.panel.panel-primary").text() - val authorAndArtist = parsedInformation.substringAfter("Groups").substringBefore("Magazines").trim() - - title = document.select("h3.truncate").text() - thumbnail_url = document.select("img.content-thumbnail-cover").attr("src") - author = authorAndArtist - artist = authorAndArtist - description = "Sin descripción" - status = SManga.UNKNOWN - genre = parsedInformation.substringAfter("Genders").substringBefore("Tags").trim().split(" ").joinToString { - it - } - } - - override fun chapterListSelector() = "div#app > div.container" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val parsedInformation = element.select("div.row > div.panel.panel-primary").text() - - name = element.select("h3.truncate").text() - scanlator = parsedInformation.substringAfter("By").substringBefore("Language").trim() - - var currentUrl = element.select("a.pull-right.btn.btn-primary").attr("href") - - if (currentUrl.contains("/1")) { - currentUrl = currentUrl.substringBeforeLast("/") - } - - setUrlWithoutDomain(currentUrl) - // date_upload = no date in the web - } - - // "/cascade" to get all images - override fun pageListRequest(chapter: SChapter): Request { - val currentUrl = chapter.url - val newUrl = if (getPageMethodPref() == "cascade" && currentUrl.contains("paginated")) { - currentUrl.substringBefore("paginated") + "cascade" - } else if (getPageMethodPref() == "paginated" && currentUrl.contains("cascade")) { - currentUrl.substringBefore("cascade") + "paginated" - } else currentUrl - - return GET("$baseUrl$newUrl", headers) - } - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - if (getPageMethodPref() == "cascade") { - document.select("div#content-images img.content-image")?.forEach { - add(Page(size, "", it.attr("data-original"))) - } - } else { - val pageList = document.select("select#select-page").first().select("option").map { it.attr("value").toInt() } - val url = document.baseUri() - - pageList.forEach { - add(Page(it, "$url/$it")) - } - } - } - - override fun imageUrlParse(document: Document) = document.select("div#content-images img.content-image").attr("data-original") - - override fun imageRequest(page: Page) = GET("$baseUrl${page.imageUrl!!}", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/section/all?view=list")!!.newBuilder() - - url.addQueryParameter("search[searchText]", query) - url.addQueryParameter("page", page.toString()) - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Types -> { - url.addQueryParameter("type", filter.toUriPart()) - } - is GenreList -> { - filter.state - .filter { genre -> genre.state } - .forEach { genre -> url.addQueryParameter("genders[]", genre.id) } - } - is FilterBy -> { - url.addQueryParameter("search[searchBy]", filter.toUriPart()) - } - is SortBy -> { - if (filter.state != null) { - url.addQueryParameter("order", SORTABLES[filter.state!!.index].second) - url.addQueryParameter( - "order-dir", - if (filter.state!!.ascending) { "asc" } else { "desc" } - ) - } - } - } - } - - return GET(url.build().toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/$PREFIX_CONTENTS/$id", headers) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - - client.newCall(searchMangaByIdRequest(realQuery)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = "/$PREFIX_CONTENTS/$realQuery" - MangasPage(listOf(details), false) - } - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - private class Genre(name: String, val id: String) : Filter.CheckBox(name) - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Géneros", genres) - - override fun getFilterList() = FilterList( - Types(), - Filter.Separator(), - FilterBy(), - SortBy(), - Filter.Separator(), - GenreList(getGenreList()) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private class Types : UriPartFilter( - "Filtrar por tipo", - arrayOf( - Pair("Ver todos", "all"), - Pair("Manga", "hentai"), - Pair("Light Hentai", "light-hentai"), - Pair("Doujinshi", "doujinshi"), - Pair("One-shot", "one-shot"), - Pair("Other", "otro") - ) - ) - - private class FilterBy : UriPartFilter( - "Campo de orden", - arrayOf( - Pair("Nombre", "name"), - Pair("Artista", "artist"), - Pair("Revista", "magazine"), - Pair("Tag", "tag") - ) - ) - - class SortBy : Filter.Sort( - "Ordenar por", - SORTABLES.map { it.first }.toTypedArray(), - Selection(2, false) - ) - - /** - * Last check: 17/02/2021 - * https://tmohentai.com/section/hentai - * - * Array.from(document.querySelectorAll('#advancedSearch .list-group .list-group-item')) - * .map(a => `Genre("${a.querySelector('span').innerText.replace(' ', '')}", "${a.querySelector('input').value}")`).join(',\n') - */ - private fun getGenreList() = listOf( - Genre("Romance", "1"), - Genre("Fantasy", "2"), - Genre("Comedy", "3"), - Genre("Parody", "4"), - Genre("Student", "5"), - Genre("Adventure", "6"), - Genre("Milf", "7"), - Genre("Orgy", "8"), - Genre("Big Breasts", "9"), - Genre("Bondage", "10"), - Genre("Tentacles", "11"), - Genre("Incest", "12"), - Genre("Ahegao", "13"), - Genre("Bestiality", "14"), - Genre("Futanari", "15"), - Genre("Rape", "16"), - Genre("Monsters", "17"), - Genre("Pregnant", "18"), - Genre("Small Breast", "19"), - Genre("Bukkake", "20"), - Genre("Femdom", "21"), - Genre("Fetish", "22"), - Genre("Forced", "23"), - Genre("3D", "24"), - Genre("Furry", "25"), - Genre("Adultery", "26"), - Genre("Anal", "27"), - Genre("FootJob", "28"), - Genre("BlowJob", "29"), - Genre("Toys", "30"), - Genre("Vanilla", "31"), - Genre("Colour", "32"), - Genre("Uncensored", "33"), - Genre("Netorare", "34"), - Genre("Virgin", "35"), - Genre("Cheating", "36"), - Genre("Harem", "37"), - Genre("Horror", "38"), - Genre("Lolicon", "39"), - Genre("Mature", "40"), - Genre("Nympho", "41"), - Genre("Public Sex", "42"), - Genre("Sport", "43"), - Genre("Domination", "44"), - Genre("Tsundere", "45"), - Genre("Yandere", "46") - ) - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val pageMethodPref = androidx.preference.ListPreference(screen.context).apply { - key = PAGE_METHOD_PREF - title = PAGE_METHOD_PREF_TITLE - entries = arrayOf("Cascada", "Páginado") - entryValues = arrayOf("cascade", "paginated") - summary = PAGE_METHOD_PREF_SUMMARY - setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(pageMethodPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val pageMethodPref = ListPreference(screen.context).apply { - key = PAGE_METHOD_PREF - title = PAGE_METHOD_PREF_TITLE - entries = arrayOf("Cascada", "Páginado") - entryValues = arrayOf(PAGE_METHOD_PREF_CASCADE, PAGE_METHOD_PREF_PAGINATED) - summary = PAGE_METHOD_PREF_SUMMARY - setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(pageMethodPref) - } - - private fun getPageMethodPref() = preferences.getString(PAGE_METHOD_PREF, PAGE_METHOD_PREF_DEFAULT_VALUE) - - companion object { - private const val PAGE_METHOD_PREF = "pageMethodPref" - private const val PAGE_METHOD_PREF_TITLE = "Método de descarga de imágenes" - private const val PAGE_METHOD_PREF_SUMMARY = "Puede corregir errores al cargar las imágenes.\nConfiguración actual: %s" - private const val PAGE_METHOD_PREF_CASCADE = "cascade" - private const val PAGE_METHOD_PREF_PAGINATED = "paginated" - private const val PAGE_METHOD_PREF_DEFAULT_VALUE = PAGE_METHOD_PREF_CASCADE - - const val PREFIX_CONTENTS = "contents" - const val PREFIX_ID_SEARCH = "id:" - - private val SORTABLES = listOf( - Pair("Alfabético", "alphabetic"), - Pair("Creación", "publication_date"), - Pair("Popularidad", "popularity") - ) - } -} diff --git a/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentaiUrlActivity.kt b/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentaiUrlActivity.kt deleted file mode 100644 index a1488f8a7..000000000 --- a/src/es/tmohentai/src/eu/kanade/tachiyomi/extension/es/tmohentai/TMOHentaiUrlActivity.kt +++ /dev/null @@ -1,40 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.tmohentai - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://tmohentai.com/contents/:id intents and redirects them to - * the main Tachiyomi process. - */ -class TMOHentaiUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${TMOHentai.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("TMOHentaiUrlActivity", e.toString()) - } - } else { - Log.e("TMOHentaiUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/es/tumangaonline/AndroidManifest.xml b/src/es/tumangaonline/AndroidManifest.xml deleted file mode 100644 index 8706ffc60..000000000 --- a/src/es/tumangaonline/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".es.tumangaonline.TuMangaOnlineUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="lectortmo.com" - android:pathPattern="/library/..*/..*/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/es/tumangaonline/build.gradle b/src/es/tumangaonline/build.gradle deleted file mode 100644 index 3edb99872..000000000 --- a/src/es/tumangaonline/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'TuMangaOnline' - pkgNameSuffix = 'es.tumangaonline' - extClass = '.TuMangaOnline' - extVersionCode = 33 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/tumangaonline/res/mipmap-hdpi/ic_launcher.png b/src/es/tumangaonline/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9b90ad753..000000000 Binary files a/src/es/tumangaonline/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tumangaonline/res/mipmap-mdpi/ic_launcher.png b/src/es/tumangaonline/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 46b23e86c..000000000 Binary files a/src/es/tumangaonline/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tumangaonline/res/mipmap-xhdpi/ic_launcher.png b/src/es/tumangaonline/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bc084f08f..000000000 Binary files a/src/es/tumangaonline/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png b/src/es/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 04fc3ae88..000000000 Binary files a/src/es/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d77b493d7..000000000 Binary files a/src/es/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt b/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt deleted file mode 100644 index cb9b95da6..000000000 --- a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt +++ /dev/null @@ -1,669 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.tumangaonline - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale - -class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { - - override val name = "TuMangaOnline" - - override val baseUrl = "https://lectortmo.com" - - override val lang = "es" - - override val supportsLatest = true - - private val imageCDNUrl = "https://img1.japanreader.com" - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private val webRateLimitInterceptor = SpecificHostRateLimitInterceptor( - HttpUrl.parse(baseUrl)!!, - preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60 - ) - - private val imageCDNRateLimitInterceptor = SpecificHostRateLimitInterceptor( - HttpUrl.parse(imageCDNUrl)!!, - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60 - ) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(webRateLimitInterceptor) - .addNetworkInterceptor(imageCDNRateLimitInterceptor) - .build() - - // Marks erotic content as false and excludes: Ecchi(6), GirlsLove(17), BoysLove(18) and Harem(19) genders - private val getSFWUrlPart = if (getSFWModePref()) "&exclude_genders%5B%5D=6&exclude_genders%5B%5D=17&exclude_genders%5B%5D=18&exclude_genders%5B%5D=19&erotic=false" else "" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title$getSFWUrlPart&_page=1&page=$page", headers) - - override fun popularMangaNextPageSelector() = "a.page-link" - - override fun popularMangaSelector() = "div.element" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.select("div.element > a").let { - setUrlWithoutDomain(it.attr("href").substringAfter(" ")) - title = it.select("h4.text-truncate").text() - thumbnail_url = it.select("style").toString().substringAfter("('").substringBeforeLast("')") - } - } - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&filter_by=title$getSFWUrlPart&_page=1&page=$page", headers) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/library")!!.newBuilder() - url.addQueryParameter("title", query) - if (getSFWModePref()) { - SFW_MODE_PREF_EXCLUDE_GENDERS.forEach { gender -> - url.addQueryParameter("exclude_genders[]", gender) - } - url.addQueryParameter("erotic", "false") - } - url.addQueryParameter("page", page.toString()) - url.addQueryParameter("_page", "1") // Extra Query to Prevent Scrapping aka without it = 403 - filters.forEach { filter -> - when (filter) { - is Types -> { - url.addQueryParameter("type", filter.toUriPart()) - } - is Demography -> { - url.addQueryParameter("demography", filter.toUriPart()) - } - is Status -> { - url.addQueryParameter("status", filter.toUriPart()) - } - is TranslationStatus -> { - url.addQueryParameter("translation_status", filter.toUriPart()) - } - is FilterBy -> { - url.addQueryParameter("filter_by", filter.toUriPart()) - } - is SortBy -> { - if (filter.state != null) { - url.addQueryParameter("order_item", SORTABLES[filter.state!!.index].second) - url.addQueryParameter( - "order_dir", - if (filter.state!!.ascending) { "asc" } else { "desc" } - ) - } - } - is ContentTypeList -> { - filter.state.forEach { content -> - // If (SFW mode is not enabled) OR (SFW mode is enabled AND filter != erotic) -> Apply filter - // else -> ignore filter - if (!getSFWModePref() || (getSFWModePref() && content.id != "erotic")) { - when (content.state) { - Filter.TriState.STATE_IGNORE -> url.addQueryParameter(content.id, "") - Filter.TriState.STATE_INCLUDE -> url.addQueryParameter(content.id, "true") - Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter(content.id, "false") - } - } - } - } - is GenreList -> { - filter.state.forEach { genre -> - when (genre.state) { - Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("genders[]", genre.id) - Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("exclude_genders[]", genre.id) - } - } - } - } - } - return GET(url.build().toString(), headers) - } - override fun searchMangaSelector() = popularMangaSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select("h2.element-subtitle").text() - document.select("h5.card-title").let { - author = it?.first()?.attr("title")?.substringAfter(", ") - artist = it?.last()?.attr("title")?.substringAfter(", ") - } - genre = document.select("a.py-2").joinToString(", ") { - it.text() - } - description = document.select("p.element-description")?.text() - status = parseStatus(document.select("span.book-status")?.text().orEmpty()) - thumbnail_url = document.select(".book-thumbnail").attr("src") - } - private fun parseStatus(status: String) = when { - status.contains("Publicándose") -> SManga.ONGOING - status.contains("Finalizado") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - // One-shot - if (document.select("div.chapters").isEmpty()) { - return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) } - } - // Regular list of chapters - val chapters = mutableListOf<SChapter>() - document.select(regularChapterListSelector()).forEach { chapelement -> - val chapternumber = chapelement.select("a.btn-collapse").text() - .substringBefore(":") - .substringAfter("Capítulo") - .trim() - .toFloat() - val chaptername = chapelement.select("div.col-10.text-truncate").text() - val scanelement = chapelement.select("ul.chapter-list > li") - if (getScanlatorPref()) { - scanelement.forEach { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) } - } else { - scanelement.first { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) } - } - } - return chapters - } - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used") - private fun oneShotChapterListSelector() = "div.chapter-list-element > ul.list-group li.list-group-item" - private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply { - url = element.select("div.row > .text-right > a").attr("href") - name = "One Shot" - scanlator = element.select("div.col-md-6.text-truncate")?.text() - date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } - ?: 0 - } - private fun regularChapterListSelector() = "div.chapters > ul.list-group li.p-0.list-group-item" - private fun regularChapterFromElement(element: Element, chName: String, number: Float) = SChapter.create().apply { - url = element.select("div.row > .text-right > a").attr("href") - name = chName - chapter_number = number - scanlator = element.select("div.col-md-6.text-truncate")?.text() - date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } - ?: 0 - } - private fun parseChapterDate(date: String): Long = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)?.time - ?: 0 - override fun pageListRequest(chapter: SChapter): Request { - val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri() - // Get /cascade instead of /paginate to get all pages at once - val newUrl = if (getPageMethodPref() == "cascade" && currentUrl.contains("paginated")) { - currentUrl.substringBefore("paginated") + "cascade" - } else if (getPageMethodPref() == "paginated" && currentUrl.contains("cascade")) { - currentUrl.substringBefore("cascade") + "paginated" - } else currentUrl - return GET(newUrl, headers) - } - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - if (getPageMethodPref() == "cascade") { - document.select("div.viewer-container img").forEach { - add( - Page( - size, - "", - it.let { - if (it.hasAttr("data-src")) - it.attr("abs:data-src") else it.attr("abs:src") - } - ) - ) - } - } else { - val pageList = document.select("#viewer-pages-select").first().select("option").map { it.attr("value").toInt() } - val url = document.baseUri() - pageList.forEach { - add(Page(it, "$url/$it")) - } - } - } - // Note: At this moment (15/02/2021) it's necessary to make the image request without headers to prevent 403. - override fun imageRequest(page: Page) = GET(page.imageUrl!!) - - override fun imageUrlParse(document: Document): String { - return document.select("div.viewer-container > div.img-container > img.viewer-image").attr("src") - } - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/$PREFIX_LIBRARY/$id", headers) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - - client.newCall(searchMangaByIdRequest(realQuery)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = "/$PREFIX_LIBRARY/$realQuery" - MangasPage(listOf(details), false) - } - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - private class Types : UriPartFilter( - "Filtrar por tipo", - arrayOf( - Pair("Ver todo", ""), - Pair("Manga", "manga"), - Pair("Manhua", "manhua"), - Pair("Manhwa", "manhwa"), - Pair("Novela", "novel"), - Pair("One shot", "one_shot"), - Pair("Doujinshi", "doujinshi"), - Pair("Oel", "oel") - ) - ) - - private class Status : UriPartFilter( - "Filtrar por estado de serie", - arrayOf( - Pair("Ver todo", ""), - Pair("Publicándose", "publishing"), - Pair("Finalizado", "ended"), - Pair("Cancelado", "cancelled"), - Pair("Pausado", "on_hold") - ) - ) - - private class TranslationStatus : UriPartFilter( - "Filtrar por estado de traducción", - arrayOf( - Pair("Ver todo", ""), - Pair("Activo", "publishing"), - Pair("Finalizado", "ended"), - Pair("Abandonado", "cancelled") - ) - ) - - private class Demography : UriPartFilter( - "Filtrar por demografía", - arrayOf( - Pair("Ver todo", ""), - Pair("Seinen", "seinen"), - Pair("Shoujo", "shoujo"), - Pair("Shounen", "shounen"), - Pair("Josei", "josei"), - Pair("Kodomo", "kodomo") - ) - ) - - private class FilterBy : UriPartFilter( - "Filtrar por", - arrayOf( - Pair("Título", "title"), - Pair("Autor", "author"), - Pair("Compañia", "company") - ) - ) - - class SortBy : Filter.Sort( - "Ordenar por", - SORTABLES.map { it.first }.toTypedArray(), - Selection(0, false) - ) - - private class ContentType(name: String, val id: String) : Filter.TriState(name) - - private class ContentTypeList(content: List<ContentType>) : Filter.Group<ContentType>("Filtrar por tipo de contenido", content) - - private class Genre(name: String, val id: String) : Filter.TriState(name) - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres) - - override fun getFilterList() = FilterList( - Types(), - Filter.Separator(), - Filter.Header("Ignorado sino se filtra por tipo"), - Status(), - Filter.Separator(), - Filter.Header("Ignorado sino se filtra por tipo"), - TranslationStatus(), - Filter.Separator(), - Demography(), - Filter.Separator(), - FilterBy(), - Filter.Separator(), - SortBy(), - Filter.Separator(), - ContentTypeList(getContentTypeList()), - Filter.Separator(), - GenreList(getGenreList()) - ) - - // Array.from(document.querySelectorAll('#books-genders .col-auto .custom-control')) - // .map(a => `Genre("${a.querySelector('label').innerText}", "${a.querySelector('input').value}")`).join(',\n') - // on https://lectortmo.com/library - // Last revision 15/02/2021 - private fun getGenreList() = listOf( - Genre("Acción", "1"), - Genre("Aventura", "2"), - Genre("Comedia", "3"), - Genre("Drama", "4"), - Genre("Recuentos de la vida", "5"), - Genre("Ecchi", "6"), - Genre("Fantasia", "7"), - Genre("Magia", "8"), - Genre("Sobrenatural", "9"), - Genre("Horror", "10"), - Genre("Misterio", "11"), - Genre("Psicológico", "12"), - Genre("Romance", "13"), - Genre("Ciencia Ficción", "14"), - Genre("Thriller", "15"), - Genre("Deporte", "16"), - Genre("Girls Love", "17"), - Genre("Boys Love", "18"), - Genre("Harem", "19"), - Genre("Mecha", "20"), - Genre("Supervivencia", "21"), - Genre("Reencarnación", "22"), - Genre("Gore", "23"), - Genre("Apocalíptico", "24"), - Genre("Tragedia", "25"), - Genre("Vida Escolar", "26"), - Genre("Historia", "27"), - Genre("Militar", "28"), - Genre("Policiaco", "29"), - Genre("Crimen", "30"), - Genre("Superpoderes", "31"), - Genre("Vampiros", "32"), - Genre("Artes Marciales", "33"), - Genre("Samurái", "34"), - Genre("Género Bender", "35"), - Genre("Realidad Virtual", "36"), - Genre("Ciberpunk", "37"), - Genre("Musica", "38"), - Genre("Parodia", "39"), - Genre("Animación", "40"), - Genre("Demonios", "41"), - Genre("Familia", "42"), - Genre("Extranjero", "43"), - Genre("Niños", "44"), - Genre("Realidad", "45"), - Genre("Telenovela", "46"), - Genre("Guerra", "47"), - Genre("Oeste", "48") - ) - - private fun getContentTypeList() = listOf( - ContentType("Webcomic", "webcomic"), - ContentType("Yonkoma", "yonkoma"), - ContentType("Amateur", "amateur"), - ContentType("Erótico", "erotic") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - - val SFWModePref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SFW_MODE_PREF - title = SFW_MODE_PREF_TITLE - summary = SFW_MODE_PREF_SUMMARY - setDefaultValue(SFW_MODE_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SFW_MODE_PREF, checkValue).commit() - } - } - - val scanlatorPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SCANLATOR_PREF - title = SCANLATOR_PREF_TITLE - summary = SCANLATOR_PREF_SUMMARY - setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit() - } - } - - val pageMethodPref = androidx.preference.ListPreference(screen.context).apply { - key = PAGE_METHOD_PREF - title = PAGE_METHOD_PREF_TITLE - entries = arrayOf("Cascada", "Páginado") - entryValues = arrayOf("cascade", "paginated") - summary = PAGE_METHOD_PREF_SUMMARY - setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - // Rate limit - val apiRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = WEB_RATELIMIT_PREF - title = WEB_RATELIMIT_PREF_TITLE - summary = WEB_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(SFWModePref) - screen.addPreference(scanlatorPref) - screen.addPreference(pageMethodPref) - screen.addPreference(apiRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - - val SFWModePref = CheckBoxPreference(screen.context).apply { - key = SFW_MODE_PREF - title = SFW_MODE_PREF_TITLE - summary = SFW_MODE_PREF_SUMMARY - setDefaultValue(SFW_MODE_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SFW_MODE_PREF, checkValue).commit() - } - } - - val scanlatorPref = CheckBoxPreference(screen.context).apply { - key = SCANLATOR_PREF - title = SCANLATOR_PREF_TITLE - summary = SCANLATOR_PREF_SUMMARY - setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit() - } - } - - val pageMethodPref = ListPreference(screen.context).apply { - key = PAGE_METHOD_PREF - title = PAGE_METHOD_PREF_TITLE - entries = arrayOf("Cascada", "Páginado") - entryValues = arrayOf("cascade", "paginated") - summary = PAGE_METHOD_PREF_SUMMARY - setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - // Rate limit - val apiRateLimitPreference = ListPreference(screen.context).apply { - key = WEB_RATELIMIT_PREF - title = WEB_RATELIMIT_PREF_TITLE - summary = WEB_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(SFWModePref) - screen.addPreference(scanlatorPref) - screen.addPreference(pageMethodPref) - screen.addPreference(apiRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - } - - private fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE) - - private fun getSFWModePref(): Boolean = preferences.getBoolean(SFW_MODE_PREF, SFW_MODE_PREF_DEFAULT_VALUE) - - private fun getPageMethodPref() = preferences.getString(PAGE_METHOD_PREF, PAGE_METHOD_PREF_DEFAULT_VALUE) - - companion object { - private const val SCANLATOR_PREF = "scanlatorPref" - private const val SCANLATOR_PREF_TITLE = "Mostrar todos los scanlator" - private const val SCANLATOR_PREF_SUMMARY = "Se mostraran capítulos repetidos pero con diferentes Scanlators" - private const val SCANLATOR_PREF_DEFAULT_VALUE = true - - private const val SFW_MODE_PREF = "SFWModePref" - private const val SFW_MODE_PREF_TITLE = "Ocultar contenido NSFW" - private const val SFW_MODE_PREF_SUMMARY = "Ocultar el contenido erótico (puede que aún activandolo se sigan mostrando portadas o series NSFW). Ten en cuenta que al activarlo se ignoran filtros al explorar y buscar.\nLos filtros ignorados son: Filtrar por tipo de contenido (Erotico) y el Filtrar por generos: Ecchi, Boys Love, Girls Love y Harem." - private const val SFW_MODE_PREF_DEFAULT_VALUE = false - private val SFW_MODE_PREF_EXCLUDE_GENDERS = listOf("6", "17", "18", "19") - - private const val PAGE_METHOD_PREF = "pageMethodPref" - private const val PAGE_METHOD_PREF_TITLE = "Método para descargar imágenes" - private const val PAGE_METHOD_PREF_SUMMARY = "Previene ser banneado por el servidor cuando se usa la configuración \"Cascada\" ya que esta reduce la cantidad de solicitudes.\nPuedes usar \"Páginado\" cuando las imágenes no carguen usando \"Cascada\".\nConfiguración actual: %s" - private const val PAGE_METHOD_PREF_DEFAULT_VALUE = "cascade" - - private const val WEB_RATELIMIT_PREF = "webRatelimitPreference" - // Ratelimit permits per second for main website - private const val WEB_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para el sitio web" - // This value affects network request amount to TMO url. Lower this value may reduce the chance to get HTTP 429 error, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s - private const val WEB_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red a la URL de TMO. Reducir este valor puede disminuir la posibilidad de obtener un error HTTP 429, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s" - private const val WEB_RATELIMIT_PREF_DEFAULT_VALUE = "10" - - private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference" - // Ratelimit permits per second for image CDN - private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para descarga de imágenes" - // This value affects network request amount for loading image. Lower this value may reduce the chance to get error when loading image, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s - private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red para descargar imágenes. Reducir este valor puede disminuir errores al cargar imagenes, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s" - private const val IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE = "10" - - private val ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray() - - const val PREFIX_LIBRARY = "library" - const val PREFIX_ID_SEARCH = "id:" - - private val SORTABLES = listOf( - Pair("Me gusta", "likes_count"), - Pair("Alfabético", "alphabetically"), - Pair("Puntuación", "score"), - Pair("Creación", "creation"), - Pair("Fecha estreno", "release_date"), - Pair("Núm. Capítulos", "num_chapters") - ) - } -} diff --git a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt b/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt deleted file mode 100644 index 486ff7133..000000000 --- a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt +++ /dev/null @@ -1,42 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.tumangaonline - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://lectortmo.com/library/:type/:id/:slug intents and redirects them to - * the main Tachiyomi process. - */ -class TuMangaOnlineUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - - if (pathSegments != null && pathSegments.size > 3) { - val type = pathSegments[1] - val id = pathSegments[2] - val slug = pathSegments[3] - - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${TuMangaOnline.PREFIX_ID_SEARCH}$type/$id/$slug") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("TMOUrlActivity", e.toString()) - } - } else { - Log.e("TMOUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/es/vcpvmp/AndroidManifest.xml b/src/es/vcpvmp/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/es/vcpvmp/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/es/vcpvmp/build.gradle b/src/es/vcpvmp/build.gradle deleted file mode 100644 index 078408a18..000000000 --- a/src/es/vcpvmp/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'VCPVMP' - pkgNameSuffix = "es.vcpvmp" - extClass = '.VCPVMPFactory' - extVersionCode = 4 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/vcpvmp/res/ic_launcher-web.png b/src/es/vcpvmp/res/ic_launcher-web.png deleted file mode 100644 index 0c5311d14..000000000 Binary files a/src/es/vcpvmp/res/ic_launcher-web.png and /dev/null differ diff --git a/src/es/vcpvmp/res/mipmap-hdpi/ic_launcher.png b/src/es/vcpvmp/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 02df2d7fa..000000000 Binary files a/src/es/vcpvmp/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/vcpvmp/res/mipmap-mdpi/ic_launcher.png b/src/es/vcpvmp/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7128ab184..000000000 Binary files a/src/es/vcpvmp/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/vcpvmp/res/mipmap-xhdpi/ic_launcher.png b/src/es/vcpvmp/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d027020d2..000000000 Binary files a/src/es/vcpvmp/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/vcpvmp/res/mipmap-xxhdpi/ic_launcher.png b/src/es/vcpvmp/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ace26f9a..000000000 Binary files a/src/es/vcpvmp/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/vcpvmp/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/vcpvmp/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e67aaca86..000000000 Binary files a/src/es/vcpvmp/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/es/vcpvmp/src/eu/kanade/tachiyomi/extension/es/vcpvmp/VCPVMP.kt b/src/es/vcpvmp/src/eu/kanade/tachiyomi/extension/es/vcpvmp/VCPVMP.kt deleted file mode 100644 index bc92c1466..000000000 --- a/src/es/vcpvmp/src/eu/kanade/tachiyomi/extension/es/vcpvmp/VCPVMP.kt +++ /dev/null @@ -1,487 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.vcpvmp - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -open class VCPVMP(override val name: String, override val baseUrl: String) : ParsedHttpSource() { - - override val lang = "es" - - override val supportsLatest: Boolean = false - - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/page/$page", headers) - - override fun popularMangaSelector() = "div#ccontent div.gallery" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.select("a.cover").first().let { - setUrlWithoutDomain(it.attr("href")) - title = it.select("div.caption").text() - thumbnail_url = it.select("img").attr("abs:src").substringBefore("?") - } - } - - override fun popularMangaNextPageSelector() = "ul.pagination > li.active + li" - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - document.select("div#catag").let { - genre = document.select("div#tagsin > a[rel=tag]").joinToString { it.text() } - status = SManga.UNKNOWN - thumbnail_url = document.select(pageListSelector).firstOrNull()?.attr("abs:src") - } - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return Observable.just( - listOf( - SChapter.create().apply { - name = manga.title - url = manga.url - } - ) - ) - } - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - protected open val pageListSelector = "div.comicimg img" - override fun pageListParse(document: Document): List<Page> = document.select(pageListSelector) - .mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = HttpUrl.parse(baseUrl)!!.newBuilder() - val isOnVCP = (baseUrl == "https://vercomicsporno.com") - - url.addPathSegments("page") - url.addPathSegments(page.toString()) - url.addQueryParameter("s", query) - - filters.forEach { filter -> - when (filter) { - is Genre -> { - when (filter.toUriPart().isNotEmpty()) { - true -> { - url = HttpUrl.parse(baseUrl)!!.newBuilder() - - url.addPathSegments(if (isOnVCP) "tags" else "genero") - url.addPathSegments(filter.toUriPart()) - - url.addPathSegments("page") - url.addPathSegments(page.toString()) - } - } - } - is Category -> { - when (filter.toUriPart().isNotEmpty()) { - true -> { - url.addQueryParameter("cat", filter.toUriPart()) - } - } - } - } - } - - return GET(url.build().toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun getFilterList() = FilterList( - Genre(), - Filter.Separator(), - Category() - ) - - // Array.from(document.querySelectorAll('div.tagcloud a.tag-cloud-link')).map(a => `Pair("${a.innerText}", "${a.href.replace('https://vercomicsporno.com/etiqueta/', '')}")`).join(',\n') - // from https://vercomicsporno.com/ - private class Genre : UriPartFilter( - "Filtrar por categoría", - arrayOf( - Pair("Ver todos", ""), - Pair("Anales", "anales"), - Pair("Comics Porno", "comics-porno"), - Pair("Culonas", "culonas"), - Pair("Doujins", "doujins"), - Pair("Furry", "furry"), - Pair("Incesto", "incesto"), - Pair("Lesbianas", "lesbianas"), - Pair("Madre Hijo", "madre-hijo"), - Pair("Mamadas", "mamadas"), - Pair("Manga Hentai", "manga-hentai"), - Pair("Milfs", "milfs"), - Pair("Milftoon", "milftoon-comics"), - Pair("Orgias", "orgias"), - Pair("Parodias Porno", "parodias-porno"), - Pair("Rubias", "rubias"), - Pair("Series De Tv", "series-de-tv"), - Pair("Tetonas", "tetonas"), - Pair("Trios", "trios"), - Pair("Videojuegos", "videojuegos"), - Pair("Yuri", "yuri-2") - ) - ) - - // Array.from(document.querySelectorAll('form select#cat option.level-0')).map(a => `Pair("${a.innerText}", "${a.value}")`).join(',\n') - // from https://vercomicsporno.com/ - private class Category : UriPartFilter( - "Filtrar por categoría", - arrayOf( - Pair("Ver todos", ""), - Pair("5ish", "2853"), - Pair("69", "1905"), - Pair("8muses", "856"), - Pair("Aarokira", "2668"), - Pair("ABBB", "3058"), - Pair("Absurd Stories", "2846"), - Pair("Adam 00", "1698"), - Pair("Aeolus", "2831"), - Pair("Afrobull", "3032"), - Pair("Alcor", "2837"), - Pair("angstrom", "2996"), - Pair("Anonymouse", "2851"), - Pair("Aoino Broom", "3086"), - Pair("Aquarina", "2727"), - Pair("Arabatos", "1780"), - Pair("Aroma Sensei", "2663"), - Pair("Art of jaguar", "167"), - Pair("Atreyu Studio", "3040"), - Pair("Awaerr", "2921"), - Pair("Bakuhaku", "2866"), - Pair("Bashfulbeckon", "2841"), - Pair("Bear123", "2814"), - Pair("Black and White", "361"), - Pair("Black House", "3044"), - Pair("Blackadder", "83"), - Pair("Blacky Chan", "2901"), - Pair("Blargsnarf", "2728"), - Pair("BlueVersusRed", "2963"), - Pair("Bnouait", "2706"), - Pair("Born to Die", "2982"), - Pair("Buena trama", "2579"), - Pair("Buru", "2736"), - Pair("Cagri", "2751"), - Pair("CallMePlisskin", "2960"), - Pair("Catfightcentral", "2691"), - Pair("cecyartbytenshi", "2799"), - Pair("Cheka.art", "2999"), - Pair("Cherry Mouse Street", "2891"), - Pair("cherry-gig", "2679"), - Pair("Chochi", "3085"), - Pair("ClaraLaine", "2697"), - Pair("Clasicos", "2553"), - Pair("Cobatsart", "2729"), - Pair("Comics porno", "6"), - Pair("Comics Porno 3D", "1910"), - Pair("Comics porno mexicano", "511"), - Pair("Comics XXX", "119"), - Pair("CrazyDad3d", "2657"), - Pair("Creeeen", "2922"), - Pair("Croc", "1684"), - Pair("Crock", "3004"), - Pair("Cyberunique", "2801"), - Pair("Danaelus", "3080"), - Pair("DankoDeadZone", "3055"), - Pair("Darkhatboy", "2856"), - Pair("DarkShadow", "2845"), - Pair("DarkToons Cave", "2893"), - Pair("Dasan", "2692"), - Pair("David Willis", "2816"), - Pair("Dboy", "3094"), - Pair("Dconthedancefloor", "2905"), - Pair("Degenerate", "2923"), - Pair("Diathorn", "2894"), - Pair("Dicasty", "2983"), - Pair("Dimedrolly", "3017"), - Pair("Dirtycomics", "2957"), - Pair("DMAYaichi", "2924"), - Pair("Dony", "2769"), - Pair("Doxy", "2698"), - Pair("Drawnsex", "9"), - Pair("DrCockula", "2708"), - Pair("Dude-doodle-do", "2984"), - Pair("ebluberry", "2842"), - Pair("Ecchi Kimochiii", "1948"), - Pair("EcchiFactor 2.0", "1911"), - Pair("Eirhjien", "2817"), - Pair("Eliana Asato", "2878"), - Pair("Ender Selya", "2774"), - Pair("Enessef-UU", "3124"), - Pair("ERN", "3010"), - Pair("Erotibot", "2711"), - Pair("Escoria", "2945"), - Pair("Evil Rick", "2946"), - Pair("FearingFun", "3057"), - Pair("Felsala", "2138"), - Pair("Fetishhand", "2932"), - Pair("Fikomi", "2887"), - Pair("Fixxxer", "2737"), - Pair("FLBL", "3050"), - Pair("Folo", "2762"), - Pair("Forked Tail", "2830"), - Pair("Fotonovelas XXX", "320"), - Pair("Freckles", "3095"), - Pair("Fred Perry", "2832"), - Pair("Freehand", "400"), - Pair("FrozenParody", "1766"), - Pair("Fuckit", "2883"), - Pair("Funsexydragonball", "2786"), - Pair("Futanari", "1732"), - Pair("Futanari Fan", "2787"), - Pair("Garabatoz", "2877"), - Pair("Gerph", "2889"), - Pair("GFI", "3123"), - Pair("Ghettoyouth", "2730"), - Pair("Gilftoon", "2619"), - Pair("Glassfish", "84"), - Pair("GNAW", "3084"), - Pair("Goat-Head", "3011"), - Pair("Greivs", "3136"), - Pair("Grigori", "2775"), - Pair("Grose", "2876"), - Pair("Gundam888", "2681"), - Pair("Hagfish", "2599"), - Pair("Hary Draws", "2752"), - Pair("Hioshiru", "2673"), - Pair("Hmage", "2822"), - Pair("Horny-Oni", "2947"), - Pair("Hoteggs102", "2925"), - Pair("InCase", "1927"), - Pair("Incest Candy", "3126"), - Pair("Incesto 3d", "310"), - Pair("Incognitymous", "2693"), - Pair("Inker Shike", "2895"), - Pair("Interracial", "364"), - Pair("Inusen", "2854"), - Pair("Inuyuru", "2699"), - Pair("isakishi", "2721"), - Pair("Jadenkaiba", "2064"), - Pair("javisuzumiya", "2823"), - Pair("Jay Marvel", "2135"), - Pair("Jay Naylor", "174"), - Pair("Jellcaps", "2818"), - Pair("Jhon Person", "135"), - Pair("Jitsch", "2835"), - Pair("Jkr", "718"), - Pair("JLullaby", "2680"), - Pair("John North", "2927"), - Pair("JohnJoseco", "2906"), - Pair("JooJoo", "3026"), - Pair("Joru", "2798"), - Pair("JZerosk", "2757"), - Pair("K/DA", "2667"), - Pair("Ka-iN", "2874"), - Pair("Kadath", "2700"), - Pair("Kannel", "2836"), - Pair("Kaos", "1994"), - Pair("Karmagik", "2943"), - Pair("Karmakaze", "2968"), - Pair("Katoto Chan", "2916"), - Pair("Kimmundo", "2669"), - Pair("Kinkamashe", "2873"), - Pair("Kinkymation", "2733"), - Pair("Kirtu", "107"), - Pair("Kiselrok", "3075"), - Pair("Kogeikun", "2738"), - Pair("KrasH", "2958"), - Pair("Krazy Krow", "2848"), - Pair("Kumi Pumi", "2771"), - Pair("l", "1"), - Pair("Lady Astaroth", "2722"), - Pair("LaundryMom", "2926"), - Pair("LawyBunne", "2744"), - Pair("Laz", "2933"), - Pair("Lemon Font", "2750"), - Pair("Lewdua", "2734"), - Pair("LilithN", "2991"), - Pair("Locofuria", "2578"), - Pair("Loonyjams", "2935"), - Pair("Los Simpsons XXX", "94"), - Pair("Lumo", "2858"), - Pair("MAD-Project", "2890"), - Pair("Magnificent Sexy Gals", "2942"), - Pair("Manaworld", "85"), - Pair("Manaworldcomics", "164"), - Pair("Manga hentai", "152"), - Pair("Maoukouichi", "2910"), - Pair("Marcos Crot", "3025"), - Pair("Matemi", "2741"), - Pair("Mavruda", "2865"), - Pair("MCC", "2843"), - Pair("Meesh", "2740"), - Pair("Meinfischer", "3063"), - Pair("Melkor Mancin", "169"), - Pair("Meowwithme", "2936"), - Pair("Metal Owl", "2694"), - Pair("Miles-DF", "2864"), - Pair("Milffur", "140"), - Pair("Milftoon", "13"), - Pair("Milftoonbeach", "1712"), - Pair("Milky Bunny", "3066"), - Pair("MissBehaviour", "2997"), - Pair("Mojarte", "1417"), - Pair("Moose", "2939"), - Pair("morganagod", "2917"), - Pair("Moval-X", "2785"), - Pair("Mr. E Comics", "2562"), - Pair("Mr. Estella", "3068"), - Pair("MrPotatoParty", "2712"), - Pair("My Bad Bunny", "2989"), - Pair("Myster Box", "2670"), - Pair("Nastee34", "2930"), - Pair("Neal D Anderson", "2725"), - Pair("nearphotison", "3039"), - Pair("nicekotatsu", "2749"), - Pair("nihaotomita", "2998"), - Pair("Nikipostat", "2824"), - Pair("NiniiDawns", "2937"), - Pair("Nisego", "2768"), - Pair("Norasuko", "2800"), - Pair("Noticias", "1664"), - Pair("nsfyosu", "2859"), - Pair("Nyoronyan", "2758"), - Pair("NyuroraXBigdon", "3134"), - Pair("O-tako Studios", "2723"), - Pair("Oh!Nice", "2896"), - Pair("OldFlameShotgun", "2884"), - Pair("Otomo-San", "2788"), - Pair("Pack Imagenes", "654"), - Pair("Pak009", "2819"), - Pair("Palcomix", "48"), - Pair("Pandora Box", "155"), - Pair("peculiart", "3000"), - Pair("Pegasus Smith", "2682"), - Pair("Personalami", "2789"), - Pair("PeterAndWhitney", "2860"), - Pair("Pia-Sama", "2797"), - Pair("PinkPawg", "2861"), - Pair("Pinktoon", "2868"), - Pair("Pixelboy", "2840"), - Pair("pleasure castle", "3081"), - Pair("Pokeporn", "1914"), - Pair("Polyle", "2952"), - Pair("Poonet", "648"), - Pair("Prism Girls", "1926"), - Pair("Privados", "858"), - Pair("PTDMCA", "2949"), - Pair("QTsunade", "2770"), - Pair("quad", "3051"), - Pair("Quarko-Muon", "2872"), - Pair("Queenchikki", "3062"), - Pair("QueenComplex", "2951"), - Pair("QueenTsunade", "2811"), - Pair("Queervanire", "2871"), - Pair("r_ex", "2898"), - Pair("Raidon-san", "2962"), - Pair("RanmaBooks", "1974"), - Pair("Razter", "2689"), - Pair("recreator 2099", "2671"), - Pair("Redboard", "2803"), - Pair("reddanmanic", "2867"), - Pair("Reinbach", "2888"), - Pair("Relatedguy", "2829"), - Pair("ReloadHB", "3012"), - Pair("Revolverwing", "2790"), - Pair("RickFoxxx", "1411"), - Pair("Rino99", "2934"), - Pair("Ripperelite", "2820"), - Pair("RobCiveCat", "2739"), - Pair("RogueArtLove", "2812"), - Pair("Rousfairly", "2776"), - Pair("Rukasu", "2778"), - Pair("Rupalulu", "3135"), - Pair("SakuSaku Panic", "2907"), - Pair("SaMelodii", "2701"), - Pair("SanePerson", "2683"), - Pair("SatyQ", "3024"), - Pair("Saurian", "2950"), - Pair("Selrock", "2886"), - Pair("Shadako26", "2780"), - Pair("Shadbase", "1713"), - Pair("Shadow2007x", "2781"), - Pair("ShadowFenrir", "3132"), - Pair("Sheela", "2690"), - Pair("Sillygirl", "2129"), - Pair("Sin Porno", "2266"), - Pair("Sinner", "2897"), - Pair("Sinope", "2985"), - Pair("Sirkowski", "2802"), - Pair("Skulltitti", "2918"), - Pair("SleepyGimp", "2911"), - Pair("Slipshine", "2791"), - Pair("Slypon", "2912"), - Pair("Smutichi", "2821"), - Pair("Snaketrap", "2940"), - Pair("Sorje", "2961"), - Pair("Spirale", "2870"), - Pair("Stereoscope Comics", "3054"), - Pair("Stormfeder", "2759"), - Pair("Sun1Sol", "2782"), - Pair("SunsetRiders7", "1705"), - Pair("Super Melons", "2850"), - Pair("Taboolicious", "88"), - Pair("Tease Comix", "2948"), - Pair("Tekuho", "2601"), - Pair("Tentabat", "2862"), - Pair("the dark mangaka", "2783"), - Pair("The Pit", "2792"), - Pair("thegoodbadart", "2684"), - Pair("TheKite", "2825"), - Pair("Theminus", "2828"), - Pair("TheNewGuy", "3018"), - Pair("TheOtherHalf", "2666"), - Pair("Tim Fischer", "2763"), - Pair("Totempole", "2746"), - Pair("TotesFleisch8", "2764"), - Pair("Tovio Rogers", "3056"), - Pair("Tracy Scops", "2648"), - Pair("Transmorpher DDS", "2672"), - Pair("Turtlechan", "2796"), - Pair("TvMx", "2793"), - Pair("Urakan", "3043"), - Pair("Uzonegro", "2695"), - Pair("V3rnon", "2973"), - Pair("Vaiderman", "3031"), - Pair("VentZX", "2575"), - Pair("Vercomicsporno", "1376"), - Pair("Watsup", "2863"), - Pair("Whargleblargle", "2844"), - Pair("Wherewolf", "2685"), - Pair("Witchking00", "1815"), - Pair("Wulfsaga", "2931"), - Pair("Xamrock", "2686"), - Pair("Xierra099", "2702"), - Pair("Xkit", "2703"), - Pair("Y3df", "86"), - Pair("Yamamoto", "3019"), - Pair("Yusioka", "3082"), - Pair("Zillionaire", "2807"), - Pair("Zzomp", "252"), - Pair("ZZZ Comics", "2839") - ) - ) -} diff --git a/src/es/vcpvmp/src/eu/kanade/tachiyomi/extension/es/vcpvmp/VCPVMPFactory.kt b/src/es/vcpvmp/src/eu/kanade/tachiyomi/extension/es/vcpvmp/VCPVMPFactory.kt deleted file mode 100644 index 164ff08d3..000000000 --- a/src/es/vcpvmp/src/eu/kanade/tachiyomi/extension/es/vcpvmp/VCPVMPFactory.kt +++ /dev/null @@ -1,381 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.vcpvmp - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -import eu.kanade.tachiyomi.source.model.Filter - -@Nsfw -class VCPVMPFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - VCP(), - VMP() - ) -} - -class VCP : VCPVMP("VCP", "https://vercomicsporno.com") - -class VMP : VCPVMP("VMP", "https://vermangasporno.com") { - override val pageListSelector = "div.comicimg img[src^=$baseUrl]" - - // Array.from(document.querySelectorAll('div.tagcloud a.tag-cloud-link')) - // .map(a => `Pair("${a.innerText}", "${a.href.replace('https://vermangasporno.com/genero/', '')}")`).join(',\n') - // from https://vermangasporno.com/ - private class Genre : UriPartFilter( - "Filtrar por generos", - arrayOf( - Pair("Ver todos", ""), - Pair("Ahegao", "ahegao"), - Pair("Anal", "anal"), - Pair("Big Ass", "big-ass"), - Pair("Big Breasts", "big-breasts"), - Pair("Blowjob", "blowjob"), - Pair("Cheating", "cheating"), - Pair("Colegiala", "colegiala"), - Pair("Fullcolor", "fullcolor"), - Pair("Group", "group"), - Pair("Incest", "incest"), - Pair("loli", "loli"), - Pair("Lolicon", "lolicon"), - Pair("Milf", "milf"), - Pair("Nakadashi", "nakadashi"), - Pair("Paizuri", "paizuri"), - Pair("Schoolgirl Uniform", "schoolgirl-uniform"), - Pair("Sole Female", "sole-female"), - Pair("Stockings", "stockings"), - Pair("Tetona", "tetona"), - Pair("Tetonas", "tetonas") - ) - ) - - // Array.from(document.querySelectorAll('form select#cat option.level-0')).map(a => `Pair("${a.innerText}", "${a.value}")`).join(',\n') - // from https://vermangasporno.com/ - private class Category : UriPartFilter( - "Filtrar por categoría", - arrayOf( - Pair("Ver todos", ""), - Pair("3×3 Eyes", "1325"), - Pair("Accel World", "175"), - Pair("Aikatsu!", "1983"), - Pair("Akame ga Kill!", "2235"), - Pair("Akaruku Tanoshiku Kimochiyoku", "2268"), - Pair("Amagami", "1194"), - Pair("Amagi Brilliant Park", "209"), - Pair("Amano Megumi ha Sukidarake!", "888"), - Pair("Ane Doki", "1121"), - Pair("Angel Beats!", "176"), - Pair("Ano Hi Mita Hana no Namae wo Bokutachi wa Mada Shiranai", "1097"), - Pair("Another", "177"), - Pair("Ansatsu Kyoushitsu", "2"), - Pair("Aoi Hana", "1256"), - Pair("Aquarion EVOL", "2089"), - Pair("Arcana Heart", "934"), - Pair("Arslan senki", "2068"), - Pair("Asobi ni Iku yo!", "1280"), - Pair("Azur Lane", "2076"), - Pair("Baka to Test to Shoukanjuu", "1202"), - Pair("Baka to Test to Shoukanjuu | Autor: Kurosawa Kiyotaka", "1201"), - Pair("Bakemonogatari", "931"), - Pair("Bakuman", "1259"), - Pair("BanG Dream!", "1863"), - Pair("Batman", "184"), - Pair("bijin onna joushi takizawa-san", "2104"), - Pair("Bishoujo Senshi Sailor Moon", "745"), - Pair("Bleach", "82"), - Pair("Blend S", "2102"), - Pair("Blood+", "189"), - Pair("Boku no Hero Academia", "245"), - Pair("Boku wa tomodachi ga sukunai", "674"), - Pair("Bokutachi wa Benkyou ga Dekinai", "2180"), - Pair("Boruto", "2071"), - Pair("Capcom", "251"), - Pair("Charlotte", "1444"), - Pair("Clannad", "102"), - Pair("Claymore", "170"), - Pair("Code Geass", "171"), - Pair("Cyberbots", "981"), - Pair("Dagashi Kashi", "733"), - Pair("Danganronpa", "92"), - Pair("Danmachi", "1042"), - Pair("Darker than Black", "995"), - Pair("Darker than Black: The Black Contractor", "1086"), - Pair("Darkstalkers", "1996"), - Pair("Date A Live", "670"), - Pair("Dead Or Alive", "233"), - Pair("Deadman Wonderland", "900"), - Pair("Denpa Onna to Seishun Otoko", "952"), - Pair("Dokidoki! Precure", "1919"), - Pair("Dr. Slump", "977"), - Pair("Dragon Quest", "3"), - Pair("dragon quest iii", "239"), - Pair("Dragonball", "4"), - Pair("Dragon’s Crown", "1064"), - Pair("DREAM C CLUB", "941"), - Pair("Dungeon Ni Deai O Motomeru No Wa Machigatteiru Darou Ka", "226"), - Pair("Dungeon ni Deai wo Motomeru no wa Machigatteiru Darou ka", "127"), - Pair("Dungeon Travelers", "1951"), - Pair("Dynasty Warriors", "1885"), - Pair("Enen no Shouboutai", "2169"), - Pair("Eromanga Sensei", "901"), - Pair("Evangelion", "172"), - Pair("Fairy Tail", "348"), - Pair("Fantasy Earth Zero", "1433"), - Pair("Fate Kaleid Liner Prisma Illya", "237"), - Pair("Fate Stay Night", "235"), - Pair("Fate/Grand Order", "891"), - Pair("Fate/hollow ataraxia", "1294"), - Pair("Fate/stay night", "722"), - Pair("Final Fantasy", "591"), - Pair("Final Figh", "1215"), - Pair("Freezing", "190"), - Pair("Full Metal Daemon: Muramasa", "122"), - Pair("Full Metal Panic", "167"), - Pair("Fullmetal Alchemist", "118"), - Pair("Furry", "1972"), - Pair("Gabriel Dropout", "884"), - Pair("Gakkou Gurashi!", "1047"), - Pair("Gate: Jieitai Kano Chi nite Kaku Tatakaeri", "761"), - Pair("Gate: Jieitai Kanochi nite", "241"), - Pair("Gate: Jieitai Kanochi nite Kaku Tatakaeri", "243"), - Pair("Gegege no Kitarou", "1862"), - Pair("Getsuyoubi no Tawawa", "250"), - Pair("Ghost In The Shell", "1898"), - Pair("Girls und Panzer", "904"), - Pair("Goblin Slayer", "2143"), - Pair("Gochuumon wa Usagi Desu ka?", "5"), - Pair("Golden Kamuy", "2227"), - Pair("Gotoubun no Hanayome", "2251"), - Pair("Granblue Fantasy", "257"), - Pair("Grisaia no Kajitsu", "6"), - Pair("Guilty Gear", "727"), - Pair("Gundam 00", "857"), - Pair("Gundam Build Divers", "2137"), - Pair("Gundam Build Fighters", "7"), - Pair("Gundam SEED Destiny", "103"), - Pair("Hanasaku Iroha", "1104"), - Pair("Hanayamata", "1947"), - Pair("Hatsujou no Genri", "2139"), - Pair("Hatsukoi Delusion", "1930"), - Pair("Hayate no Gotoku!", "595"), - Pair("He Is My Master", "104"), - Pair("Hentai Ouji to Warawanai Neko.", "8"), - Pair("Hibike! Euphonium", "9"), - Pair("Highschool Dead", "168"), - Pair("Highschool DxD", "105"), - Pair("Highschool of the Dead", "937"), - Pair("Himouto! Umaru-chan", "1039"), - Pair("Hokenshitsu no Shinigami", "1105"), - Pair("Honkai Gakuen", "2165"), - Pair("Hyperdimension Neptunia", "1567"), - Pair("Ichigo 100%", "151"), - Pair("Incesto", "182"), - Pair("Infinite Stratos", "786"), - Pair("Isekai Maou to Shoukan Shoujo no Dorei Majutsu", "2170"), - Pair("K-ON", "150"), - Pair("Kaguya Luna", "2249"), - Pair("Kaguya-sama wa Kokurasetai", "2157"), - Pair("Kaichou wa Maid-sama!", "1129"), - Pair("Kaiten Mutenmaru", "911"), - Pair("Kami Nomi zo Shiru Sekai", "1091"), - Pair("Kämpfer", "1120"), - Pair("Kangoku Gakuen", "1036"), - Pair("Kanon", "65"), - Pair("Kantai Collection: KanColle", "10"), - Pair("Kara no Kyoukai", "731"), - Pair("Kenichi", "96"), - Pair("Keroro Gunsou", "11"), - Pair("Kidou Senshi Gundam 00", "960"), - Pair("Kill la Kill", "1851"), - Pair("Kimetsu no Yaiba", "2149"), - Pair("Kimi ni Todoke", "1127"), - Pair("Kimi no Na wa", "2147"), - Pair("KimiKiss", "1182"), - Pair("King of fighter", "155"), - Pair("King of Fighters", "788"), - Pair("Kiratto Pri Chan", "2182"), - Pair("Kobayashi-san-chi no Maid Dragon", "299"), - Pair("Koihime Musou", "1188"), - Pair("Kono Subarashii Sekai Ni Syukufuku O", "259"), - Pair("Kono Subarashii Sekai ni Syukufuku o!", "853"), - Pair("Konosuba!", "2128"), - Pair("Kurogane no Linebarrels", "1150"), - Pair("Kyoukai Senjou no Horizon", "660"), - Pair("Ladies Versus Butlers", "204"), - Pair("Little Witch Academia", "2127"), - Pair("Love Hina", "94"), - Pair("Love Live Sunshine", "207"), - Pair("Love Live!", "716"), - Pair("Love Live! School Idol Project", "12"), - Pair("Love Plus", "1013"), - Pair("Lucky Star", "893"), - Pair("Macross Frontier", "157"), - Pair("Mahou Sensei Negima", "61"), - Pair("Mahou Sensei Negima!", "1331"), - Pair("Mahou Shoujo Lyrical Nanoha", "13"), - Pair("Mahouka Koukou no Rettousei", "93"), - Pair("Mai-Otome", "2271"), - Pair("Maji de Watashi ni Koi Shinasai!", "1218"), - Pair("Maria-sama ga Miteru", "902"), - Pair("Mayo Chiki!", "137"), - Pair("Medaka Box", "615"), - Pair("Minecraft", "2098"), - Pair("Mirai Nikki", "1138"), - Pair("Mitsuboshi Colors", "2307"), - Pair("Mobile Suit Gundam Tekketsu No Orphans", "297"), - Pair("Mondaiji-tachi ga Isekai Kara Kuru Sou Desu yo?", "1403"), - Pair("Monster Hunter", "1742"), - Pair("Monster Musume no Iru Nichijou", "683"), - Pair("Moyashimon", "1509"), - Pair("Musaigen no Phantom World", "298"), - Pair("Nagi no Asukara", "1830"), - Pair("Nanatsu no Taizai", "2299"), - Pair("Naruto", "14"), - Pair("Nazo no Kanojo X", "1247"), - Pair("Neon Genesis Evangelion", "62"), - Pair("Nisekoi", "246"), - Pair("Nyan Koi!", "978"), - Pair("Oboro Muramasa", "1116"), - Pair("Ojousama to Maid no Midarana Seikatsu", "1931"), - Pair("Okusan", "998"), - Pair("One Piece", "68"), - Pair("One Punch Man", "188"), - Pair("Onegai Teacher", "1076"), - Pair("Ookami to Koushinryou", "958"), - Pair("Ookami-san to Shichinin no Nakama-tachi", "1244"), - Pair("Ore no Imouto ga Konna ni Kawaii Wake ga Nai", "328"), - Pair("Original", "15"), - Pair("Oshiete! Galko-chan", "2136"), - Pair("Overlord", "686"), - Pair("Overwatch", "260"), - Pair("Panty & Stocking with Garterbelt", "1176"), - Pair("Papa no Iu Koto o Kikinasai!", "976"), - Pair("Parasyte", "1911"), - Pair("Persona 3", "255"), - Pair("Persona 4", "953"), - Pair("Persona 5", "2265"), - Pair("Pokemon", "148"), - Pair("Princess crown", "983"), - Pair("Princess Lover!", "1175"), - Pair("Prison School", "164"), - Pair("Psycho-Pass", "2275"), - Pair("Puella Magi Madoka Magica", "948"), - Pair("Queen’s Blade", "656"), - Pair("Ragnarok Online", "1243"), - Pair("Rakudai Kishi no Cavalry", "765"), - Pair("Rakuen Tsuihou -Expelled from Paradise-", "1045"), - Pair("Ranma 1/2", "767"), - Pair("Re:Zero kara Hajimeru Isekai Seikatsu", "161"), - Pair("Real Drive", "261"), - Pair("Rebuild of evangelion", "149"), - Pair("Renkin San-kyuu Magical? Pokaan", "737"), - Pair("Resident Evil", "1118"), - Pair("Rockman DASH", "1242"), - Pair("Rokka no Yuusha", "1600"), - Pair("Rosario + Vampire", "1204"), - Pair("Rosario Vampire", "234"), - Pair("Rozen Maiden", "1240"), - Pair("Rurouni Kenshin", "1520"), - Pair("Saber Marionette", "73"), - Pair("Saenai Heroine no Sodatekata", "926"), - Pair("Sailor Moon", "186"), - Pair("Saint Seiya", "66"), - Pair("Saki", "258"), - Pair("School Rumble", "191"), - Pair("Sekirei", "180"), - Pair("Sekiro: Shadows Die Twice", "2152"), - Pair("Serial Experiments Lain", "1498"), - Pair("Seto No Hanayome", "219"), - Pair("Shadowverse", "1957"), - Pair("Shantae", "1050"), - Pair("Shijou Saikyou no Deshi Kenichi", "1144"), - Pair("Shingeki no Kyojin", "169"), - Pair("Shingetsutan Tsukihime", "16"), - Pair("Shinmai Fukei Kiruko-san", "2207"), - Pair("Shinmai Maou no Testament", "17"), - Pair("Shinra Bansho", "18"), - Pair("Shinrabansho", "2063"), - Pair("Shinrabanshou", "1979"), - Pair("Shinryaku! Ika Musume", "1160"), - Pair("shirokuma cafe", "2074"), - Pair("Shitsuke Ai", "1630"), - Pair("Shokugeki no Soma", "152"), - Pair("Smile Precure", "858"), - Pair("Smile PreCure!", "1841"), - Pair("SNK", "252"), - Pair("Sora no Otoshimono", "20"), - Pair("Soul Eater", "158"), - Pair("Space Dandy", "1826"), - Pair("Spice and wolf", "154"), - Pair("Spider-man", "2140"), - Pair("Star Twinkle PreCure", "2310"), - Pair("Steins;Gate", "992"), - Pair("Street Fighter", "101"), - Pair("Strike Witches", "642"), - Pair("Subarashii Sekai ni Shukufuku wo!", "739"), - Pair("Suite Precure♪", "145"), - Pair("Super Sonico", "1102"), - Pair("Super Street Fighter IV", "802"), - Pair("Suzumiya Haruhi No Yuuutsu", "314"), - Pair("Sword art online", "147"), - Pair("taimanin asagi", "1869"), - Pair("Taimanin Yukikaze", "826"), - Pair("Tales of the Abyss", "1148"), - Pair("Tamako Market", "770"), - Pair("Tate no Yuusha no Nariagari", "2184"), - Pair("Teen Titans", "21"), - Pair("Tengen Toppa Gurren Lagann", "1190"), - Pair("Tengen Toppa Gurren-Lagann", "100"), - Pair("Tera", "2215"), - Pair("TERA The Exiled Realm of Arborea", "2214"), - Pair("Terra Formars", "2073"), - Pair("The iDOLM@STER", "22"), - Pair("the loud house", "2054"), - Pair("The OneChanbara", "1053"), - Pair("The Seven Deadly Sins", "620"), - Pair("To Love-Ru", "23"), - Pair("To Love-Ru Darkness", "1135"), - Pair("Toaru Kagaku no Railgun", "769"), - Pair("Toaru Majutsu no Index", "741"), - Pair("ToHeart", "1164"), - Pair("ToHeart2", "905"), - Pair("ToHeart2 AnotherDays", "97"), - Pair("Tokyo 7th Sisters", "762"), - Pair("Tokyo Ghoul", "864"), - Pair("Tonari no Seki-kun", "1768"), - Pair("Toradora", "192"), - Pair("Toradora!", "1152"), - Pair("Touhou Project", "24"), - Pair("Traducción Exclusiva", "2285"), - Pair("Tsujou Kougeki ga Zentai Kougeki de 2-kai Kougeki no Okaasan wa Suki desu ka?", "2131"), - Pair("Tsukihime", "877"), - Pair("Uchuu no Stellvia", "715"), - Pair("Utawarerumono", "1905"), - Pair("Uzaki-chan wa Asobitai!", "2146"), - Pair("Valkyria Chronicles", "1084"), - Pair("Vampire savior", "982"), - Pair("Vocaloid", "912"), - Pair("Watashi ga Motenai no wa Dou Kangaetemo Omaera ga Warui!", "1032"), - Pair("Witch Craft Works", "552"), - Pair("Witchblade", "1302"), - Pair("Wizard of Oz", "723"), - Pair("Yahari Ore no Seishun Love Come wa Machigatteiru", "967"), - Pair("Yakitate!! Japan", "1409"), - Pair("yatterman", "984"), - Pair("Yotsubato!", "1429"), - Pair("Yu-Gi-Oh! ZEXAL", "196"), - Pair("Yuru Yuri", "25"), - Pair("YuruYuri", "894"), - Pair("Zegapain", "1330"), - Pair("Zero no Tsukaima", "26"), - Pair("Zettai Junpaku Mahou Shoujo", "1918"), - Pair("Zettai Karen Children", "1417"), - Pair("Zoids Shinseiki Zero", "153"), - Pair("Zombieland Saga", "2059") - ) - ) -} - -open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second -} diff --git a/src/fr/japanread/AndroidManifest.xml b/src/fr/japanread/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/japanread/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/japanread/build.gradle b/src/fr/japanread/build.gradle deleted file mode 100644 index 2e5ec170d..000000000 --- a/src/fr/japanread/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Japanread' - pkgNameSuffix = 'fr.japanread' - extClass = '.Japanread' - extVersionCode = 4 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/japanread/res/mipmap-hdpi/ic_launcher.png b/src/fr/japanread/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 021a2a031..000000000 Binary files a/src/fr/japanread/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-hdpi/ic_launcher_foreground.png b/src/fr/japanread/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 60143d9d7..000000000 Binary files a/src/fr/japanread/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-hdpi/ic_launcher_round.png b/src/fr/japanread/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index f6c54bcf5..000000000 Binary files a/src/fr/japanread/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-mdpi/ic_launcher.png b/src/fr/japanread/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 03303c96f..000000000 Binary files a/src/fr/japanread/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-mdpi/ic_launcher_foreground.png b/src/fr/japanread/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 237957075..000000000 Binary files a/src/fr/japanread/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-mdpi/ic_launcher_round.png b/src/fr/japanread/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 9b514fcd2..000000000 Binary files a/src/fr/japanread/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xhdpi/ic_launcher.png b/src/fr/japanread/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f93ba59e7..000000000 Binary files a/src/fr/japanread/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xhdpi/ic_launcher_foreground.png b/src/fr/japanread/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 6ffa5de70..000000000 Binary files a/src/fr/japanread/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xhdpi/ic_launcher_round.png b/src/fr/japanread/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index f1ce9445f..000000000 Binary files a/src/fr/japanread/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6d9baade0..000000000 Binary files a/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index f87c30f4a..000000000 Binary files a/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher_round.png b/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 5b6cdc3cb..000000000 Binary files a/src/fr/japanread/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f63441b3f..000000000 Binary files a/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 74a48dabd..000000000 Binary files a/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher_round.png b/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 73bb3336e..000000000 Binary files a/src/fr/japanread/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src/fr/japanread/src/eu/kanade/tachiyomi/extension/fr/japanread/Japanread.kt b/src/fr/japanread/src/eu/kanade/tachiyomi/extension/fr/japanread/Japanread.kt deleted file mode 100644 index 9be4cc854..000000000 --- a/src/fr/japanread/src/eu/kanade/tachiyomi/extension/fr/japanread/Japanread.kt +++ /dev/null @@ -1,202 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.japanread - -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.Calendar - -class Japanread : ParsedHttpSource() { - - override val name = "Japanread" - - override val baseUrl = "https://www.japanread.cc" - - override val lang = "fr" - - override val supportsLatest = true - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun popularMangaSelector() = "#nav-tabContent #nav-home li" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("p a").text() - setUrlWithoutDomain(element.select("p a").attr("href")) - thumbnail_url = element.select("img").attr("src").replace("manga_small", "manga_large") - } - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/?page=$page", headers) - } - - override fun latestUpdatesSelector() = "section.main-content > .container > .row > .col-lg-9 tbody > tr > td[rowspan]" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.nextElementSibling().nextElementSibling().select("a").text() - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = element.select("img").attr("src").replace("manga_medium", "manga_large") - } - } - - override fun latestUpdatesNextPageSelector() = "a[rel=\"next\"]" - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&page=$page") - } - - override fun searchMangaSelector() = "#manga-container > div > div.col-lg-6" - - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("div.text-truncate a").text() - setUrlWithoutDomain(element.select("div.text-truncate a").attr("href")) - description = element.select("div.text-muted").text() - thumbnail_url = element.select("img").attr("src").replace("manga_medium", "manga_large") - } - } - - override fun searchMangaNextPageSelector() = "a[rel=\"next\"]" - - // Details - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - title = document.select("h1.card-header").text() - artist = document.select("div.col-lg-3:contains(Artiste) + div").text() - author = document.select("div.col-lg-3:contains(Auteur) + div").text() - description = document.select("div.col-lg-3:contains(Description) + div").text() - genre = document.select("div.col-lg-3:contains(Type - Catégories) + div .badge").joinToString { it.text() } - status = document.select("div.col-lg-3:contains(Statut) + div").text().let { - when { - it.contains("En cours") -> SManga.ONGOING - it.contains("Terminé") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - thumbnail_url = document.select("img[alt=\"couverture manga\"]").attr("src") - } - } - - private fun apiHeaders() = headersBuilder().apply { - add("Referer", baseUrl) - add("x-requested-with", "XMLHttpRequest") - }.build() - - // Chapters - // Subtract relative date - private fun parseRelativeDate(date: String): Long { - val trimmedDate = date.substringAfter("Il y a").trim().split(" ") - - val calendar = Calendar.getInstance() - when (trimmedDate[1]) { - "ans" -> calendar.apply { add(Calendar.YEAR, -trimmedDate[0].toInt()) } - "an" -> calendar.apply { add(Calendar.YEAR, -trimmedDate[0].toInt()) } - "mois" -> calendar.apply { add(Calendar.MONTH, -trimmedDate[0].toInt()) } - "sem." -> calendar.apply { add(Calendar.WEEK_OF_MONTH, -trimmedDate[0].toInt()) } - "j" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) } - "h" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) } - "min" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) } - "s" -> calendar.apply { add(Calendar.SECOND, 0) } - } - - return calendar.timeInMillis - } - - override fun chapterListSelector() = "#chapters div[data-row=\"chapter\"]" - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - name = element.select("div.col-lg-5 a").text() - setUrlWithoutDomain(element.select("div.col-lg-5 a").attr("href")) - date_upload = parseRelativeDate(element.select("div.order-lg-8").text()) - scanlator = element.select(".chapter-list-group a").joinToString { it.text() } - } - } - - // Alternative way through API in case jSoup doesn't work anymore - // It gives precise timestamp, but we are not using it - // since the API wrongly returns null for the scanlation group - /*private fun getChapterName(jsonElement: JsonElement): String { - var name = "" - - if (jsonElement["volume"].asString != "") { - name += "Tome " + jsonElement["volume"].asString + " " - } - if (jsonElement["chapter"].asString != "") { - name += "Ch " + jsonElement["chapter"].asString + " " - } - - if (jsonElement["title"].asString != "") { - if (name != "") { - name += " - " - } - name += jsonElement["title"].asString - } - - return name - } - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val mangaId = document.select("div[data-avg]").attr("data-avg") - - client.newCall(GET(baseUrl + document.select("#chapters div[data-row=chapter]").first().select("div.col-lg-5 a").attr("href"), headers)).execute() - - val apiResponse = client.newCall(GET("$baseUrl/api/?id=$mangaId&type=manga", apiHeaders())).execute() - - val jsonData = apiResponse.body()!!.string() - val json = JsonParser().parse(jsonData).asJsonObject - - return json["chapter"].obj.entrySet() - .map { - SChapter.create().apply { - name = getChapterName(it.value.obj) - url = "$baseUrl/api/?id=${it.key}&type=chapter" - date_upload = it.value.obj["timestamp"].asLong * 1000 - // scanlator = element.select(".chapter-list-group a").joinToString { it.text() } - } - } - .sortedByDescending { it.date_upload } - } - override fun chapterListSelector() = throw UnsupportedOperationException("Not Used") - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not Used")*/ - - // Pages - override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl${chapter.url}", headers) - - override fun pageListParse(document: Document): List<Page> { - val chapterId = document.select("meta[data-chapter-id]").attr("data-chapter-id") - - val apiResponse = client.newCall(GET("$baseUrl/api/?id=$chapterId&type=chapter", apiHeaders())).execute() - - val jsonData = apiResponse.body()!!.string() - val json = JsonParser().parse(jsonData).asJsonObject - - val baseImagesUrl = json["baseImagesUrl"].string - - return json["page_array"].asJsonArray.mapIndexed { idx, it -> - val imgUrl = "$baseUrl$baseImagesUrl/${it.asString}" - Page(idx, baseUrl, imgUrl) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - override fun imageRequest(page: Page): Request { - return GET(page.imageUrl!!, headers) - } -} diff --git a/src/fr/japscan/AndroidManifest.xml b/src/fr/japscan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/japscan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/japscan/build.gradle b/src/fr/japscan/build.gradle deleted file mode 100644 index ee6b84753..000000000 --- a/src/fr/japscan/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Japscan' - pkgNameSuffix = 'fr.japscan' - extClass = '.Japscan' - extVersionCode = 26 - libVersion = '1.2' -} - -dependencies { - implementation 'org.apache.commons:commons-lang3:3.8.1' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/japscan/res/mipmap-hdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4659fbb8c..000000000 Binary files a/src/fr/japscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-mdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 19a5531b0..000000000 Binary files a/src/fr/japscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-xhdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7320f09eb..000000000 Binary files a/src/fr/japscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 52e248fa6..000000000 Binary files a/src/fr/japscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/japscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 166cd3cfe..000000000 Binary files a/src/fr/japscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/japscan/res/web_hi_res_512.png b/src/fr/japscan/res/web_hi_res_512.png deleted file mode 100644 index 3af8c2771..000000000 Binary files a/src/fr/japscan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt b/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt deleted file mode 100644 index 18304d93b..000000000 --- a/src/fr/japscan/src/eu/kanade/tachiyomi/extension/fr/japscan/Japscan.kt +++ /dev/null @@ -1,492 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.japscan - -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.Application -import android.content.SharedPreferences -import android.graphics.Bitmap -import android.graphics.Canvas -import android.net.Uri -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import android.util.Log -import android.view.View -import android.webkit.JavascriptInterface -import android.webkit.WebChromeClient -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.CountDownLatch -import java.util.concurrent.CyclicBarrier -import kotlin.collections.ArrayList -import kotlin.collections.List -import kotlin.collections.distinctBy -import kotlin.collections.filter -import kotlin.collections.first -import kotlin.collections.forEach -import kotlin.collections.forEachIndexed -import kotlin.collections.map -import kotlin.collections.mutableListOf -import kotlin.collections.toTypedArray - -class Japscan : ConfigurableSource, ParsedHttpSource() { - - override val id: Long = 11 - - override val name = "Japscan" - - override val baseUrl = "https://www.japscan.se" - - override val lang = "fr" - - override val supportsLatest = true - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - internal class JsObject(private val latch: CountDownLatch, var width: Int = 0, var height: Int = 0) { - @JavascriptInterface - fun passSize(widthjs: Int, ratio: Float) { - Log.d("japscan", "wvsc js returned $widthjs, $ratio") - width = widthjs - height = (width.toFloat() / ratio).toInt() - latch.countDown() - } - } - - @SuppressLint("SetJavaScriptEnabled") - override val client: OkHttpClient = network.cloudflareClient.newBuilder().addInterceptor { chain -> - val indicator = "&wvsc" - val cleanupjs = "var checkExist=setInterval(function(){if(document.getElementsByTagName('CNV-VV').length){clearInterval(checkExist);var e=document.body,a=e.children;for(e.appendChild(document.getElementsByTagName('CNV-VV')[0]);'CNV-VV'!=a[0].tagName;)e.removeChild(a[0]);for(var t of[].slice.call(a[0].all_canvas))t.style.maxWidth='100%';window.android.passSize(a[0].all_canvas[0].width,a[0].all_canvas[0].width/a[0].all_canvas[0].height)}},100);" - val request = chain.request() - val url = request.url().toString() - - val newRequest = request.newBuilder() - .url(url.substringBefore(indicator)) - .build() - val response = chain.proceed(newRequest) - if (!url.endsWith(indicator)) return@addInterceptor response - // Webview screenshotting code - val handler = Handler(Looper.getMainLooper()) - val latch = CountDownLatch(1) - var webView: WebView? = null - var height = 0 - var width = 0 - val jsinterface = JsObject(latch) - Log.d("japscan", "init wvsc") - handler.post { - val webview = WebView(Injekt.get<Application>()) - webView = webview - webview.settings.javaScriptEnabled = true - webview.settings.domStorageEnabled = true - webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null) - webview.settings.useWideViewPort = false - webview.settings.loadWithOverviewMode = false - webview.settings.userAgentString = webview.settings.userAgentString.replace("Mobile", "eliboM").replace("Android", "diordnA") - webview.addJavascriptInterface(jsinterface, "android") - var retries = 1 - webview.webChromeClient = object : WebChromeClient() { - @SuppressLint("NewApi") - override fun onProgressChanged(view: WebView, progress: Int) { - if (progress == 100 && retries--> 0) { - Log.d("japscan", "wvsc loading finished") - view.evaluateJavascript(cleanupjs) {} - } - } - } - webview.loadUrl(url.replace("&wvsc", "")) - } - - latch.await() - width = jsinterface.width - height = jsinterface.height - // webView!!.isDrawingCacheEnabled = true - - webView!!.measure(width, height) - webView!!.layout(0, 0, width, height) - Thread.sleep(350) - - val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - var canvas = Canvas(bitmap) - webView!!.draw(canvas) - - // val bitmap: Bitmap = webView!!.drawingCache - val output = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.PNG, 100, output) - val rb = ResponseBody.create(MediaType.parse("image/png"), output.toByteArray()) - handler.post { webView!!.destroy() } - response.newBuilder().body(rb).build() - }.build() - - companion object { - val dateFormat by lazy { - SimpleDateFormat("dd MMM yyyy", Locale.US) - } - private const val SHOW_SPOILER_CHAPTERS_Title = "Les chapitres en Anglais ou non traduit sont upload en tant que \" Spoilers \" sur Japscan" - private const val SHOW_SPOILER_CHAPTERS = "JAPSCAN_SPOILER_CHAPTERS" - private val prefsEntries = arrayOf("Montrer uniquement les chapitres traduit en Français", "Montrer les chapitres spoiler") - private val prefsEntryValues = arrayOf("hide", "show") - } - - private fun chapterListPref() = preferences.getString(SHOW_SPOILER_CHAPTERS, "hide") - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/mangas/", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - pageNumberDoc = document - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - val hasNextPage = false - return MangasPage(mangas, hasNextPage) - } - - override fun popularMangaNextPageSelector(): String? = null - override fun popularMangaSelector() = "#top_mangas_week li > span" - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - manga.thumbnail_url = "$baseUrl/imgs/${it.attr("href").replace(Regex("/$"),".jpg").replace("manga","mangas")}".toLowerCase(Locale.ROOT) - } - return manga - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()) - .distinctBy { element -> element.select("a").attr("href") } - .map { element -> - latestUpdatesFromElement(element) - } - val hasNextPage = false - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesNextPageSelector(): String? = null - override fun latestUpdatesSelector() = "#chapters > div > h3.text-truncate" - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - private val gson = Gson() - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isEmpty()) { - val uri = Uri.parse(baseUrl).buildUpon() - .appendPath("mangas") - filters.forEach { filter -> - when (filter) { - is TextField -> uri.appendPath(((page - 1) + filter.state.toInt()).toString()) - is PageList -> uri.appendPath(((page - 1) + filter.values[filter.state]).toString()) - } - } - return GET(uri.toString(), headers) - } else { - val formBody = FormBody.Builder() - .add("search", query) - .build() - val searchHeaders = headers.newBuilder() - .add("X-Requested-With", "XMLHttpRequest") - .build() - - try { - return client.newCall(POST("$baseUrl/live-search/", searchHeaders, formBody)).execute().use { response -> - if (!response.isSuccessful) throw Exception("Unexpected code $response") - - val jsonObject = gson.fromJson<JsonObject>(response.body()!!.string()) - - if (jsonObject.asJsonArray.size() === 0) { - Log.d("japscan", "Search not returning anything, using duckduckgo") - - throw Exception("No data") - } - - return response.request() - } - } finally { - // Fallback to duckduckgo if the search does not return any result - val uri = Uri.parse("https://duckduckgo.com/lite/").buildUpon() - .appendQueryParameter("q", "$query site:$baseUrl/manga/") - .appendQueryParameter("kd", "-1") - return GET(uri.toString(), headers) - } - } - } - - override fun searchMangaNextPageSelector(): String? = "li.page-item:last-child:not(li.active),.next_form .navbutton" - override fun searchMangaSelector(): String = "div.card div.p-2, a.result-link" - override fun searchMangaParse(response: Response): MangasPage { - if ("live-search" in response.request().url().toString()) { - val body = response.body()!!.string() - val json = JsonParser().parse(body).asJsonArray - val mangas = json.map { jsonElement -> - searchMangaFromJson(jsonElement) - } - - val hasNextPage = false - - return MangasPage(mangas, hasNextPage) - } else { - val document = response.asJsoup() - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - } - - override fun searchMangaFromElement(element: Element): SManga { - if (element.attr("class") == "result-link") { - return SManga.create().apply { - title = element.text().substringAfter(" ").substringBefore(" | JapScan") - setUrlWithoutDomain(element.attr("abs:href")) - } - } else { - return SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:src") - element.select("p a").let { - title = it.text() - url = it.attr("href") - } - } - } - } - - private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply { - title = jsonElement["name"].string - url = jsonElement["url"].string - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div#main > .card > .card-body").first() - - val manga = SManga.create() - manga.thumbnail_url = "$baseUrl/${infoElement.select(".d-flex > div.m-2:eq(0) > img").attr("src")}" - - infoElement.select(".d-flex > div.m-2:eq(1) > p.mb-2").forEachIndexed { _, el -> - when (el.select("span").text().trim()) { - "Auteur(s):" -> manga.author = el.text().replace("Auteur(s):", "").trim() - "Artiste(s):" -> manga.artist = el.text().replace("Artiste(s):", "").trim() - "Genre(s):" -> manga.genre = el.text().replace("Genre(s):", "").trim() - "Statut:" -> manga.status = el.text().replace("Statut:", "").trim().let { - parseStatus(it) - } - } - } - manga.description = infoElement.select("> p").text().orEmpty() - - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("En Cours") -> SManga.ONGOING - status.contains("Terminé") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "#chapters_list > div.collapse > div.chapters_list" + - if (chapterListPref() == "hide") { ":not(:has(.badge:contains(SPOILER),.badge:contains(RAW),.badge:contains(VUS)))" } else { "" } - // JapScan sometimes uploads some "spoiler preview" chapters, containing 2 or 3 untranslated pictures taken from a raw. Sometimes they also upload full RAWs/US versions and replace them with a translation as soon as available. - // Those have a span.badge "SPOILER" or "RAW". The additional pseudo selector makes sure to exclude these from the chapter list. - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.ownText() - // Using ownText() doesn't include childs' text, like "VUS" or "RAW" badges, in the chapter name. - chapter.date_upload = element.select("> span").text().trim().let { parseChapterDate(it) } - return chapter - } - - private fun parseChapterDate(date: String): Long { - return try { - dateFormat.parse(date)?.time ?: 0 - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(document: Document): List<Page> { - // no webview screenshot needed anymore : - // document.getElementsByTag("option").mapIndexed { i, it -> Page(i, "", baseUrl + it.attr("value") + "&wvsc") } - - val zjsurl = document.getElementsByTag("script").first { it.attr("src").contains("zjs", ignoreCase = true) }.attr("src") - Log.d("japscan", "ZJS at $zjsurl") - val zjs = client.newCall(GET(baseUrl + zjsurl, headers)).execute().body()!!.string() - Log.d("japscan", "webtoon, netdumping initiated") - val pagecount = document.getElementsByTag("option").size - val pages = ArrayList<Page>() - val handler = Handler(Looper.getMainLooper()) - val checkNew = ArrayList<String>(pagecount) - var maxIter = document.getElementsByTag("option").size - var isSinglePage = false - if ((zjs.toLowerCase().split("new image").size - 1) == 1) { - isSinglePage = true - maxIter = 1 - } - var webView: WebView? = null - val dummyimage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - val dummystream = ByteArrayOutputStream() - dummyimage.compress(Bitmap.CompressFormat.JPEG, 100, dummystream) - val barrier = CyclicBarrier(2) - - for (i in 0 until maxIter) { - handler.post { - if (webView == null) { - val webview = WebView(Injekt.get<Application>()) - webView = webview - webview.settings.javaScriptEnabled = true - webview.settings.domStorageEnabled = true - webview.settings.userAgentString = webview.settings.userAgentString.replace("Mobile", "eliboM").replace("Android", "diordnA") - webview.webViewClient = object : WebViewClient() { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - override fun shouldInterceptRequest( - view: WebView, - request: WebResourceRequest - ): WebResourceResponse? { - if (request.url.toString().startsWith("https://cdn.statically.io/img/c.japscan.se/") && !checkNew.contains(request.url.toString())) { - checkNew.add(request.url.toString()) - pages.add(Page(pages.size, "", request.url.toString())) - Log.d("japscan", "intercepted ${request.url}") - if (pages.size == pagecount || !isSinglePage) { - barrier.await() - } - return WebResourceResponse("image/jpeg", "UTF-8", ByteArrayInputStream(dummystream.toByteArray())) - } - return super.shouldInterceptRequest(view, request) - } - } - } - if (isSinglePage) { - webView?.loadUrl(baseUrl + document.select("li[^data-]").first().dataset()["chapter-url"]) - } else { - webView?.loadUrl(baseUrl + document.getElementsByTag("option")[i].attr("value")) - } - } - barrier.await() - } - handler.post { webView!!.destroy() } - return pages - } - - override fun imageUrlParse(document: Document): String = "" - - // Filters - private class TextField(name: String) : Filter.Text(name) - - private class PageList(pages: Array<Int>) : Filter.Select<Int>("Page #", arrayOf(0, *pages)) - - override fun getFilterList(): FilterList { - val totalPages = pageNumberDoc?.select("li.page-item:last-child a")?.text() - val pagelist = mutableListOf<Int>() - return if (!totalPages.isNullOrEmpty()) { - for (i in 0 until totalPages.toInt()) { - pagelist.add(i + 1) - } - FilterList( - Filter.Header("Page alphabétique"), - PageList(pagelist.toTypedArray()) - ) - } else FilterList( - Filter.Header("Page alphabétique"), - TextField("Page #"), - Filter.Header("Appuyez sur reset pour la liste") - ) - } - - private var pageNumberDoc: Document? = null - - // Prefs - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val chapterListPref = androidx.preference.ListPreference(screen.context).apply { - key = SHOW_SPOILER_CHAPTERS_Title - title = SHOW_SPOILER_CHAPTERS_Title - entries = prefsEntries - entryValues = prefsEntryValues - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SHOW_SPOILER_CHAPTERS, entry).commit() - } - } - screen.addPreference(chapterListPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val chapterListPref = ListPreference(screen.context).apply { - key = SHOW_SPOILER_CHAPTERS_Title - title = SHOW_SPOILER_CHAPTERS_Title - entries = prefsEntries - entryValues = prefsEntryValues - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = this.findIndexOfValue(selected) - val entry = entryValues[index] as String - preferences.edit().putString(SHOW_SPOILER_CHAPTERS, entry).commit() - } - } - screen.addPreference(chapterListPref) - } -} diff --git a/src/fr/kangaryu/AndroidManifest.xml b/src/fr/kangaryu/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/kangaryu/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/kangaryu/build.gradle b/src/fr/kangaryu/build.gradle deleted file mode 100644 index babda2aa9..000000000 --- a/src/fr/kangaryu/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Kangaryu' - pkgNameSuffix = 'fr.kangaryu' - extClass = '.Kangaryu' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/kangaryu/res/mipmap-hdpi/ic_launcher.png b/src/fr/kangaryu/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4d40f2e4e..000000000 Binary files a/src/fr/kangaryu/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/kangaryu/res/mipmap-mdpi/ic_launcher.png b/src/fr/kangaryu/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7e72a1463..000000000 Binary files a/src/fr/kangaryu/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/kangaryu/res/mipmap-xhdpi/ic_launcher.png b/src/fr/kangaryu/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f143706e0..000000000 Binary files a/src/fr/kangaryu/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/kangaryu/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/kangaryu/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e08bb9f80..000000000 Binary files a/src/fr/kangaryu/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/kangaryu/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/kangaryu/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f8da38822..000000000 Binary files a/src/fr/kangaryu/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/kangaryu/res/web_hi_res_512.png b/src/fr/kangaryu/res/web_hi_res_512.png deleted file mode 100644 index 37e5542f8..000000000 Binary files a/src/fr/kangaryu/res/web_hi_res_512.png and /dev/null differ diff --git a/src/fr/kangaryu/src/eu/kanade/tachiyomi/extension/fr/kangaryu/Kangaryu.kt b/src/fr/kangaryu/src/eu/kanade/tachiyomi/extension/fr/kangaryu/Kangaryu.kt deleted file mode 100644 index 52be60e49..000000000 --- a/src/fr/kangaryu/src/eu/kanade/tachiyomi/extension/fr/kangaryu/Kangaryu.kt +++ /dev/null @@ -1,153 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.kangaryu - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonObject -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Kangaryu : ParsedHttpSource() { - - override val name = "Kangaryu" - - override val baseUrl = "https://kangaryu-team.fr" - - override val lang = "fr" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // used to be in FoolSlide - override val versionId = 2 - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/manga-list", headers) - } - - override fun popularMangaSelector() = "div.profile-card-2" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.profile-name a").let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - return MangasPage(super.latestUpdatesParse(response).mangas.distinctBy { it.url }, false) - } - - override fun latestUpdatesSelector() = "div.events" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.manga-chap a").let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?query=$query", headers) - } - - private val gson by lazy { Gson() } - - override fun searchMangaParse(response: Response): MangasPage { - val mangas = gson.fromJson<JsonObject>(response.body()!!.string())["suggestions"].array.map { json -> - SManga.create().apply { - url = "/manga/${json["data"].string}" - title = json["value"].string - thumbnail_url = "https://kangaryu-team.fr/uploads/manga/${json["data"].string}/cover/cover_250x350.jpg" - } - } - return MangasPage(mangas, false) - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - thumbnail_url = document.select("div.boxed img").attr("abs:src") - with(document.select("div.col-sm-12 div.col-sm-8")) { - status = select("span.label").text().toStatus() - author = select("dd a[href*=author]").text() - artist = select("dd a[href*=artist]").text() - genre = select("dd a[href*=category]").joinToString { it.text() } - } - } - } - - private fun String.toStatus() = when { - this.contains("Ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("Completed", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "ul.chapters li:not([data-volume])" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - name = element.select("h5").text() - setUrlWithoutDomain(element.select("h5 a").attr("href")) - date_upload = element.select("div.date-chapter-title-rtl").text().toDate() - } - } - - private val dateFormat by lazy { SimpleDateFormat("dd/MM/yy", Locale.getDefault()) } - - private fun String.toDate(): Long { - return dateFormat.parse(this)?.time ?: 0 - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div#all img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:data-src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/fr/lirescan/AndroidManifest.xml b/src/fr/lirescan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/lirescan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/lirescan/build.gradle b/src/fr/lirescan/build.gradle deleted file mode 100644 index 19cabee93..000000000 --- a/src/fr/lirescan/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'LireScan' - pkgNameSuffix = 'fr.lirescan' - extClass = '.LireScan' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/lirescan/res/mipmap-hdpi/ic_launcher.png b/src/fr/lirescan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index dec26b74d..000000000 Binary files a/src/fr/lirescan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/lirescan/res/mipmap-mdpi/ic_launcher.png b/src/fr/lirescan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fd180aa9f..000000000 Binary files a/src/fr/lirescan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/lirescan/res/mipmap-xhdpi/ic_launcher.png b/src/fr/lirescan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 45cd995a8..000000000 Binary files a/src/fr/lirescan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/lirescan/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/lirescan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6d663182e..000000000 Binary files a/src/fr/lirescan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/lirescan/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/lirescan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 10ca1630a..000000000 Binary files a/src/fr/lirescan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/lirescan/res/web_hi_res_512.png b/src/fr/lirescan/res/web_hi_res_512.png deleted file mode 100644 index e3d15553c..000000000 Binary files a/src/fr/lirescan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/fr/lirescan/src/eu/kanade/tachiyomi/extension/fr/lirescan/LireScan.kt b/src/fr/lirescan/src/eu/kanade/tachiyomi/extension/fr/lirescan/LireScan.kt deleted file mode 100644 index 2674508c6..000000000 --- a/src/fr/lirescan/src/eu/kanade/tachiyomi/extension/fr/lirescan/LireScan.kt +++ /dev/null @@ -1,129 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.lirescan - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class LireScan : ParsedHttpSource() { - - override val name = "LireScan" - - override val baseUrl = "https://www.lirescan.me" - - override val lang = "fr" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - // There's no proper directory, get list of manga from dropdown menu available from a manga's page - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/090-eko-to-issho-lecture-en-ligne/", headers) - } - - override fun popularMangaSelector() = "div.form-group select option" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.text() - url = element.attr("value") - } - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesSelector() = "ul#releases > h4" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.text() - url = element.nextElementSibling().select("a").first().attr("href") - .removeSuffix("/").dropLastWhile { it.isDigit() } - } - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return popularMangaRequest(1) - } - - private fun searchMangaParse(response: Response, query: String): MangasPage { - val mangas = response.asJsoup().select(popularMangaSelector()).filter { it.text().contains(query, ignoreCase = true) } - .map { searchMangaFromElement(it) } - - return MangasPage(mangas, false) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - thumbnail_url = document.select("a#imglink img").attr("abs:src") - } - } - - // Chapters - - override fun chapterListSelector() = "select#chapitres option" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.text().let { chNum -> - name = "Chapter $chNum" - setUrlWithoutDomain("${element.ownerDocument().location()}$chNum/") - } - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val lastPage = document.select("ul.pagination li.page-item:contains(Suiv)").first() - .previousElementSibling().text().toInt() - - return IntRange(1, lastPage).map { num -> Page(num - 1, document.location() + num) } - } - - override fun imageUrlParse(document: Document): String { - return document.select("a#imglink img").attr("abs:src") - } - - override fun getFilterList() = FilterList() -} diff --git a/src/fr/mangakawaii/AndroidManifest.xml b/src/fr/mangakawaii/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/mangakawaii/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/mangakawaii/build.gradle b/src/fr/mangakawaii/build.gradle deleted file mode 100644 index aab7c8aa8..000000000 --- a/src/fr/mangakawaii/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangakawaii' - pkgNameSuffix = 'fr.mangakawaii' - extClass = '.MangaKawaii' - extVersionCode = 30 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/mangakawaii/res/mipmap-hdpi/ic_launcher.png b/src/fr/mangakawaii/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index dd1ca4771..000000000 Binary files a/src/fr/mangakawaii/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/mangakawaii/res/mipmap-mdpi/ic_launcher.png b/src/fr/mangakawaii/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d4a0fee5b..000000000 Binary files a/src/fr/mangakawaii/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/mangakawaii/res/mipmap-xhdpi/ic_launcher.png b/src/fr/mangakawaii/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 47120d788..000000000 Binary files a/src/fr/mangakawaii/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/mangakawaii/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/mangakawaii/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index edd38305d..000000000 Binary files a/src/fr/mangakawaii/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/mangakawaii/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/mangakawaii/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1d7aaee7f..000000000 Binary files a/src/fr/mangakawaii/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/mangakawaii/res/web_hi_res_512.png b/src/fr/mangakawaii/res/web_hi_res_512.png deleted file mode 100644 index 77461e3f7..000000000 Binary files a/src/fr/mangakawaii/res/web_hi_res_512.png and /dev/null differ diff --git a/src/fr/mangakawaii/src/eu/kanade/tachiyomi/extension/fr/mangakawaii/MangaKawaii.kt b/src/fr/mangakawaii/src/eu/kanade/tachiyomi/extension/fr/mangakawaii/MangaKawaii.kt deleted file mode 100644 index f83c1bcd4..000000000 --- a/src/fr/mangakawaii/src/eu/kanade/tachiyomi/extension/fr/mangakawaii/MangaKawaii.kt +++ /dev/null @@ -1,159 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.mangakawaii - -import android.net.Uri -import android.util.Base64 -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit -import kotlin.math.absoluteValue -import kotlin.random.Random - -/** - * Heavily customized MyMangaReaderCMS source - */ -class MangaKawaii : ParsedHttpSource() { - - override val name = "Mangakawaii" - override val baseUrl = "https://www.mangakawaii.com" - val cdnUrl = "https://cdn.mangakawaii.com" - override val lang = "fr" - override val supportsLatest = true - private val rateLimitInterceptor = RateLimitInterceptor(1) // 1 request per second - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addNetworkInterceptor(rateLimitInterceptor) - .build() - - protected open val userAgentRandomizer1 = "${Random.nextInt(9).absoluteValue}" - protected open val userAgentRandomizer2 = "${Random.nextInt(10, 99).absoluteValue}" - protected open val userAgentRandomizer3 = "${Random.nextInt(100, 999).absoluteValue}" - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add( - "User-Agent", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + - "Chrome/8$userAgentRandomizer1.0.4$userAgentRandomizer3.1$userAgentRandomizer2 Safari/537.36" - ) - - // Popular - override fun popularMangaRequest(page: Int) = GET(baseUrl, headers) - override fun popularMangaSelector() = "a.hot-manga__item" - override fun popularMangaNextPageSelector(): String? = null - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("div.hot-manga__item-caption").select("div.hot-manga__item-name").text().trim() - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = "$cdnUrl/uploads" + element.select("a").attr("href") + "/cover/cover_250x350.jpg" - } - - // Latest - override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) - override fun latestUpdatesSelector() = ".section__list-group li div.section__list-group-left" - override fun latestUpdatesNextPageSelector(): String? = null - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("a").attr("title") - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = "$cdnUrl/uploads" + element.select("a").attr("href") + "/cover/cover_250x350.jpg" - } - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse("$baseUrl/search").buildUpon() - .appendQueryParameter("query", query) - .appendQueryParameter("search_type", "manga") - return GET(uri.toString(), headers) - } - override fun searchMangaSelector() = "h1 + ul a[href*=manga]" - override fun searchMangaNextPageSelector(): String? = null - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("a").text().trim() - setUrlWithoutDomain(element.select("a").attr("href")) - thumbnail_url = "$cdnUrl/uploads" + element.select("a").attr("href") + "/cover/cover_250x350.jpg" - } - - // Manga details - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - thumbnail_url = document.select("div.manga-view__header-image").select("img").attr("abs:src") - description = document.select("dd.text-justify.text-break").text() - author = document.select("a[href*=author]").text() - artist = document.select("a[href*=artist]").text() - genre = document.select("a[href*=category]").joinToString { it.text() } - status = when (document.select("span.badge.bg-success.text-uppercase").text()) { - "En Cours" -> SManga.ONGOING - "Terminé" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - - // Chapter list - override fun chapterListSelector() = throw Exception("Not used") - override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used") - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - var widgetDocument = document - val widgetPageListUrl = Regex("""['"](/arrilot/load-widget.*?)['"]""").find(document.toString())?.groupValues?.get(1) - if (widgetPageListUrl != null) { - widgetDocument = client.newCall(GET("$baseUrl$widgetPageListUrl", headers)).execute().asJsoup() - } - - return widgetDocument.select("tr[class*=volume-]:has(td)").map { - SChapter.create().apply { - url = it.select("td.table__chapter").select("a").attr("href") - name = it.select("td.table__chapter").select("span").text().trim() - chapter_number = it.select("td.table__chapter").select("span").text().substringAfter("Chapitre").replace(Regex("""[,-]"""), ".").trim().toFloatOrNull() - ?: -1F - date_upload = it.select("td.table__date").firstOrNull()?.text()?.let { parseDate(it) } - ?: 0 - scanlator = document.select("[itemprop=translator] a").joinToString { it.text().replace(Regex("""[\[\]]"""), "") } - } - } - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("dd.MM.yyyy", Locale.US).parse(date)?.time ?: 0L - } - - // Pages - override fun pageListParse(document: Document): List<Page> { - val selectorEncoded1 = "Wkdim" + "gsai" + "mgWQyV2lkMm" + "xrS2img" + "ppZDFoY" + "kd4ZElHaW" + - "RsdimgFp6cHVi" + "M1FvVzNOeVl5" + "bimgzlpZG" + "lkWjJsbVhT" + "a3imgNJQzVq" + "YjI1MFlpZFd" + - "saWR1WlhJdFi" + "mgpteDFhV1FnTGi" + "mg5KdmlkZHlC" + "a2FYWimgTZiaW" + "imgRtOTBL" + "QzV0ZUMxaim" + - "gGRYUnZpZEtT" + "QTZibTki" + "mgwS0imgRwdm" + "JteGlkimgNU" + "xXTm9hV3himgr" + "aWRLU0JwYldjNm" + "JtOTBpZEti" + - "mgGdHpp" + "ZGNtTXFQimg" + "V2RwWml" + "kbDBw" - val selectorEncoded2 = String(Base64.decode(selectorEncoded1.replace("img", ""), Base64.DEFAULT)) - val selectorDecoded = String(Base64.decode(selectorEncoded2.replace("id", ""), Base64.DEFAULT)) - val elements = document.select(selectorDecoded) - - val pages = mutableListOf<Page>() - var j = 0 - for (i in 0 until elements.count()) { - if (elements[i].attr("src").trim().startsWith(cdnUrl)) { - pages.add(Page(j, document.location(), elements[i].attr("src").trim())) - ++j - } - } - return pages - } - override fun imageUrlParse(document: Document): String = throw Exception("Not used") - override fun imageRequest(page: Page): Request { - val imgHeaders = headersBuilder().apply { - add("Referer", page.url) - }.build() - return GET(page.imageUrl!!, imgHeaders) - } -} diff --git a/src/fr/scanmanga/.gitignore b/src/fr/scanmanga/.gitignore deleted file mode 100644 index 2c34b6bff..000000000 --- a/src/fr/scanmanga/.gitignore +++ /dev/null @@ -1 +0,0 @@ -local.properties diff --git a/src/fr/scanmanga/AndroidManifest.xml b/src/fr/scanmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/scanmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/scanmanga/build.gradle b/src/fr/scanmanga/build.gradle deleted file mode 100644 index 90bf91c0d..000000000 --- a/src/fr/scanmanga/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Scan-Manga' - pkgNameSuffix = 'fr.scanmanga' - extClass = '.ScanManga' - extVersionCode = 4 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/scanmanga/res/mipmap-hdpi/ic_launcher.png b/src/fr/scanmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 5c6a951ca..000000000 Binary files a/src/fr/scanmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scanmanga/res/mipmap-mdpi/ic_launcher.png b/src/fr/scanmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4daa8fb1d..000000000 Binary files a/src/fr/scanmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scanmanga/res/mipmap-xhdpi/ic_launcher.png b/src/fr/scanmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bc9eea6e9..000000000 Binary files a/src/fr/scanmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt b/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt deleted file mode 100644 index 3b866d464..000000000 --- a/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt +++ /dev/null @@ -1,215 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.scanmanga - -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.parser.Parser -import rx.Observable -import kotlin.random.Random - -class ScanManga : ParsedHttpSource() { - - override val name = "Scan-Manga" - - override val baseUrl = "https://www.scan-manga.com" - - override val lang = "fr" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor { chain -> - val originalCookies = chain.request().header("Cookie") ?: "" - val newReq = chain - .request() - .newBuilder() - .header("Cookie", "$originalCookies; _ga=GA1.2.${shuffle("123456789")}.${System.currentTimeMillis() / 1000}") - .build() - chain.proceed(newReq) - }.build()!! - - private val gson = Gson() - - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Accept-Language", "fr-FR") - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/Tout-le-TOP.html", headers) - } - - override fun popularMangaSelector() = "div.image_manga a[href]" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("img").attr("title") - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.select("img").attr("data-original") - } - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesSelector() = "#content_news .listing" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("a.nom_manga").text() - setUrlWithoutDomain(element.select("a.nom_manga").attr("href")) - /*thumbnail_url = element.select(".logo_manga img").let { - if (it.hasAttr("data-original")) - it.attr("data-original") else it.attr("src") - }*/ // Better not use it, width is too large, which results in terrible image - } - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - fun shuffle(s: String?): String? { - val result = StringBuffer(s!!) - var n = result.length - while (n > 1) { - val randomPoint: Int = Random.nextInt(n) - val randomChar = result[randomPoint] - result.setCharAt(n - 1, randomChar) - n-- - } - return result.toString() - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val searchHeaders = headersBuilder().apply { - add("Referer", "$baseUrl/scanlation/liste_series.html") - add("x-requested-with", "XMLHttpRequest") - }.build() - return GET("$baseUrl/scanlation/scan.data.json", searchHeaders) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - private fun searchMangaParse(response: Response, query: String): MangasPage { - return MangasPage(parseMangaFromJson(response).mangas.filter { it.title.contains(query, ignoreCase = true) }, false) - } - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonData = response.body()!!.string()!! - if (jsonData == "") { - return MangasPage(listOf<SManga>(), false) - } - - val jsonObject = JsonParser().parse(jsonData).asJsonObject - - val mangas = jsonObject.keySet() - .map { key -> - // "95","%24100-is-Too-Cheap","0","3","One Shot","","2 avril 2010","","335","178","4010","" - SManga.create().apply { - url = "/" + jsonObject[key][0].string + "/" + jsonObject[key][1].string + ".html" - title = Parser.unescapeEntities(key, false) - genre = jsonObject[key][2].string.let { - when { - it.contains("0") -> "Shōnen" - it.contains("1") -> "Shōjo" - it.contains("2") -> "Seinen" - it.contains("3") -> "Josei" - else -> null - } - } - status = jsonObject[key][3].string.let { - when { - it.contains("0") -> SManga.ONGOING // En cours - it.contains("1") -> SManga.ONGOING // En pause - it.contains("2") -> SManga.COMPLETED // Terminé - it.contains("3") -> SManga.COMPLETED // One shot - else -> SManga.UNKNOWN - } - } - } - } - - return MangasPage(mangas, false) - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - - // Details - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - title = document.select("h2[itemprop=\"name\"]").text() - author = document.select("li[itemprop=\"author\"] a").joinToString { it.text() } - description = document.select("p[itemprop=\"description\"]").text() - thumbnail_url = document.select(".contenu_fiche_technique .image_manga img").attr("src") - } - } - - // Chapters - override fun chapterListSelector() = throw Exception("Not used") - override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used") - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - - return document.select("div.texte_volume_manga ul li.chapitre div.chapitre_nom a").map { - SChapter.create().apply { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - scanlator = document.select("li[itemprop=\"translator\"] a").joinToString { it.text() } - } - } - } - - // Pages - override fun pageListParse(document: Document): List<Page> { - val docString = document.toString() - - var lelUrl = Regex("""['"](http.*?scanmanga.eu.*)['"]""").find(docString)?.groupValues?.get(1) - if (lelUrl == null) { - lelUrl = Regex("""['"](http.*?le[il].scan-manga.com.*)['"]""").find(docString)?.groupValues?.get(1) - } - - return Regex("""["'](.*?zoneID.*?pageID.*?siteID.*?)["']""").findAll(docString).toList().mapIndexed { i, pageParam -> - Page(i, document.location(), lelUrl + pageParam.groupValues[1]) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun imageRequest(page: Page): Request { - val imgHeaders = headersBuilder().apply { - add("Referer", page.url) - }.build() - return GET(page.imageUrl!!, imgHeaders) - } -} diff --git a/src/fr/scantrad/AndroidManifest.xml b/src/fr/scantrad/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/scantrad/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/scantrad/build.gradle b/src/fr/scantrad/build.gradle deleted file mode 100644 index 4436a8210..000000000 --- a/src/fr/scantrad/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Scantrad' - pkgNameSuffix = 'fr.scantrad' - extClass = '.Scantrad' - extVersionCode = 16 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/scantrad/res/mipmap-hdpi/ic_launcher.png b/src/fr/scantrad/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8e2c9f121..000000000 Binary files a/src/fr/scantrad/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantrad/res/mipmap-mdpi/ic_launcher.png b/src/fr/scantrad/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 22b182445..000000000 Binary files a/src/fr/scantrad/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantrad/res/mipmap-xhdpi/ic_launcher.png b/src/fr/scantrad/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index fd1328ca1..000000000 Binary files a/src/fr/scantrad/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantrad/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/scantrad/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7e0004bea..000000000 Binary files a/src/fr/scantrad/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantrad/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/scantrad/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 88b4db633..000000000 Binary files a/src/fr/scantrad/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantrad/res/web_hi_res_512.png b/src/fr/scantrad/res/web_hi_res_512.png deleted file mode 100644 index d4088345a..000000000 Binary files a/src/fr/scantrad/res/web_hi_res_512.png and /dev/null differ diff --git a/src/fr/scantrad/src/eu/kanade/tachiyomi/extension/fr/scantrad/Scantrad.kt b/src/fr/scantrad/src/eu/kanade/tachiyomi/extension/fr/scantrad/Scantrad.kt deleted file mode 100644 index 0bc4562ac..000000000 --- a/src/fr/scantrad/src/eu/kanade/tachiyomi/extension/fr/scantrad/Scantrad.kt +++ /dev/null @@ -1,217 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.scantrad - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale -import java.util.concurrent.TimeUnit - -class Scantrad : ParsedHttpSource() { - - override val name = "Scantrad" - - override val baseUrl = "https://scantrad.net" - - override val lang = "fr" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(1) - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addNetworkInterceptor(rateLimitInterceptor) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36") - .add("Accept-Language", "fr") - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/mangas", headers) - } - - override fun popularMangaSelector() = "div.h-left a" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("div.hmi-titre").text() - manga.thumbnail_url = element.select("img").attr("abs:src") - - return manga - } - - override fun popularMangaNextPageSelector() = "Not needed" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = mutableListOf<SManga>() - - document.select(latestUpdatesSelector()).map { mangas.add(latestUpdatesFromElement(it)) } - - return MangasPage(mangas.distinctBy { it.url }, false) - } - - override fun latestUpdatesSelector() = "div.h-left > div.home-manga" - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.select("div.hmi-titre a").first().attr("abs:href")) - manga.title = element.select("div.hmi-titre a").first().text() - manga.thumbnail_url = element.select("img").attr("abs:src") - - return manga - } - - override fun latestUpdatesNextPageSelector() = "not needed" - - // Search - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(1) - - private fun searchMangaParse(response: Response, query: String): MangasPage { - return MangasPage(popularMangaParse(response).mangas.filter { it.title.contains(query, ignoreCase = true) }, false) - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - - document.select("div.mf-info").let { - manga.title = it.select("div.titre").text() - manga.description = it.select("div.synopsis").text() - manga.thumbnail_url = it.select("div.poster img").attr("abs:src") - manga.status = parseStatus(it.select("div.sub-i:last-of-type span").text()) - } - - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("En cours") -> SManga.ONGOING - status.contains("Terminé") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - // ignore links from Amazon that get mixed in - override fun chapterListSelector() = "div.chapitre:has(span:contains(lire))" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - chapter.setUrlWithoutDomain(element.select("a.chr-button").attr("href")) - chapter.name = element.select("div.chl-titre").text() - chapter.date_upload = parseChapterDate(element.select("div.chl-date").text()) - - return chapter - } - - private fun parseChapterDate(date: String): Long { - val value = date.split(" ")[3].toIntOrNull() - - return if (value != null) { - when (date.split(" ")[4]) { - "minute", "minutes" -> Calendar.getInstance().apply { - add(Calendar.MINUTE, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "heure", "heures" -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "jour", "jours" -> Calendar.getInstance().apply { - add(Calendar.DATE, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "semaine", "semaines" -> Calendar.getInstance().apply { - add(Calendar.DATE, value * 7 * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "mois" -> Calendar.getInstance().apply { - add(Calendar.MONTH, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "an", "ans", "année" -> Calendar.getInstance().apply { - add(Calendar.YEAR, value * -1) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - else -> { - return 0L - } - } - } else { - try { - SimpleDateFormat("dd MMM yyyy", Locale.FRENCH).parse(date.substringAfter("le "))?.time ?: 0 - } catch (_: Exception) { - 0L - } - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("div.sc-lel img[id]").forEachIndexed { i, img -> - pages.add(Page(i, "", img.attr("abs:data-src"))) - } - - return pages - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/fr/scantradunion/AndroidManifest.xml b/src/fr/scantradunion/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/fr/scantradunion/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/fr/scantradunion/build.gradle b/src/fr/scantradunion/build.gradle deleted file mode 100644 index 1519d1e5e..000000000 --- a/src/fr/scantradunion/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Scantrad Union' - pkgNameSuffix = 'fr.scantradunion' - extClass = '.ScantradUnion' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/fr/scantradunion/res/mipmap-hdpi/ic_launcher.png b/src/fr/scantradunion/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ed532ebd7..000000000 Binary files a/src/fr/scantradunion/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantradunion/res/mipmap-mdpi/ic_launcher.png b/src/fr/scantradunion/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c6d9278c8..000000000 Binary files a/src/fr/scantradunion/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantradunion/res/mipmap-xhdpi/ic_launcher.png b/src/fr/scantradunion/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2c1943cf4..000000000 Binary files a/src/fr/scantradunion/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantradunion/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/scantradunion/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 199390624..000000000 Binary files a/src/fr/scantradunion/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantradunion/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/scantradunion/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f2c2802f1..000000000 Binary files a/src/fr/scantradunion/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/fr/scantradunion/res/web_hi_res_512.png b/src/fr/scantradunion/res/web_hi_res_512.png deleted file mode 100644 index f10def9b7..000000000 Binary files a/src/fr/scantradunion/res/web_hi_res_512.png and /dev/null differ diff --git a/src/fr/scantradunion/src/eu/kanade/tachiyomi/extension/fr/scantradunion/ScantradUnion.kt b/src/fr/scantradunion/src/eu/kanade/tachiyomi/extension/fr/scantradunion/ScantradUnion.kt deleted file mode 100644 index a6fa56044..000000000 --- a/src/fr/scantradunion/src/eu/kanade/tachiyomi/extension/fr/scantradunion/ScantradUnion.kt +++ /dev/null @@ -1,165 +0,0 @@ -package eu.kanade.tachiyomi.extension.fr.scantradunion - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -class ScantradUnion : ParsedHttpSource() { - override val name = "Scantrad Union" - override val baseUrl = "https://scantrad-union.com" - override val lang = "fr" - override val supportsLatest = true - - // If these parameters are not used, the search results are incomplete. - private val searchUrlSuffix = "&asp_active=1&p_asid=1&p_asp_data=YXNwX2dlbiU1QiU1RD10aXRsZSZjdXN0b21zZXQlNUIlNUQ9bWFuZ2E=" - private val frenchDateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.FRANCE) - - override fun chapterFromElement(element: Element): SChapter { - val chapterNumberStr = element.select(".chapter-number").text() - // We don't have a css selector to select the date directly, but we know that it will always - // be the third child of a .name-chapter. - val dateUploadStr = element.select(".name-chapter").first().children().elementAt(2).text() - val chapterName = element.select(".chapter-name").text() - // The only way to get the chapter url is to check all .btnlel and take the one starting with https://... - val url = element.select(".btnlel").map { it.attr("href") }.first { it.startsWith("https://scantrad-union.com/read/") } - val chapterNumberStrFormatted = formatMangaNumber(chapterNumberStr) - - val chapter = SChapter.create() - // The chapter name is often empty - // So we will display ${chapterNumber} - ${chapterName} and only ${chapterNumber} if chapterName is empty - chapter.name = listOf(chapterNumberStrFormatted, chapterName).filter(String::isNotBlank).joinToString(" - ") - chapter.date_upload = parseFrenchDateFromString(dateUploadStr) - // The scanlator is several teams of translators concatenated in one string. - chapter.scanlator = element.select(".btnteam").joinToString(" ") { teamElem -> teamElem.text() } - chapter.setUrlWithoutDomain(url) - return chapter - } - - override fun chapterListSelector(): String = ".links-projects li" - - override fun imageUrlParse(document: Document): String = "" - - override fun latestUpdatesFromElement(element: Element): SManga { - val title = element.select("a.text-truncate").text() - val url = element.select("a.text-truncate").attr("href") - - val manga = SManga.create() - manga.title = formatMangaTitle(title) - manga.thumbnail_url = element.select("img.attachment-thumbnail").attr("src") - manga.author = element.select(".nomteam").text() - // Cannot distinguish authors and artists because they are in the same section. - manga.artist = manga.author - manga.setUrlWithoutDomain(url) - return manga - } - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesSelector() = ".dernieresmaj .colonne" - - override fun mangaDetailsParse(document: Document): SManga { - val title = document.select(".projet-description h2").text() - val statusStr = document.select(".label.label-primary")[2].text() - - val manga = SManga.create() - manga.title = formatMangaTitle(title) - manga.thumbnail_url = document.select(".projet-image img").attr("src") - manga.description = document.select(".sContent").text() - manga.author = document.select("div.project-details a[href*=auteur]") - .joinToString(", ") { teamElem -> teamElem.text() } - // Cannot distinguish authors and artists because they are in the same section. - manga.artist = manga.author - manga.status = mapMangaStatusStringToConst(statusStr) - manga.setUrlWithoutDomain(document.location()) - return manga - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("#webtoon a img") - .mapIndexed { index: Int, imgElem: Element -> - // In webtoon mode, images have an src attribute only. - // In manga mode, images have a data-src attribute that contains the src - val imgElemDataSrc = imgElem.attr("data-src") - val imgElemSrc = imgElem.attr("src") - - val imgUrl = if (imgElemDataSrc.isNullOrBlank()) imgElemSrc else imgElemDataSrc - - Page(index, "", imgUrl) - } - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.title = formatMangaTitle(element.select(".index-top3-title").text()) - manga.setUrlWithoutDomain(element.attr("href")) - manga.thumbnail_url = element.select(".index-top3-bg").attr("style") - .substringAfter("background:url('").substringBefore("')") - return manga - } - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/projets/", headers) - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun popularMangaSelector(): String = ".index-top3-a" - - override fun searchMangaFromElement(element: Element): SManga { - val titleLinkElem = element.select("a.index-post-header-a") - - val manga = SManga.create() - manga.title = formatMangaTitle(titleLinkElem.text()) - manga.setUrlWithoutDomain(titleLinkElem.attr("href")) - manga.thumbnail_url = element.select("img.wp-post-image").attr("src") - return manga - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val searchUrl = "$baseUrl/?s=$query$searchUrlSuffix" - return GET(searchUrl, headers) - } - - override fun searchMangaSelector(): String = "article.post-outer" - - private fun formatMangaNumber(value: String): String { - return value.removePrefix("#").trim() - } - - private fun formatMangaTitle(value: String): String { - // Translations produced by Scantrad Union partners are prefixed with "[Partenaire] ". - return value.removePrefix("[Partenaire]").trim() - } - - private fun parseFrenchDateFromString(value: String): Long { - return try { - frenchDateFormat.parse(value)?.time ?: 0L - } catch (ex: ParseException) { - 0L - } - } - - private fun mapMangaStatusStringToConst(status: String): Int { - return when (status.trim().toLowerCase(Locale.FRENCH)) { - "en cours" -> SManga.ONGOING - "terminé" -> SManga.COMPLETED - "licencié" -> SManga.LICENSED - else -> SManga.UNKNOWN - } - } -} diff --git a/src/id/KomikFan/AndroidManifest.xml b/src/id/KomikFan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/KomikFan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/KomikFan/build.gradle b/src/id/KomikFan/build.gradle deleted file mode 100644 index 0488353ba..000000000 --- a/src/id/KomikFan/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'KomikFan' - pkgNameSuffix = 'id.komikfan' - extClass = '.KomikFan' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/KomikFan/res/mipmap-hdpi/ic_launcher.png b/src/id/KomikFan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cad870a62..000000000 Binary files a/src/id/KomikFan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/KomikFan/res/mipmap-mdpi/ic_launcher.png b/src/id/KomikFan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 078965e89..000000000 Binary files a/src/id/KomikFan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/KomikFan/res/mipmap-xhdpi/ic_launcher.png b/src/id/KomikFan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bfba038de..000000000 Binary files a/src/id/KomikFan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/KomikFan/res/mipmap-xxhdpi/ic_launcher.png b/src/id/KomikFan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8a538f78f..000000000 Binary files a/src/id/KomikFan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/KomikFan/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/KomikFan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index de401f4e9..000000000 Binary files a/src/id/KomikFan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/KomikFan/res/web_hi_res_512.png b/src/id/KomikFan/res/web_hi_res_512.png deleted file mode 100644 index c847f344b..000000000 Binary files a/src/id/KomikFan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/KomikFan/src/eu/kanade/tachiyomi/extension/id/komikfan/KomikFan.kt b/src/id/KomikFan/src/eu/kanade/tachiyomi/extension/id/komikfan/KomikFan.kt deleted file mode 100644 index 2f63c20a3..000000000 --- a/src/id/KomikFan/src/eu/kanade/tachiyomi/extension/id/komikfan/KomikFan.kt +++ /dev/null @@ -1,165 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.komikfan - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class KomikFan : ParsedHttpSource() { - - override val name = "KomikFan" - override val baseUrl = "https://komikfan.com" - override val lang = "id" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US) - - // popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/daftar-komik", headers) - } - - override fun popularMangaSelector() = "#a-z .row-cells .ranking1" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("a img").attr("data-src") - manga.setUrlWithoutDomain(element.select("a").attr("href")) - manga.title = element.select("a h4").text() - return manga - } - - override fun popularMangaNextPageSelector(): String? = null - - // latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/manga?page=$page", headers) - } - - override fun latestUpdatesSelector() = ".daftar .bge" - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - val titleDash = element.select(".kan a.popunder").attr("href").substringAfter("manga/") - - manga.thumbnail_url = "$baseUrl/storage/komik/sampul_detail/$titleDash.jpg" - manga.setUrlWithoutDomain(element.select(".kan a.popunder").attr("href")) - manga.title = element.select(".kan a.popunder h3").text() - - return manga - } - - override fun latestUpdatesNextPageSelector() = ".pagination a:contains(lanjut)" - - // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/searching?cari=$query") - } - - override fun searchMangaSelector() = latestUpdatesSelector() - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - val titleDash = element.select("a").attr("href").substringAfter("manga/") - - manga.thumbnail_url = "$baseUrl/storage/komik/sampul_detail/$titleDash.jpg" - manga.setUrlWithoutDomain(element.select("a").attr("href")) - manga.title = element.select(".kan h3").text() - - return manga - } - - override fun searchMangaNextPageSelector(): String? = null - - // manga details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - thumbnail_url = document.select("article section .ims img").attr("src") - title = document.select(".inftable tbody ~ tr:contains(judul) b").text() - author = document.select(".inftable td:contains(komikus) + td").firstOrNull()?.ownText() - status = parseStatus(document.select(".inftable tr:contains(status)").text()) - genre = document.select("ul.genre li.genre a").joinToString { it.text() } - description = document.select("#Sinopsis p").joinToString("\n") { it.text() } - } - - private fun parseStatus(element: String): Int = when { - element.toLowerCase().contains("ongoing") -> SManga.ONGOING - element.toLowerCase().contains("completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapters - override fun chapterListSelector() = "#Chapter > table tr:has(a)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("td.judulseries a").first() - val chapter = SChapter.create() - val mangaTitle = element.select("td.judulseries a span").text() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text().substringAfter(mangaTitle) - chapter.date_upload = element.select("td.tanggalseries time").first()?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - fun parseChapterDate(date: String): Long { - return if (date.contains("yang lalu")) { - val value = date.split(' ')[0].toInt() - when { - "detik" in date -> Calendar.getInstance().apply { - add(Calendar.SECOND, value * -1) - }.timeInMillis - "menit" in date -> Calendar.getInstance().apply { - add(Calendar.MINUTE, value * -1) - }.timeInMillis - "jam" in date -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, value * -1) - }.timeInMillis - "hari" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * -1) - }.timeInMillis - "minggu" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * 7 * -1) - }.timeInMillis - "bulan" in date -> Calendar.getInstance().apply { - add(Calendar.MONTH, value * -1) - }.timeInMillis - "tahun" in date -> Calendar.getInstance().apply { - add(Calendar.YEAR, value * -1) - }.timeInMillis - else -> { - 0L - } - } - } else { - try { - dateFormat.parse(date)?.time ?: 0 - } catch (_: Exception) { - 0L - } - } - } - - // pages - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - var i = 0 - document.select("#Baca_Komik img").forEach { element -> - val url = element.attr("src") - i++ - if (url.isNotEmpty()) { - pages.add(Page(i, "", url)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" -} diff --git a/src/id/bacakomik/AndroidManifest.xml b/src/id/bacakomik/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/bacakomik/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/bacakomik/build.gradle b/src/id/bacakomik/build.gradle deleted file mode 100644 index fe832a12a..000000000 --- a/src/id/bacakomik/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Bacakomik' - pkgNameSuffix = 'id.bacakomik' - extClass = '.Bacakomik' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/bacakomik/res/mipmap-hdpi/ic_launcher.png b/src/id/bacakomik/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index bd7c11286..000000000 Binary files a/src/id/bacakomik/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacakomik/res/mipmap-mdpi/ic_launcher.png b/src/id/bacakomik/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index be688d7a7..000000000 Binary files a/src/id/bacakomik/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacakomik/res/mipmap-xhdpi/ic_launcher.png b/src/id/bacakomik/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f8156f7fa..000000000 Binary files a/src/id/bacakomik/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacakomik/res/mipmap-xxhdpi/ic_launcher.png b/src/id/bacakomik/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c900f3fb9..000000000 Binary files a/src/id/bacakomik/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacakomik/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/bacakomik/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index bd6367e86..000000000 Binary files a/src/id/bacakomik/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacakomik/res/web_hi_res_512.png b/src/id/bacakomik/res/web_hi_res_512.png deleted file mode 100644 index 9e9aa8339..000000000 Binary files a/src/id/bacakomik/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/bacakomik/src/eu/kanade/tachiyomi/extension/id/bacakomik/Bacakomik.kt b/src/id/bacakomik/src/eu/kanade/tachiyomi/extension/id/bacakomik/Bacakomik.kt deleted file mode 100644 index 1f9863e7e..000000000 --- a/src/id/bacakomik/src/eu/kanade/tachiyomi/extension/id/bacakomik/Bacakomik.kt +++ /dev/null @@ -1,327 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.bacakomik - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Bacakomik : ParsedHttpSource() { - override val name = "Bacakomik" - override val baseUrl = "https://bacakomik.co" - override val lang = "id" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US) - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/daftar-manga/page/$page/?order=popular", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/daftar-manga/page/$page/?order=update", headers) - } - - override fun popularMangaSelector() = "div.animepost" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.next.page-numbers" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.limit img").attr("src") - element.select("div.animposx > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val builtUrl = if (page == 1) "$baseUrl/daftar-manga/" else "$baseUrl/daftar-manga/page/$page/?order=" - val url = HttpUrl.parse(builtUrl)!!.newBuilder() - url.addQueryParameter("title", query) - url.addQueryParameter("page", page.toString()) - filters.forEach { filter -> - when (filter) { - is AuthorFilter -> { - url.addQueryParameter("author", filter.state) - } - is YearFilter -> { - url.addQueryParameter("yearx", filter.state) - } - is StatusFilter -> { - val status = when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "completed" - Filter.TriState.STATE_EXCLUDE -> "ongoing" - else -> "" - } - url.addQueryParameter("status", status) - } - is TypeFilter -> { - url.addQueryParameter("type", filter.toUriPart()) - } - is SortByFilter -> { - url.addQueryParameter("order", filter.toUriPart()) - } - is GenreListFilter -> { - filter.state - .filter { it.state != Filter.TriState.STATE_IGNORE } - .forEach { url.addQueryParameter("genre[]", it.id) } - } - } - } - return GET(url.build().toString(), headers) - } - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.infoanime").first() - val descElement = document.select("div.desc > .entry-content.entry-content-single").first() - val sepName = infoElement.select(".infox > .spe > span:nth-child(2)").last() - val manga = SManga.create() - // need authorCleaner to take "pengarang:" string to remove it from author - val authorCleaner = document.select(".infox .spe b:contains(Pengarang)").text() - manga.author = document.select(".infox .spe span:contains(Pengarang)").text().substringAfter(authorCleaner) - manga.artist = manga.author - val genres = mutableListOf<String>() - infoElement.select(".infox > .genre-info > a").forEach { element -> - val genre = element.text() - genres.add(genre) - } - manga.genre = genres.joinToString(", ") - manga.status = parseStatus(infoElement.select(".infox > .spe > span:nth-child(1)").text()) - manga.description = descElement.select("p").text() - manga.thumbnail_url = document.select(".thumb > img:nth-child(1)").attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.toLowerCase().contains("berjalan") -> SManga.ONGOING - element.toLowerCase().contains("tamat") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "#chapter_list li" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select(".lchx a").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select(".dt a").first()?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - fun parseChapterDate(date: String): Long { - return if (date.contains("yang lalu")) { - val value = date.split(' ')[0].toInt() - when { - "detik" in date -> Calendar.getInstance().apply { - add(Calendar.SECOND, value * -1) - }.timeInMillis - "menit" in date -> Calendar.getInstance().apply { - add(Calendar.MINUTE, value * -1) - }.timeInMillis - "jam" in date -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, value * -1) - }.timeInMillis - "hari" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * -1) - }.timeInMillis - "minggu" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * 7 * -1) - }.timeInMillis - "bulan" in date -> Calendar.getInstance().apply { - add(Calendar.MONTH, value * -1) - }.timeInMillis - "tahun" in date -> Calendar.getInstance().apply { - add(Calendar.YEAR, value * -1) - }.timeInMillis - else -> { - 0L - } - } - } else { - try { - dateFormat.parse(date)?.time ?: 0 - } catch (_: Exception) { - 0L - } - } - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""Chapter\s([0-9]+)""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[1]?.value!!.toFloat() - } - } - } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - var i = 0 - document.select("div.imgch-auh img").forEach { element -> - val url = element.attr("src") - i++ - if (url.isNotEmpty()) { - pages.add(Page(i, "", url)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - if (page.imageUrl!!.contains("i2.wp.com")) { - val headers = Headers.Builder() - headers.apply { - add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") - } - return GET(page.imageUrl!!, headers.build()) - } else { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - } - - private class AuthorFilter : Filter.Text("Author") - - private class YearFilter : Filter.Text("Year") - - private class TypeFilter : UriPartFilter( - "Type", - arrayOf( - Pair("Default", ""), - Pair("Manga", "Manga"), - Pair("Manhwa", "Manhwa"), - Pair("Manhua", "Manhua"), - Pair("Comic", "Comic") - ) - ) - - private class SortByFilter : UriPartFilter( - "Sort By", - arrayOf( - Pair("Default", ""), - Pair("A-Z", "title"), - Pair("Z-A", "titlereverse"), - Pair("Latest Update", "update"), - Pair("Latest Added", "latest"), - Pair("Popular", "popular") - ) - ) - - private class StatusFilter : UriPartFilter( - "Status", - arrayOf( - Pair("All", ""), - Pair("Ongoing", "ongoing"), - Pair("Completed", "completed") - ) - ) - - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreListFilter(genres: List<Genre>) : Filter.Group<Genre>("Genre", genres) - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - AuthorFilter(), - YearFilter(), - StatusFilter(), - TypeFilter(), - SortByFilter(), - GenreListFilter(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("4-Koma", "4-koma"), - Genre("4-Koma. Comedy", "4-koma-comedy"), - Genre("Action", "action"), - Genre("Action. Adventure", "action-adventure"), - Genre("Adult", "adult"), - Genre("Adventure", "adventure"), - Genre("Comedy", "comedy"), - Genre("Cooking", "cooking"), - Genre("Demons", "demons"), - Genre("Doujinshi", "doujinshi"), - Genre("Drama", "drama"), - Genre("Ecchi", "ecchi"), - Genre("Echi", "echi"), - Genre("Fantasy", "fantasy"), - Genre("Game", "game"), - Genre("Gender Bender", "gender-bender"), - Genre("Gore", "gore"), - Genre("Harem", "harem"), - Genre("Historical", "historical"), - Genre("Horror", "horror"), - Genre("Isekai", "isekai"), - Genre("Josei", "josei"), - Genre("Magic", "magic"), - Genre("Manga", "manga"), - Genre("Manhua", "manhua"), - Genre("Manhwa", "manhwa"), - Genre("Martial Arts", "martial-arts"), - Genre("Mature", "mature"), - Genre("Mecha", "mecha"), - Genre("Medical", "medical"), - Genre("Military", "military"), - Genre("Music", "music"), - Genre("Mystery", "mystery"), - Genre("One Shot", "one-shot"), - Genre("Oneshot", "oneshot"), - Genre("Parody", "parody"), - Genre("Police", "police"), - Genre("Psychological", "psychological"), - Genre("Romance", "romance"), - Genre("Samurai", "samurai"), - Genre("School", "school"), - Genre("School Life", "school-life"), - Genre("Sci-fi", "sci-fi"), - Genre("Seinen", "seinen"), - Genre("Shoujo", "shoujo"), - Genre("Shoujo Ai", "shoujo-ai"), - Genre("Shounen", "shounen"), - Genre("Shounen Ai", "shounen-ai"), - Genre("Slice of Life", "slice-of-life"), - Genre("Smut", "smut"), - Genre("Sports", "sports"), - Genre("Super Power", "super-power"), - Genre("Supernatural", "supernatural"), - Genre("Thriller", "thriller"), - Genre("Tragedy", "tragedy"), - Genre("Vampire", "vampire"), - Genre("Webtoon", "webtoon"), - Genre("Webtoons", "webtoons"), - Genre("Yuri", "yuri") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/id/bacamanga/AndroidManifest.xml b/src/id/bacamanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/bacamanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/bacamanga/build.gradle b/src/id/bacamanga/build.gradle deleted file mode 100644 index 9afdbf8bb..000000000 --- a/src/id/bacamanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Baca Manga' - pkgNameSuffix = 'id.bacamanga' - extClass = '.BacaManga' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/bacamanga/res/mipmap-hdpi/ic_launcher.png b/src/id/bacamanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2ad31a785..000000000 Binary files a/src/id/bacamanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacamanga/res/mipmap-mdpi/ic_launcher.png b/src/id/bacamanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c155cca26..000000000 Binary files a/src/id/bacamanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacamanga/res/mipmap-xhdpi/ic_launcher.png b/src/id/bacamanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4040725a3..000000000 Binary files a/src/id/bacamanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacamanga/res/mipmap-xxhdpi/ic_launcher.png b/src/id/bacamanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a01ed8600..000000000 Binary files a/src/id/bacamanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacamanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/bacamanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index bdae49d3c..000000000 Binary files a/src/id/bacamanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/bacamanga/res/web_hi_res_512.png b/src/id/bacamanga/res/web_hi_res_512.png deleted file mode 100644 index af6bfc36e..000000000 Binary files a/src/id/bacamanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/bacamanga/src/eu/kanade/tachiyomi/extension/id/bacamanga/BacaManga.kt b/src/id/bacamanga/src/eu/kanade/tachiyomi/extension/id/bacamanga/BacaManga.kt deleted file mode 100644 index ebe40f845..000000000 --- a/src/id/bacamanga/src/eu/kanade/tachiyomi/extension/id/bacamanga/BacaManga.kt +++ /dev/null @@ -1,273 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.bacamanga - -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class BacaManga : ParsedHttpSource() { - - override val name = "BacaManga" - override val baseUrl = "https://bacamanga.cc" - override val lang = "id" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/komik-populer/page/$page/", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/manga/page/$page/", headers) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = if (query.isNotBlank()) { - val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder() - val pattern = "\\s+".toRegex() - val q = query.replace(pattern, "+") - if (query.isNotEmpty()) { - url.addQueryParameter("s", q) - } else { - url.addQueryParameter("s", "") - } - url.toString() - } else { - val url = HttpUrl.parse("$baseUrl/daftar-komik/page/$page")!!.newBuilder() - var orderBy: String - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Status -> url.addQueryParameter("status", arrayOf("", "ongoing", "completed")[filter.state]) - is GenreList -> { - val genreInclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } - } - if (genreInclude.isNotEmpty()) { - genreInclude.forEach { genre -> - url.addQueryParameter("genre[]", genre) - } - } - } - is SortBy -> { - orderBy = filter.toUriPart() - url.addQueryParameter("order", orderBy) - } - } - } - url.toString() - } - return GET(url, headers) - } - - override fun popularMangaSelector() = "div.animepost" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").attr("data-lazy-src") - manga.setUrlWithoutDomain(element.select(".bigor > a").attr("href")) - manga.title = element.select(".bigor .tt h2").text() - return manga - } - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.next.page-numbers" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select(".infox").first() - val sepName = infoElement.select(".spe > span:nth-child(4)").last() - val manga = SManga.create() - manga.author = infoElement.select(".spe span:contains(Pengarang)").text().replace("Pengarang: ", "").trim() - manga.artist = sepName.ownText() - val genres = mutableListOf<String>() - infoElement.select(".genre-info > a").forEach { element -> - val genre = element.text() - genres.add(genre) - } - manga.genre = genres.joinToString(", ") - manga.status = parseStatus(infoElement.select(".spe span:contains(Status)").text()) - manga.description = document.select("div[^itemprop]").last().text() - manga.thumbnail_url = document.select(".thumb noscript img").first().attr("src") - - return manga - } - - private fun parseStatus(element: String): Int = when { - - element.toLowerCase().contains("berjalan") -> SManga.ONGOING - element.toLowerCase().contains("tamat") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div#chapter_list ul li" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select(".lchx a").first() - val timeElement = element.select("span.rightoff").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = 0 - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""Chapter\s([0-9]+)""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[1]?.value!!.toFloat() - } - } - } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val scriptToParse = document.select("script[src*=cache]").first().attr("src") - val slideaid = client.newCall(GET(scriptToParse, headers)).execute().body()!!.string() - val imagesList = slideaid.substringAfter("var imgch").substringBefore(";").substringAfter("=").trim() - val img_url = slideaid.substringAfter("#chimg").substringBefore("onError").substringAfter("src=\"").substringBefore("'").trim() - val json = JsonParser().parse(imagesList).asJsonArray - json.forEachIndexed { i, url -> - val url_clean = url.toString().removeSurrounding("\"") - // BASE URL HARD CODED - pages.add(Page(i, "", "$img_url$url_clean")) - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val headers = Headers.Builder() - headers.apply { - add("Referer", baseUrl) - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.100 Mobile Safari/537.36") - } - - if (page.imageUrl!!.contains("i0.wp.com")) { - headers.apply { - add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") - } - } - return GET(page.imageUrl!!, headers.build()) - } - - private class SortBy : UriPartFilter( - "Sort by", - arrayOf( - Pair("Default", ""), - Pair("A-Z", "title"), - Pair("Z-A", "titlereverse"), - Pair("Latest Update", "update"), - Pair("Latest Added", "latest"), - Pair("Popular", "popular") - ) - ) - - private class Status : UriPartFilter( - "Status", - arrayOf( - Pair("All", ""), - Pair("Ongoing", "Ongoing"), - Pair("Completed", "Completed") - ) - ) - - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - SortBy(), - Filter.Separator(), - Status(), - Filter.Separator(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("Action", "action"), - Genre("Adult", "adult"), - Genre("Adventure", "adventure"), - Genre("Comedy", "comedy"), - Genre("Demon", "demon"), - Genre("Demons", "demons"), - Genre("Doujinshi", "doujinshi"), - Genre("Drama", "drama"), - Genre("Ecchi", "ecchi"), - Genre("Fantasy", "fantasy"), - Genre("Game", "game"), - Genre("Gender Bender", "gender-bender"), - Genre("Genres: Action", "genres-action"), - Genre("Gore", "gore"), - Genre("Harem", "harem"), - Genre("Historical", "historical"), - Genre("Horor", "horor"), - Genre("Horror", "horror"), - Genre("Isekai", "isekai"), - Genre("Josei", "josei"), - Genre("Lolicon", "lolicon"), - Genre("Magic", "magic"), - Genre("Manhua", "manhua"), - Genre("Martial Art", "martial-art"), - Genre("Martial Arts", "martial-arts"), - Genre("Mature", "mature"), - Genre("Mecha", "mecha"), - Genre("Medical", "medical"), - Genre("Military", "military"), - Genre("Mistery", "mistery"), - Genre("Music", "music"), - Genre("Mystery", "mystery"), - Genre("Project", "project"), - Genre("Psychological", "psychological"), - Genre("Reincarnation", "reincarnation"), - Genre("Romance", "romance"), - Genre("School", "school"), - Genre("School Life", "school-life"), - Genre("school of life", "school-of-life"), - Genre("Sci-fi", "sci-fi"), - Genre("Seinen", "seinen"), - Genre("sepernatural", "sepernatural"), - Genre("Shotacon", "shotacon"), - Genre("Shoujo", "shoujo"), - Genre("Shoujo Ai", "shoujo-ai"), - Genre("Shounen", "shounen"), - Genre("Shounen Ai", "shounen-ai"), - Genre("Slice of Life", "slice-of-life"), - Genre("Smut", "smut"), - Genre("Sports", "sports"), - Genre("Super Power", "super-power"), - Genre("Supernatural", "supernatural"), - Genre("Thriller", "thriller"), - Genre("Tragedy", "tragedy"), - Genre("Webtoons", "webtoons"), - Genre("Worn and Torn Newbie", "worn-and-torn-newbie"), - Genre("Yuri", "yuri") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/id/comicfx/AndroidManifest.xml b/src/id/comicfx/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/comicfx/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/comicfx/build.gradle b/src/id/comicfx/build.gradle deleted file mode 100644 index 49d4df83a..000000000 --- a/src/id/comicfx/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Comic Fx' - pkgNameSuffix = 'id.comicfx' - extClass = '.ComicFx' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/comicfx/res/mipmap-hdpi/ic_launcher.png b/src/id/comicfx/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7074e8c9a..000000000 Binary files a/src/id/comicfx/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/comicfx/res/mipmap-mdpi/ic_launcher.png b/src/id/comicfx/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 9603b993f..000000000 Binary files a/src/id/comicfx/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/comicfx/res/mipmap-xhdpi/ic_launcher.png b/src/id/comicfx/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e0d190c89..000000000 Binary files a/src/id/comicfx/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/comicfx/res/mipmap-xxhdpi/ic_launcher.png b/src/id/comicfx/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 01974ade8..000000000 Binary files a/src/id/comicfx/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/comicfx/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/comicfx/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a1be89151..000000000 Binary files a/src/id/comicfx/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/comicfx/res/web_hi_res_512.png b/src/id/comicfx/res/web_hi_res_512.png deleted file mode 100644 index 649f5acef..000000000 Binary files a/src/id/comicfx/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/comicfx/src/eu/kanade/tachiyomi/extension/id/comicfx/ComicFx.kt b/src/id/comicfx/src/eu/kanade/tachiyomi/extension/id/comicfx/ComicFx.kt deleted file mode 100644 index ebe75afe5..000000000 --- a/src/id/comicfx/src/eu/kanade/tachiyomi/extension/id/comicfx/ComicFx.kt +++ /dev/null @@ -1,199 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.comicfx - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale - -class ComicFx : ParsedHttpSource() { - - override val name = "Comic Fx" - override val baseUrl = "https://comicfx.net" - override val lang = "id" - override val supportsLatest = true - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/filterList?page=$page&sortBy=name&asc=true", headers) - } - - override fun popularMangaSelector() = "div.media" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select(".media-left a img").attr("src") - manga.title = element.select(".media-body .media-heading a strong").text() - val item = element.select(".media-left a") - manga.setUrlWithoutDomain(item.attr("href")) - - return manga - } - - override fun popularMangaNextPageSelector() = ".pagination li a[rel=next]" - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest-release?page=$page", headers) - } - - override fun latestUpdatesSelector() = "div.daftar-komik .komika" - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select(".komik-img a .batas img").attr("src") - manga.title = element.select(".komik-des a h3").text() - val item = element.select("div.komik-img a") - manga.setUrlWithoutDomain(item.attr("href")) - - return manga - } - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filters = if (filters.isEmpty()) getFilterList() else filters - val genre = filters.findInstance<GenreList>()?.toUriPart() - val order = filters.findInstance<OrderByFilter>()?.toUriPart() - - // todo search - - return GET("$baseUrl/filterList?page=$page&cstatus=&ctype=&cat=$genre&alpha=&$order&author=&artist=&tag=") // filter - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - author = document.select("#author a").text() - artist = document.select(".infolengkap span:contains(Artist) a").text() - status = parseStatus(document.select(".infolengkap span:contains(status) i").text()) - description = document.select("div.sinopsis p").text() - genre = document.select(".infolengkap span:contains(Genre) a").joinToString { it.text() } - } - - protected fun parseStatus(element: String?): Int = when { - element == null -> SManga.UNKNOWN - listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING - listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - override fun chapterListSelector() = "div.chaplist li .pull-left a" - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) } - - // Add timestamp to latest chapter, taken from "Updated On". so source which not provide chapter timestamp will have atleast one - val updateOn = document.select(".infokomik .infolengkap span:contains(update) b").text() - val date = document.select(".infokomik .infolengkap span:contains(update)").text().substringAfter(updateOn) - val checkChapter = document.select(chapterListSelector()).firstOrNull() - if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date) - - return chapters - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.text() - } - - // Pages - override fun imageUrlParse(document: Document) = "" - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("#all img").mapIndexed { i, element -> - val image = element.attr("data-src") - if (image != "") { - pages.add(Page(i, "", image)) - } - } - - return pages - } - - // filters - override fun getFilterList() = FilterList( - OrderByFilter(), - GenreList() - ) - - private class OrderByFilter : UriPartFilter( - "Sort by", - arrayOf( - Pair("sortBy=name&asc=true", "Default"), - Pair("sortBy=name&asc=true", "A-Z"), - Pair("sortBy=name&asc=false", "Z-A"), - Pair("sortBy=views&asc=false", "Popular to Less"), - Pair("sortBy=views&asc=true", "Less to Popular") - ) - ) - - private class GenreList : UriPartFilter( - "Select Genre", - arrayOf( - Pair("", "<select>"), - Pair("1", "Action"), - Pair("2", "Adventure"), - Pair("3", "Comedy"), - Pair("4", "Doujinshi"), - Pair("5", "Drama"), - Pair("6", "Ecchi"), - Pair("7", "Fantasy"), - Pair("8", "Gender Bender"), - Pair("9", "Harem"), - Pair("10", "Historical"), - Pair("11", "Horror"), - Pair("12", "Josei"), - Pair("13", "Martial Arts"), - Pair("14", "Mature"), - Pair("15", "Mecha"), - Pair("16", "Mystery"), - Pair("17", "One Shot"), - Pair("18", "Psychological"), - Pair("19", "Romance"), - Pair("20", "School Life"), - Pair("21", "Sci-Fi"), - Pair("22", "Seinen"), - Pair("23", "Shoujo"), - Pair("24", "Shoujo Ai"), - Pair("25", "Shounen"), - Pair("26", "Shounen Ai"), - Pair("27", "Slice of Life"), - Pair("28", "Sports"), - Pair("29", "Supernatural"), - Pair("30", "Tragedy"), - Pair("34", "Smut") - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first - } - - private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T -} diff --git a/src/id/komiku/AndroidManifest.xml b/src/id/komiku/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/komiku/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/komiku/build.gradle b/src/id/komiku/build.gradle deleted file mode 100644 index c488de8da..000000000 --- a/src/id/komiku/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Komiku' - pkgNameSuffix = 'id.komiku' - extClass = '.Komiku' - extVersionCode = 11 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/komiku/res/mipmap-hdpi/ic_launcher.png b/src/id/komiku/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e6c2b3b47..000000000 Binary files a/src/id/komiku/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/komiku/res/mipmap-mdpi/ic_launcher.png b/src/id/komiku/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e261a0933..000000000 Binary files a/src/id/komiku/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/komiku/res/mipmap-xhdpi/ic_launcher.png b/src/id/komiku/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 44824c7c0..000000000 Binary files a/src/id/komiku/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/komiku/res/mipmap-xxhdpi/ic_launcher.png b/src/id/komiku/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b3e79b18b..000000000 Binary files a/src/id/komiku/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/komiku/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/komiku/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1d4a3c3ce..000000000 Binary files a/src/id/komiku/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/komiku/res/web_hi_res_512.png b/src/id/komiku/res/web_hi_res_512.png deleted file mode 100644 index 92b8a2f82..000000000 Binary files a/src/id/komiku/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/komiku/src/eu/kanade/tachiyomi/extension/id/komiku/Komiku.kt b/src/id/komiku/src/eu/kanade/tachiyomi/extension/id/komiku/Komiku.kt deleted file mode 100644 index ef868ec2b..000000000 --- a/src/id/komiku/src/eu/kanade/tachiyomi/extension/id/komiku/Komiku.kt +++ /dev/null @@ -1,289 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.komiku - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Komiku : ParsedHttpSource() { - override val name = "Komiku" - - override val baseUrl = "https://komiku.id" - - override val lang = "id" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // popular - override fun popularMangaSelector() = "div.bge" - - override fun popularMangaRequest(page: Int): Request { - if (page == 1) { - return GET("$baseUrl/other/hot/?orderby=meta_value_num", headers) - } else { - return GET("$baseUrl/other/hot/page/$page/?orderby=meta_value_num", headers) - } - } - - private val coverRegex = Regex("""(/Manga-|/Manhua-|/Manhwa-)""") - private val coverUploadRegex = Regex("""/uploads/\d\d\d\d/\d\d/""") - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.title = element.select("h3").text().trim() - manga.setUrlWithoutDomain(element.select("a:has(h3)").attr("href")) - - // scraped image doesn't make for a good cover; so try to transform it - // make it take bad cover instead of null if it contains upload date as those URLs aren't very useful - if (element.select("img").attr("data-src").contains(coverUploadRegex)) { - manga.thumbnail_url = element.select("img").attr("data-src") - } else { - manga.thumbnail_url = element.select("img").attr("data-src").substringBeforeLast("?").replace(coverRegex, "/Komik-") - } - - return manga - } - - override fun popularMangaNextPageSelector() = ".pag-nav a.next" - - // latest - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesRequest(page: Int): Request { - if (page == 1) { - return GET("$baseUrl/pustaka/?orderby=modified", headers) - } else { - return GET("$baseUrl/pustaka/page/$page/?orderby=modified", headers) - } - } - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // search - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/pustaka/page/$page/")?.newBuilder()!!.addQueryParameter("s", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is CategoryNames -> { - val category = filter.values[filter.state] - url.addQueryParameter("category_name", category.key) - } - is OrderBy -> { - val order = filter.values[filter.state] - url.addQueryParameter("orderby", order.key) - } - is GenreList1 -> { - val genre = filter.values[filter.state] - url.addQueryParameter("genre", genre.key) - } - is GenreList2 -> { - val genre = filter.values[filter.state] - url.addQueryParameter("genre2", genre.key) - } - is StatusList -> { - val status = filter.values[filter.state] - url.addQueryParameter("status", status.key) - } - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = "a.next" - - private class Category(title: String, val key: String) : Filter.TriState(title) { - override fun toString(): String { - return name - } - } - - private class Genre(title: String, val key: String) : Filter.TriState(title) { - override fun toString(): String { - return name - } - } - - private class Order(title: String, val key: String) : Filter.TriState(title) { - override fun toString(): String { - return name - } - } - - private class Status(title: String, val key: String) : Filter.TriState(title) { - override fun toString(): String { - return name - } - } - - private class CategoryNames(categories: Array<Category>) : Filter.Select<Category>("Category", categories, 0) - private class OrderBy(orders: Array<Order>) : Filter.Select<Order>("Order", orders, 0) - private class GenreList1(genres: Array<Genre>) : Filter.Select<Genre>("Genre 1", genres, 0) - private class GenreList2(genres: Array<Genre>) : Filter.Select<Genre>("Genre 2", genres, 0) - private class StatusList(statuses: Array<Status>) : Filter.Select<Status>("Status", statuses, 0) - - override fun getFilterList() = FilterList( - CategoryNames(categoryNames), - OrderBy(orderBy), - GenreList1(genreList), - GenreList2(genreList), - StatusList(statusList) - ) - - private val categoryNames = arrayOf( - Category("All", ""), - Category("Manga", "manga"), - Category("Manhua", "manhua"), - Category("Manhwa", "manhwa") - ) - - private val orderBy = arrayOf( - Order("Ranking", "meta_value_num"), - Order("New Title", "date"), - Order("Updates", "modified"), - Order("Random", "rand") - ) - - private val genreList = arrayOf( - Genre("All", ""), - Genre("Action", "action"), - Genre("Adventure", "adventure"), - Genre("Comedy", "comedy"), - Genre("Cooking", "cooking"), - Genre("Crime", "crime"), - Genre("Demons", "demons"), - Genre("Drama", "drama"), - Genre("Ecchi", "ecchi"), - Genre("Fantasy", "fantasy"), - Genre("Game", "game"), - Genre("Gender Bender", "gender-bender"), - Genre("Harem", "harem"), - Genre("Historical", "historical"), - Genre("Horror", "horror"), - Genre("Isekai", "isekai"), - Genre("Josei", "josei"), - Genre("Magic", "magic"), - Genre("Martial Arts", "martial-arts"), - Genre("Mature", "mature"), - Genre("Mecha", "mecha"), - Genre("Medical", "medical"), - Genre("Military", "military"), - Genre("Music", "music"), - Genre("Mystery", "mystery"), - Genre("One Shot", "one-shot"), - Genre("Overpower", "overpower"), - Genre("Parodi", "parodi"), - Genre("Police", "police"), - Genre("Psychological", "psychological"), - Genre("Reincarnation", "reincarnation"), - Genre("Romance", "romance"), - Genre("School", "school"), - Genre("School life", "school-life"), - Genre("Sci-fi", "sci-fi"), - Genre("Seinen", "seinen"), - Genre("Shotacon", "shotacon"), - Genre("Shoujo", "shoujo"), - Genre("Shoujo Ai", "shoujo-ai"), - Genre("Shounen", "shounen"), - Genre("Shounen Ai", "shounen-ai"), - Genre("Slice of Life", "slice-of-life"), - Genre("Sport", "sport"), - Genre("Sports", "sports"), - Genre("Super Power", "super-power"), - Genre("Supernatural", "supernatural"), - Genre("Thriller", "thriller"), - Genre("Tragedy", "tragedy"), - Genre("Urban", "urban"), - Genre("Vampire", "vampire"), - Genre("Webtoons", "webtoons"), - Genre("Yuri", "yuri") - ) - - private val statusList = arrayOf( - Status("All", ""), - Status("Ongoing", "ongoing"), - Status("End", "end") - ) - - // manga details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - description = document.select("#Sinopsis > p").text().trim() - author = document.select("table.inftable td:contains(Komikus)+td").text() - genre = document.select("li[itemprop=genre] > a").joinToString { it.text() } - status = parseStatus(document.select("table.inftable tr > td:contains(Status) + td").text()) - thumbnail_url = document.select("div.ims > img").attr("abs:src") - - // add series type(manga/manhwa/manhua/other) thinggy to genre - val seriesTypeSelector = "table.inftable tr:contains(Jenis) a, table.inftable tr:has(a[href*=category\\/]) a, a[href*=category\\/]" - document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let { - if (it.isEmpty().not() && genre!!.contains(it, true).not()) { - genre += if (genre!!.isEmpty()) it else ", $it" - } - } - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapters - override fun chapterListSelector() = "#Daftar_Chapter tr:has(td.judulseries)" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("a").text() - - val timeStamp = element.select("td.tanggalseries") - if (timeStamp.text().contains("lalu")) { - date_upload = parseRelativeDate(timeStamp.text().trim()) ?: 0 - } else { - date_upload = parseDate(timeStamp.last()) - } - } - - private fun parseDate(element: Element): Long = SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(element.text())?.time ?: 0 - - // Used Google translate here - private fun parseRelativeDate(date: String): Long? { - val trimmedDate = date.substringBefore(" lalu").removeSuffix("s").split(" ") - - val calendar = Calendar.getInstance() - when (trimmedDate[1]) { - "jam" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) } - "menit" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) } - "detik" -> calendar.apply { add(Calendar.SECOND, 0) } - } - - return calendar.timeInMillis - } - - // pages - override fun pageListParse(document: Document): List<Page> { - return document.select("#Baca_Komik img").mapIndexed { i, element -> - Page(i, "", element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") -} diff --git a/src/id/maidmanga/AndroidManifest.xml b/src/id/maidmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/maidmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/maidmanga/build.gradle b/src/id/maidmanga/build.gradle deleted file mode 100644 index 495deefb4..000000000 --- a/src/id/maidmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Maid - Manga' - pkgNameSuffix = 'id.maidmanga' - extClass = '.MaidManga' - extVersionCode = 7 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/maidmanga/res/mipmap-hdpi/ic_launcher.png b/src/id/maidmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2e3b14344..000000000 Binary files a/src/id/maidmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/maidmanga/res/mipmap-mdpi/ic_launcher.png b/src/id/maidmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 466f51da5..000000000 Binary files a/src/id/maidmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/maidmanga/res/mipmap-xhdpi/ic_launcher.png b/src/id/maidmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 40a2b4045..000000000 Binary files a/src/id/maidmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/maidmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/id/maidmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d93824e21..000000000 Binary files a/src/id/maidmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/maidmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/maidmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 8ae042194..000000000 Binary files a/src/id/maidmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/maidmanga/res/web_hi_res_512.png b/src/id/maidmanga/res/web_hi_res_512.png deleted file mode 100644 index 1c485244f..000000000 Binary files a/src/id/maidmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/maidmanga/src/eu/kanade/tachiyomi/extension/id/maidmanga/MaidManga.kt b/src/id/maidmanga/src/eu/kanade/tachiyomi/extension/id/maidmanga/MaidManga.kt deleted file mode 100644 index 2641981ab..000000000 --- a/src/id/maidmanga/src/eu/kanade/tachiyomi/extension/id/maidmanga/MaidManga.kt +++ /dev/null @@ -1,264 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.maidmanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MaidManga : ParsedHttpSource() { - - override val name = "Maid - Manga" - - override val baseUrl = "https://www.maid.my.id" - - override val lang = "id" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private fun pagePathSegment(page: Int): String = if (page > 1) "page/$page/" else "" - - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/advanced-search/${pagePathSegment(page)}?order=update") - } - - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/advanced-search/${pagePathSegment(page)}?order=popular") - } - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/advanced-search/${pagePathSegment(page)}")!!.newBuilder() - url.addQueryParameter("title", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is AuthorFilter -> { - url.addQueryParameter("author", filter.state) - } - is YearFilter -> { - url.addQueryParameter("yearx", filter.state) - } - is StatusFilter -> { - val status = when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "completed" - Filter.TriState.STATE_EXCLUDE -> "ongoing" - else -> "" - } - url.addQueryParameter("status", status) - } - is TypeFilter -> { - url.addQueryParameter("type", filter.toUriPart()) - } - is OrderByFilter -> { - url.addQueryParameter("order", filter.toUriPart()) - } - is GenreList -> { - filter.state - .filter { it.state } - .forEach { url.addQueryParameter("genre[]", it.id) } - } - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "div.flexbox2-item" - - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - setUrlWithoutDomain(element.select("div.flexbox2-content a").attr("href")) - title = element.select("div.flexbox2-title > span").first().text() - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun searchMangaNextPageSelector() = "div.pagination span.current + a" - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - genre = document.select("div.series-genres a").joinToString { it.text() } - description = document.select("div.series-synops").text() - thumbnail_url = document.select("div.series-thumb img").attr("abs:src") - status = parseStatus(document.select("div.block span.status").text()) - author = document.select("ul.series-infolist li b:contains(Author) + span").text() - - // add series type(manga/manhwa/manhua/other) thinggy to genre - document.select("div.block span.type").firstOrNull()?.ownText()?.let { - if (it.isEmpty().not() && it != "-" && genre!!.contains(it, true).not()) { - genre += if (genre!!.isEmpty()) it else ", $it" - } - } - - // add alternative name to manga description - val altName = "Alternative Name: " - document.select(".series-title span").firstOrNull()?.ownText()?.let { - if (it.isEmpty().not()) { - description += when { - description!!.isEmpty() -> altName + it - else -> "\n\n$altName" + it - } - } - } - } - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("MMM d, yyyy", Locale("id")).parse(date)?.time ?: 0L - } - - // careful not to include download links - override fun chapterListSelector() = "ul.series-chapterlist div.flexch-infoz > a" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.select("span").first().ownText() - date_upload = parseDate(element.select("span.date").text()) - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.reader-area img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList( - Filter.Header("You can combine filter."), - Filter.Separator(), - AuthorFilter(), - YearFilter(), - StatusFilter(), - TypeFilter(), - OrderByFilter(), - GenreList(getGenreList()) - ) - - private class AuthorFilter : Filter.Text("Author") - - private class YearFilter : Filter.Text("Year") - - private class StatusFilter : Filter.TriState("Completed") - - private class TypeFilter : UriPartFilter( - "Type", - arrayOf( - Pair("All", ""), - Pair("Manga", "Manga"), - Pair("Manhua", "Manhua"), - Pair("Manhwa", "Manhwa"), - Pair("One-Shot", "One-Shot"), - Pair("Doujin", "Doujin") - ) - ) - private class OrderByFilter : UriPartFilter( - "Order By", - arrayOf( - Pair("<select>", ""), - Pair("A-Z", "title"), - Pair("Z-A", "titlereverse"), - Pair("Latest Update", "update"), - Pair("Latest Added", "latest"), - Pair("Popular", "popular"), - Pair("Rating", "rating") - ) - ) - - private fun getGenreList() = listOf( - Tag("4-koma", "4-Koma"), - Tag("4-koma-comedy", "4-Koma Comedy"), - Tag("action", "Action"), - Tag("adult", "Adult"), - Tag("adventure", "Adventure"), - Tag("comedy", "Comedy"), - Tag("demons", "Demons"), - Tag("drama", "Drama"), - Tag("ecchi", "Ecchi"), - Tag("fantasy", "Fantasy"), - Tag("game", "Game"), - Tag("gender-bender", "Gender bender"), - Tag("gore", "Gore"), - Tag("harem", "Harem"), - Tag("historical", "Historical"), - Tag("horror", "Horror"), - Tag("isekai", "Isekai"), - Tag("josei", "Josei"), - Tag("loli", "Loli"), - Tag("magic", "Magic"), - Tag("manga", "Manga"), - Tag("manhua", "Manhua"), - Tag("manhwa", "Manhwa"), - Tag("martial-arts", "Martial Arts"), - Tag("mature", "Mature"), - Tag("mecha", "Mecha"), - Tag("military", "Military"), - Tag("monster-girls", "Monster Girls"), - Tag("music", "Music"), - Tag("mystery", "Mystery"), - Tag("one-shot", "One Shot"), - Tag("parody", "Parody"), - Tag("police", "Police"), - Tag("psychological", "Psychological"), - Tag("romance", "Romance"), - Tag("school", "School"), - Tag("school-life", "School Life"), - Tag("sci-fi", "Sci-Fi"), - Tag("socks", "Socks"), - Tag("seinen", "Seinen"), - Tag("shoujo", "Shoujo"), - Tag("shoujo-ai", "Shoujo Ai"), - Tag("shounen", "Shounen"), - Tag("shounen-ai", "Shounen Ai"), - Tag("slice-of-life", "Slice of Life"), - Tag("smut", "Smut"), - Tag("sports", "Sports"), - Tag("super-power", "Super Power"), - Tag("supernatural", "Supernatural"), - Tag("survival", "Survival"), - Tag("thriller", "Thriller"), - Tag("tragedy", "Tragedy"), - Tag("vampire", "Vampire"), - Tag("webtoons", "Webtoons"), - Tag("yuri", "Yuri") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private class Tag(val id: String, name: String) : Filter.CheckBox(name) - - private class GenreList(genres: List<Tag>) : Filter.Group<Tag>("Genres", genres) -} diff --git a/src/id/mangaindo/AndroidManifest.xml b/src/id/mangaindo/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/mangaindo/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/mangaindo/build.gradle b/src/id/mangaindo/build.gradle deleted file mode 100644 index ac69c2104..000000000 --- a/src/id/mangaindo/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manga Indo' - pkgNameSuffix = 'id.mangaindo' - extClass = '.MangaIndo' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/mangaindo/res/mipmap-hdpi/ic_launcher.png b/src/id/mangaindo/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 84c09979d..000000000 Binary files a/src/id/mangaindo/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaindo/res/mipmap-mdpi/ic_launcher.png b/src/id/mangaindo/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 292c253b7..000000000 Binary files a/src/id/mangaindo/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaindo/res/mipmap-xhdpi/ic_launcher.png b/src/id/mangaindo/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9772c4c72..000000000 Binary files a/src/id/mangaindo/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaindo/res/mipmap-xxhdpi/ic_launcher.png b/src/id/mangaindo/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index dddee1cf9..000000000 Binary files a/src/id/mangaindo/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaindo/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/mangaindo/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d55ab708d..000000000 Binary files a/src/id/mangaindo/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaindo/res/web_hi_res_512.png b/src/id/mangaindo/res/web_hi_res_512.png deleted file mode 100644 index 0877a426d..000000000 Binary files a/src/id/mangaindo/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/mangaindo/src/eu/kanade/tachiyomi/extension/id/mangaindo/MangaIndo.kt b/src/id/mangaindo/src/eu/kanade/tachiyomi/extension/id/mangaindo/MangaIndo.kt deleted file mode 100644 index 8a63171a4..000000000 --- a/src/id/mangaindo/src/eu/kanade/tachiyomi/extension/id/mangaindo/MangaIndo.kt +++ /dev/null @@ -1,153 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.mangaindo - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.google.gson.Gson -import com.google.gson.JsonObject -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaIndo : ParsedHttpSource() { - - override val name = "Manga Indo" - - override val baseUrl = "https://mangaindo.web.id" - - override val lang = "id" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/api/cpt/products/?limit=20&page=$page", headers) - } - - override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response) - - override fun popularMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - return MangasPage(super.latestUpdatesParse(response).mangas.distinctBy { it.url }, false) - } - - override fun latestUpdatesSelector() = "li.rpwe-li a" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.text().substringBeforeLast("–").trim() - setUrlWithoutDomain(element.attr("href").substringBeforeLast("-indonesia")) - } - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/api/cpt/products/?limit=20&page=$page&post_title=$query", headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - return gson.fromJson<JsonObject>(response.body()!!.string()).let { json -> - val mangas = json["data"].asJsonArray.map { data -> - SManga.create().apply { - title = data["post"]["post_title"].asString - url = "/${data["post"]["post_name"].asString}/" - thumbnail_url = data["acf"]["m-cover"]["value"].asString - } - } - MangasPage(mangas, json["page"].int < json["total_page"].int && json["data"].asJsonArray.count() == 20) - } - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return document.select("div#main").let { info -> - SManga.create().apply { - title = info.select("h2").text() - author = info.select("span#m-author").text() - artist = info.select("span#m-artist").text() - status = info.select("span#m-status").text().toStatus() - genre = info.select("span#m-genre a").joinToString { it.text() } - description = info.select("span#m-synopsis").text() - thumbnail_url = info.select("div#m-cover img").attr("abs:src") - } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("Ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("Completed", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "ul.lcp_catlist li" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - date_upload = parseChapterDate(element.select("span").text()) - } - } - - private fun parseChapterDate(date: String): Long { - return try { - SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).parse(date)?.time ?: 0L - } catch (e: Exception) { - 0L - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.entry-content img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/id/mangaku/AndroidManifest.xml b/src/id/mangaku/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/mangaku/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/mangaku/build.gradle b/src/id/mangaku/build.gradle deleted file mode 100644 index 91a035566..000000000 --- a/src/id/mangaku/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangaku' - pkgNameSuffix = 'id.mangaku' - extClass = '.Mangaku' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/mangaku/res/mipmap-hdpi/ic_launcher.png b/src/id/mangaku/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index c8f53fb05..000000000 Binary files a/src/id/mangaku/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaku/res/mipmap-mdpi/ic_launcher.png b/src/id/mangaku/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index 800a8179f..000000000 Binary files a/src/id/mangaku/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaku/res/mipmap-xhdpi/ic_launcher.png b/src/id/mangaku/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 67d77e480..000000000 Binary files a/src/id/mangaku/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaku/res/mipmap-xxhdpi/ic_launcher.png b/src/id/mangaku/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index cce3c5d1d..000000000 Binary files a/src/id/mangaku/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaku/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/mangaku/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 1ba67d887..000000000 Binary files a/src/id/mangaku/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangaku/res/web_hi_res_512.png b/src/id/mangaku/res/web_hi_res_512.png deleted file mode 100755 index 6cd7c00f5..000000000 Binary files a/src/id/mangaku/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/mangaku/src/eu/kanade/tachiyomi/extension/id/mangaku/Mangaku.kt b/src/id/mangaku/src/eu/kanade/tachiyomi/extension/id/mangaku/Mangaku.kt deleted file mode 100644 index fbb9872cc..000000000 --- a/src/id/mangaku/src/eu/kanade/tachiyomi/extension/id/mangaku/Mangaku.kt +++ /dev/null @@ -1,172 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.mangaku - -import android.annotation.SuppressLint -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class Mangaku : ParsedHttpSource() { - override val name = "Mangaku" - override val baseUrl = "https://mangaku.in/" - override val lang = "id" - override val supportsLatest = true - private var searchQuery = "" - - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl + "daftar-komik-bahasa-indonesia/", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - searchQuery = query - return GET(baseUrl + "daftar-komik-bahasa-indonesia/", headers) - } - - override fun popularMangaSelector() = "a.screenshot" - override fun latestUpdatesSelector() = "div.kiri_anime div.utao" - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.attr("rel") - manga.url = element.attr("href") - manga.title = element.text() - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.uta div.imgu img").attr("src") - - val mangaUrl = element.select("div.uta div.luf a.series").attr("href").replace("hhtps", "http") - manga.url = mangaUrl.replace("hhtps", "https") - manga.title = element.select("div.uta div.luf a.series").text() - return manga - } - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(manga.url, headers) - } - - @SuppressLint("DefaultLocale") - override fun mangaDetailsParse(document: Document): SManga { - val infoString = document.select("#abc > p > span > small").html().toString().split("<br> ") - val manga = SManga.create() - infoString.forEach { - if (it.contains("</b>")) { - val info = it.split("</b>") - val key = info[0].replace(":", "").replace("<b>", "").trim().toLowerCase() - val value = info[1].replace(":", "").trim() - when (key) { - "genre" -> manga.genre = value.replace("–", ", ").replace("-", ", ").trim() - "author" -> manga.author = value - "artist" -> manga.artist = value - "sinopsis" -> manga.description = value - } - } - } - manga.status = SManga.UNKNOWN - manga.thumbnail_url = document.select("#abc > div > span > small > a > img").attr("src") - return manga - } - - override fun chapterListRequest(manga: SManga): Request { - return GET(manga.url, headers) - } - - override fun chapterListSelector() = "div.entry > div > table > tbody > tr > td:nth-child(1) > small > div:nth-child(2) > a" - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - val chapterUrl = element.attr("href") - chapter.url = chapterUrl - chapter.name = element.text() - if (chapter.name.contains("–")) - chapter.name = chapter.name.split("–")[1].trim() - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""Chapter\s([0-9]+)""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[1]?.value!!.toFloat() - } - } - } - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET(chapter.url, headers) - } - - override fun pageListParse(document: Document): List<Page> { - var pageList = document.select("div.entry img") - - if (pageList.isEmpty()) { - pageList = document.select("div.entry-content img") - } - - val pages = mutableListOf<Page>() - var i = 0 - pageList.forEach { element -> - val imageUrl = element.attr("src") - i++ - if (imageUrl.isNotEmpty()) { - pages.add(Page(i, "", imageUrl)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - override fun imageRequest(page: Page): Request { - var mainUrl = baseUrl - var imageUrl = page.imageUrl.toString() - if (imageUrl.contains("mangaku.co")) { - mainUrl = "https://mangaku.co" - } - if (imageUrl.startsWith("//")) { - imageUrl = "https:$imageUrl" - } else if (imageUrl.startsWith("/")) { - imageUrl = mainUrl + imageUrl - } - val imgHeader = Headers.Builder().apply { - add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36") - add("referer", mainUrl) - }.build() - return GET(imageUrl, imgHeader) - } - - override fun popularMangaNextPageSelector() = "next" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - @SuppressLint("DefaultLocale") - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = arrayListOf<SManga>() - document.select(searchMangaSelector()).forEach { element -> - val manga = popularMangaFromElement(element) - if (manga.title.toLowerCase().contains(searchQuery.toLowerCase())) { - mangas.add(manga) - } - } - return MangasPage(mangas, false) - } -} diff --git a/src/id/mangayu/AndroidManifest.xml b/src/id/mangayu/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/mangayu/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/mangayu/build.gradle b/src/id/mangayu/build.gradle deleted file mode 100644 index 3591cafcb..000000000 --- a/src/id/mangayu/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaYu' - pkgNameSuffix = 'id.mangayu' - extClass = '.MangaYu' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/mangayu/res/mipmap-hdpi/ic_launcher.png b/src/id/mangayu/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4a325ef55..000000000 Binary files a/src/id/mangayu/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangayu/res/mipmap-mdpi/ic_launcher.png b/src/id/mangayu/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5743b1405..000000000 Binary files a/src/id/mangayu/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangayu/res/mipmap-xhdpi/ic_launcher.png b/src/id/mangayu/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index fdce7bdb5..000000000 Binary files a/src/id/mangayu/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangayu/res/mipmap-xxhdpi/ic_launcher.png b/src/id/mangayu/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9a504a615..000000000 Binary files a/src/id/mangayu/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangayu/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/mangayu/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 866c48a60..000000000 Binary files a/src/id/mangayu/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/mangayu/res/web_hi_res_512.png b/src/id/mangayu/res/web_hi_res_512.png deleted file mode 100644 index ca65ee0bb..000000000 Binary files a/src/id/mangayu/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/mangayu/src/eu/kanade/tachiyomi/extension/id/mangayu/MangaYu.kt b/src/id/mangayu/src/eu/kanade/tachiyomi/extension/id/mangayu/MangaYu.kt deleted file mode 100644 index b10343c66..000000000 --- a/src/id/mangayu/src/eu/kanade/tachiyomi/extension/id/mangayu/MangaYu.kt +++ /dev/null @@ -1,209 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.mangayu - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class MangaYu : ParsedHttpSource() { - - override val name = "MangaYu" - override val baseUrl = "https://mangayu.com" - override val lang = "id" - override val supportsLatest = true - private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.US) - - protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src") - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/manga?order_by=views&page=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/manga?order_by=latest&page=$page", headers) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/manga?")!!.newBuilder() - .addQueryParameter("search", query) - .addQueryParameter("page", page.toString()) - val newUrl = null - filters.forEach { filter -> - when (filter) { - is SortByFilter -> { - url.addQueryParameter("order_by", filter.toUriPart()) - } - // TODO GENRE with $baseUrl/genre - } - } - return GET(url.toString(), headers) - } - - override fun popularMangaSelector() = ".row .col-md-8 .row .col-md-6" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select(".detail a.link").attr("href")) - manga.title = element.select(".detail a.link").text() - manga.thumbnail_url = element.select(".cover a img").attr("src") - - return manga - } - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.page-link[rel=next]" - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - author = document.select("tr:contains(Author) td:nth-child(2)").text() - artist = document.select("tr:contains(Artist) td:nth-child(2)").text() - status = parseStatus(document.select("tr:contains(Status) td").firstOrNull()?.ownText()) - description = document.select("div.card-body h5 ~ p").text() - genre = document.select("tr:contains(Genre) a").joinToString { it.text() } - } - - protected fun parseStatus(element: String?): Int = when { - element == null -> SManga.UNKNOWN - listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING - listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.list-group-item a" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.select("div.d-flex").text() - date_upload = parseChapterDate(element.select("span.text-white-50").text()) ?: 0 - } - - fun parseChapterDate(date: String): Long { - return if (date.contains("yang lalu")) { - val value = date.split(' ')[0].toInt() - when { - "menit" in date -> Calendar.getInstance().apply { - add(Calendar.MINUTE, value * -1) - }.timeInMillis - "jam" in date -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, value * -1) - }.timeInMillis - "hari" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * -1) - }.timeInMillis - "minggu" in date -> Calendar.getInstance().apply { - add(Calendar.DATE, value * 7 * -1) - }.timeInMillis - "bulan" in date -> Calendar.getInstance().apply { - add(Calendar.MONTH, value * -1) - }.timeInMillis - "tahun" in date -> Calendar.getInstance().apply { - add(Calendar.YEAR, value * -1) - }.timeInMillis - else -> { - 0L - } - } - } else { - try { - dateFormat.parse(date)?.time ?: 0 - } catch (_: Exception) { - 0L - } - } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - var i = 0 - document.select(".chapter-image img").forEach { element -> - val url = element.attr("src") - i++ - if (url.isNotEmpty()) { - pages.add(Page(i, "", url)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - private class SortByFilter : UriPartFilter( - "Sort By", - arrayOf( - Pair("Default", ""), - Pair("A-Z", "name"), - Pair("Latest Update", "latest"), - Pair("Latest Added", "new"), - Pair("Popular", "views") - ) - ) - - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreListFilter(genres: List<Genre>) : Filter.Group<Genre>("Genre", genres) - - override fun getFilterList() = FilterList( - SortByFilter() - ) - -// need to add thing to search filter for genre - private fun getGenreList() = listOf( - Genre("Action", "action"), - Genre("Adventure", "adventure"), - Genre("Comedy", "comedy"), - Genre("Doujinshi", "doujinshi"), - Genre("Drama", "drama"), - Genre("Ecchi", "ecchi"), - Genre("Fantasy", "fantasy"), - Genre("Gender Bender", "gender-bender"), - Genre("Harem", "harem"), - Genre("Historical", "historical"), - Genre("Horror", "horror"), - Genre("Isekai", "isekai"), - Genre("Josei", "josei"), - Genre("Martial Arts", "martial-arts"), - Genre("Mature", "mature"), - Genre("Mecha", "mecha"), - Genre("Mystery", "mystery"), - Genre("One-shot", "one-shot"), - Genre("Psychological", "psychological"), - Genre("Reincarnation", "reincarnation"), - Genre("Romance", "romance"), - Genre("School Life", "school-life"), - Genre("Sci-fi", "sci-fi"), - Genre("Seinen", "seinen"), - Genre("Shoujo", "shoujo"), - Genre("Shoujo Ai", "shoujo-ai"), - Genre("Shounen", "shounen"), - Genre("Shounen Ai", "shounen-ai"), - Genre("Slice of Life", "slice-of-life"), - Genre("Sports", "sports"), - Genre("Supernatural", "supernatural"), - Genre("Tragedy", "tragedy"), - Genre("Yaoi", "yaoi"), - Genre("Yuri", "yuri"), - Genre("Loli", "loli"), - Genre("Game", "game"), - Genre("Medical", "medical"), - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/id/manhuaid/AndroidManifest.xml b/src/id/manhuaid/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/manhuaid/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/manhuaid/build.gradle b/src/id/manhuaid/build.gradle deleted file mode 100644 index 2f0ba6b2b..000000000 --- a/src/id/manhuaid/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ManhuaID' - pkgNameSuffix = 'id.manhuaid' - extClass = '.ManhuaID' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/manhuaid/res/mipmap-hdpi/ic_launcher.png b/src/id/manhuaid/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 029f2005f..000000000 Binary files a/src/id/manhuaid/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/manhuaid/res/mipmap-mdpi/ic_launcher.png b/src/id/manhuaid/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f9f09d443..000000000 Binary files a/src/id/manhuaid/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/manhuaid/res/mipmap-xhdpi/ic_launcher.png b/src/id/manhuaid/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 50590e34c..000000000 Binary files a/src/id/manhuaid/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/manhuaid/res/mipmap-xxhdpi/ic_launcher.png b/src/id/manhuaid/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 159d22574..000000000 Binary files a/src/id/manhuaid/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/manhuaid/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/manhuaid/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ff777adfd..000000000 Binary files a/src/id/manhuaid/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/manhuaid/res/web_hi_res_512.png b/src/id/manhuaid/res/web_hi_res_512.png deleted file mode 100644 index 10a7793f6..000000000 Binary files a/src/id/manhuaid/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/manhuaid/src/eu/kanade/tachiyomi/extension/id/manhuaid/ManhuaID.kt b/src/id/manhuaid/src/eu/kanade/tachiyomi/extension/id/manhuaid/ManhuaID.kt deleted file mode 100644 index 0c6f589b6..000000000 --- a/src/id/manhuaid/src/eu/kanade/tachiyomi/extension/id/manhuaid/ManhuaID.kt +++ /dev/null @@ -1,117 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.manhuaid - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class ManhuaID : ParsedHttpSource() { - - override val name = "ManhuaID" - - override val baseUrl = "https://manhuaid.com" - - override val lang = "id" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // popular - override fun popularMangaSelector() = "a:has(img.card-img)" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/popular/$page", headers) - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.select("img").attr("alt") - thumbnail_url = element.select("img").attr("src") - } - - override fun popularMangaNextPageSelector() = "[rel=nofollow]" - - // latest - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/latest/$page", headers) - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // search - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/search?q=$query", headers) - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - // manga details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - author = document.select("table").first().select("td")[3].text() - title = document.select("h1").text() - description = document.select(".text-justify").text() - genre = document.select("span.badge.badge-success.mr-1.mb-1").joinToString { it.text() } - status = document.select("td > span.badge.badge-success").text().let { - parseStatus(it) - } - thumbnail_url = document.select("img.img-fluid").attr("abs:src") - - // add series type(manga/manhwa/manhua/other) thinggy to genre - document.select("table tr:contains(Type) a, table a[href*=type]").firstOrNull()?.ownText()?.let { - if (it.isEmpty().not() && genre!!.contains(it, true).not()) { - genre += if (genre!!.isEmpty()) it else ", $it" - } - } - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapters - override fun chapterListSelector() = "table.table tr td:first-of-type a" - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) } - - // Add timestamp to latest chapter, taken from "Updated On". so source which not provide chapter timestamp will have atleast one - val date = document.select("table tr:contains(update) td").text() - val checkChapter = document.select(chapterListSelector()).firstOrNull() - if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date) - - return chapters - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.text() - } - - // pages - override fun pageListParse(document: Document): List<Page> { - return document.select("img.img-fluid.mb-0.mt-0").mapIndexed { i, element -> - Page(i, "", element.attr("src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") -} diff --git a/src/id/neumanga/AndroidManifest.xml b/src/id/neumanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/neumanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/neumanga/build.gradle b/src/id/neumanga/build.gradle deleted file mode 100644 index 0923f6d8e..000000000 --- a/src/id/neumanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Neumanga' - pkgNameSuffix = 'id.neumanga' - extClass = '.Neumanga' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/neumanga/res/mipmap-hdpi/ic_launcher.png b/src/id/neumanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 690a9c3fc..000000000 Binary files a/src/id/neumanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/neumanga/res/mipmap-mdpi/ic_launcher.png b/src/id/neumanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index eca876f71..000000000 Binary files a/src/id/neumanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/neumanga/res/mipmap-xhdpi/ic_launcher.png b/src/id/neumanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 18b3a8784..000000000 Binary files a/src/id/neumanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/neumanga/res/mipmap-xxhdpi/ic_launcher.png b/src/id/neumanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 64734d04a..000000000 Binary files a/src/id/neumanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/neumanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/neumanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index eb7ba7b61..000000000 Binary files a/src/id/neumanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/neumanga/res/web_hi_res_512.png b/src/id/neumanga/res/web_hi_res_512.png deleted file mode 100644 index a4b465378..000000000 Binary files a/src/id/neumanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/neumanga/src/eu/kanade/tachiyomi/extension/id/neumanga/Neumanga.kt b/src/id/neumanga/src/eu/kanade/tachiyomi/extension/id/neumanga/Neumanga.kt deleted file mode 100644 index 5547a0a95..000000000 --- a/src/id/neumanga/src/eu/kanade/tachiyomi/extension/id/neumanga/Neumanga.kt +++ /dev/null @@ -1,251 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.neumanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.json.JSONArray -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.security.SecureRandom -import java.security.cert.X509Certificate -import javax.net.ssl.SSLContext -import javax.net.ssl.X509TrustManager - -class Neumanga : ParsedHttpSource() { - - override val name = "Neumanga" - - override val baseUrl = "https://neumanga.tv" - - override val lang = "id" - - override val supportsLatest = true - - private val trustManager = object : X509TrustManager { - override fun getAcceptedIssuers(): Array<X509Certificate> { - return emptyArray() - } - - override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { - } - - override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { - } - } - - private val sslContext = SSLContext.getInstance("SSL").apply { - init(null, arrayOf(trustManager), SecureRandom()) - } - - override val client = super.client.newBuilder() - .sslSocketFactory(sslContext.socketFactory, trustManager) - .build() - - override fun popularMangaSelector() = "div#gov-result div.bolx" - - override fun latestUpdatesSelector() = "div#gov-result div.bolx" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/advanced_search?sortby=rating&advpage=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/advanced_search?sortby=latest&advpage=$page", headers) - } - - private fun mangaFromElement(query: String, element: Element): SManga { - val manga = SManga.create() - element.select(query).first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun popularMangaFromElement(element: Element): SManga { - return mangaFromElement("h2 a", element) - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return mangaFromElement("h2 a", element) - } - - override fun popularMangaNextPageSelector() = "div#gov-result ul.pagination li.active + li a" - - override fun latestUpdatesNextPageSelector() = "div#gov-result ul.pagination li.active + li a" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/advanced_search")!!.newBuilder() - .addQueryParameter("advpage", page.toString()) - .addQueryParameter("name_search_mode", "contain") - .addQueryParameter("artist_search_mode", "contain") - .addQueryParameter("author_search_mode", "contain") - .addQueryParameter("year_search_mode", "on") - .addQueryParameter("rating_search_mode", "is") - .addQueryParameter("name_search_query", query) - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Status -> url.addQueryParameter("manga_status", arrayOf("", "completed", "ongoing")[filter.state]) - is GenreList -> { - val genreInclude = mutableListOf<String>() - val genreExclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } else if (it.state == 2) { - genreExclude.add(it.id) - } - } - url.addQueryParameter("genre1", JSONArray(genreInclude).toString()) - url.addQueryParameter("genre2", JSONArray(genreExclude).toString()) - } - is SelectField -> url.addQueryParameter(filter.key, filter.values[filter.state]) - is TextField -> url.addQueryParameter(filter.key, filter.state) - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "div#gov-result div.bolx" - - override fun searchMangaFromElement(element: Element): SManga { - return mangaFromElement("h2 a", element) - } - - override fun searchMangaNextPageSelector() = "div#gov-result ul.pagination li.active + li a" - - override fun mangaDetailsParse(document: Document): SManga { - val mangaInformationWrapper = document.select("#main .info").first() - - val manga = SManga.create() - manga.author = mangaInformationWrapper.select("span a[href*=author_search_mode]").first().text() - manga.artist = mangaInformationWrapper.select("span a[href*=artist_search_mode]").first().text() - manga.genre = mangaInformationWrapper.select("a[href*=genre]").joinToString { it.text() } - manga.thumbnail_url = mangaInformationWrapper.select("img.imagemg").first().attr("src") - manga.description = document.select(".summary").first().textNodes()[1].toString() - manga.status = parseStatus(mangaInformationWrapper.select("span a[href*=manga_status]").first().text()) - - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("ongoing") -> SManga.ONGOING - status.contains("completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = ".chapter .item:first-child .item-content a" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.attr("href") + "/1") - chapter.name = element.select("h3").text() - return chapter - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - document.select(".readnav select.page").first()?.getElementsByTag("option")?.forEach { - pages.add(Page(pages.size, it.attr("value"))) - } - pages.getOrNull(0)?.imageUrl = imageUrlParse(document) - return pages - } - - override fun imageUrlParse(document: Document) = document.select(".readarea img.imagechap").attr("src") - - private class Status : Filter.TriState("Completed") - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - private class SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state) - - override fun getFilterList() = FilterList( - SelectField("Sort", "sortby", arrayOf("rating", "name", "views", "latest")), - TextField("Author", "author_search_query"), - TextField("Artist", "artist_search_query"), - TextField("Release Year", "year_value"), - Status(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("Adventure", "Adventure"), - Genre("Demons", "Demons"), - Genre("fighting", "fighting"), - Genre("Horor", "Horor"), - Genre("legend", "legend"), - Genre("Manhua", "Manhua"), - Genre("Mecha", "Mecha"), - Genre("Romance", "Romance"), - Genre("neco", "neco"), - Genre("Seinen", "Seinen"), - Genre("Slice Of Life", "Slice Of Life"), - Genre("Superhero", "Superhero"), - Genre("Tragedy", "Tragedy"), - Genre("Vampire", "Vampire"), - Genre("Supernatural", "Supernatural"), - Genre("Shoujo", "Shoujo"), - Genre("Smut", "Smut"), - Genre("School", "School"), - Genre("Oneshot", "Oneshot"), - Genre("Miatery", "Miatery"), - Genre("Manhwa", "Manhwa"), - Genre("live School", "live School"), - Genre("Horror", "Horror"), - Genre("Game", "Game"), - Genre("Antihero", "Antihero"), - Genre("Action", "Action"), - Genre("Comedy", "Comedy"), - Genre("Drama", "Drama"), - Genre("Ecchi", "Ecchi"), - Genre("Action. Adventure", "Action. Adventure"), - Genre("Gender Bender", "Gender Bender"), - Genre("Inaka", "Inaka"), - Genre("Lolicon", "Lolicon"), - Genre("Adult", "Adult"), - Genre("Cooking", "Cooking"), - Genre("Harem", "Harem"), - Genre("Isekai", "Isekai"), - Genre("Magic", "Magic"), - Genre("Music", "Music"), - Genre("Martial Arts", "Martial Arts"), - Genre("Project", "Project"), - Genre("sci fi", "sci fi"), - Genre("Shounen", "Shounen"), - Genre("Military", "Military"), - Genre("Martial Art", "Martial Art"), - Genre("Over Power", "Over Power"), - Genre("School Life", "School Life"), - Genre("Shoujo Ai", "Shoujo Ai"), - Genre("sport", "sport"), - Genre("Supranatural", "Supranatural"), - Genre("Webtoon", "Webtoon"), - Genre("Webtoons", "Webtoons"), - Genre("Suspense", "Suspense"), - Genre("Sports", "Sports"), - Genre("Yuri", "Yuri"), - Genre("Thriller", "Thriller"), - Genre("Super Power", "Super Power"), - Genre("ShounenS", "ShounenS"), - Genre("Sci-fi", "Sci-fi"), - Genre("Psychological", "Psychological"), - Genre("Mystery", "Mystery"), - Genre("Mature", "Mature"), - Genre("Manga", "Manga"), - Genre("Josei", "Josei"), - Genre("Historical", "Historical"), - Genre("Fantasy", "Fantasy"), - Genre("Dachima", "Dachima"), - Genre("Advanture", "Advanture"), - Genre("Echi", "Echi"), - Genre("4-Koma", "4-Koma") - ) -} diff --git a/src/id/nyanfm/AndroidManifest.xml b/src/id/nyanfm/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/id/nyanfm/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/id/nyanfm/build.gradle b/src/id/nyanfm/build.gradle deleted file mode 100644 index bd3639e40..000000000 --- a/src/id/nyanfm/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Nyan FM' - pkgNameSuffix = 'id.nyanfm' - extClass = '.NyanFM' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/id/nyanfm/res/mipmap-hdpi/ic_launcher.png b/src/id/nyanfm/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b81cd866b..000000000 Binary files a/src/id/nyanfm/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/nyanfm/res/mipmap-mdpi/ic_launcher.png b/src/id/nyanfm/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 901c04c0b..000000000 Binary files a/src/id/nyanfm/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/nyanfm/res/mipmap-xhdpi/ic_launcher.png b/src/id/nyanfm/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 350cd216f..000000000 Binary files a/src/id/nyanfm/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/nyanfm/res/mipmap-xxhdpi/ic_launcher.png b/src/id/nyanfm/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7097bc876..000000000 Binary files a/src/id/nyanfm/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/nyanfm/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/nyanfm/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f2490b066..000000000 Binary files a/src/id/nyanfm/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/id/nyanfm/res/web_hi_res_512.png b/src/id/nyanfm/res/web_hi_res_512.png deleted file mode 100644 index bf33aeafc..000000000 Binary files a/src/id/nyanfm/res/web_hi_res_512.png and /dev/null differ diff --git a/src/id/nyanfm/src/eu/kanade/tachiyomi/extension/id/nyanfm/NyanFM.kt b/src/id/nyanfm/src/eu/kanade/tachiyomi/extension/id/nyanfm/NyanFM.kt deleted file mode 100644 index 16659b329..000000000 --- a/src/id/nyanfm/src/eu/kanade/tachiyomi/extension/id/nyanfm/NyanFM.kt +++ /dev/null @@ -1,120 +0,0 @@ -package eu.kanade.tachiyomi.extension.id.nyanfm - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class NyanFM : ParsedHttpSource() { - - override val name = "Nyan FM" - override val baseUrl = "https://nyanfm.com" - override val lang = "id" - override val supportsLatest = false - override val client: OkHttpClient = network.cloudflareClient - - // popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/scanlation/", headers) - } - - override fun popularMangaSelector() = ".elementor-column .elementor-widget-wrap .elementor-widget-container .elementor-cta:has(.elementor-cta__bg)" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select(".elementor-cta__bg-wrapper .elementor-cta__bg").attr("style").substringAfter("(").substringBefore(")") - - manga.url = element.select(".elementor-cta__content a.elementor-button").attr("href").substringAfter(".com") - manga.title = element.select(".elementor-cta__content h2").text().trim() - return manga - } - - override fun popularMangaNextPageSelector() = "#nav-below .nav-links .next.page-numbers" - - // latest - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException() - - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException() - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException() - - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException() - - // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("https://nyanfm.com/page/$page/?s=$query") - } - - override fun searchMangaSelector() = "#main article .inside-article:has(.entry-summary:has(a))" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select(".post-image img").attr("src") - element.select(".entry-summary:has(p) a").let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun searchMangaNextPageSelector() = ".nav-links .next" - - // manga details - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - document.select(".entry-content div.elementor-section-wrap").firstOrNull()?.let { infoElement -> - thumbnail_url = infoElement.select("div.elementor-widget-wrap div.elementor-element.elementor-hidden-tablet.elementor-widget img").attr("src") - title = infoElement.select("h1").text().trim() - artist = infoElement.select("div.elementor-element:has(h3:contains(author)) + div.elementor-element p").firstOrNull()?.ownText() - status = parseStatus(infoElement.select("div.elementor-element:has(h3:contains(status)) + div.elementor-element p").text()) - genre = infoElement.select("div.elementor-element:has(h3:contains(genre)) + div.elementor-element p").joinToString(", ") { it.text() } - description = infoElement.select("div.elementor-element:has(h3:contains(sinopsis)) + div.elementor-element p").joinToString("\n") { it.text() } - } - } - } - - private fun parseStatus(element: String): Int = when { - element.toLowerCase().contains("ongoing") -> SManga.ONGOING - element.toLowerCase().contains("completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // chapters - override fun chapterListSelector() = "table tbody tr" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - element.select("td:last-child a").let { - name = it.text() - url = it.attr("href").substringAfter(baseUrl) - } - date_upload = parseChapterDate(element.select("td:first-child").text()) - } - - private fun parseChapterDate(date: String): Long { - return SimpleDateFormat("dd-MMM-yy", Locale.ENGLISH).parse(date)?.time ?: 0L - } - - // pages - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - var i = 0 - document.select("div.elementor-widget-container > div.elementor-image a > img.attachment-large.size-large").forEach { element -> - val url = element.attr("src") - i++ - if (url.isNotEmpty()) { - pages.add(Page(i, "", url)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" -} diff --git a/src/it/digitalteam/AndroidManifest.xml b/src/it/digitalteam/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/it/digitalteam/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/it/digitalteam/build.gradle b/src/it/digitalteam/build.gradle deleted file mode 100644 index e4c949305..000000000 --- a/src/it/digitalteam/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'DigitalTeam' - pkgNameSuffix = 'it.digitalteam' - extClass = '.DigitalTeam' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = false -} - -apply from: "$rootDir/common.gradle" diff --git a/src/it/digitalteam/res/mipmap-hdpi/ic_launcher.png b/src/it/digitalteam/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7aab58562..000000000 Binary files a/src/it/digitalteam/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/digitalteam/res/mipmap-mdpi/ic_launcher.png b/src/it/digitalteam/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3f097eac9..000000000 Binary files a/src/it/digitalteam/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/digitalteam/res/mipmap-xhdpi/ic_launcher.png b/src/it/digitalteam/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5d1f1d7d4..000000000 Binary files a/src/it/digitalteam/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/digitalteam/res/mipmap-xxhdpi/ic_launcher.png b/src/it/digitalteam/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 0b3a2691b..000000000 Binary files a/src/it/digitalteam/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/digitalteam/res/mipmap-xxxhdpi/ic_launcher.png b/src/it/digitalteam/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4750f78a2..000000000 Binary files a/src/it/digitalteam/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/digitalteam/res/web_hi_res_512.png b/src/it/digitalteam/res/web_hi_res_512.png deleted file mode 100644 index d05fa6056..000000000 Binary files a/src/it/digitalteam/res/web_hi_res_512.png and /dev/null differ diff --git a/src/it/digitalteam/src/eu/kanade/tachiyomi/extension/it/digitalteam/DigitalTeam.kt b/src/it/digitalteam/src/eu/kanade/tachiyomi/extension/it/digitalteam/DigitalTeam.kt deleted file mode 100644 index b109f6920..000000000 --- a/src/it/digitalteam/src/eu/kanade/tachiyomi/extension/it/digitalteam/DigitalTeam.kt +++ /dev/null @@ -1,139 +0,0 @@ -package eu.kanade.tachiyomi.extension.it.digitalteam - -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -class DigitalTeam : ParsedHttpSource() { - - override val name = "DigitalTeam" - override val baseUrl = "https://dgtread.com" - override val lang = "it" - override val supportsLatest = false - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/reader/series", headers) - } - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("La ricerca è momentaneamente disabilitata.") - - // LIST SELECTOR - override fun popularMangaSelector() = "ul li.manga_block" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - - // ELEMENT - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").attr("src") - manga.setUrlWithoutDomain(element.select(".manga_title a").first().attr("href")) - manga.title = element.select(".manga_title a").text() - return manga - } - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not Used") - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not Used") - - // NEXT SELECTOR - // Not needed - override fun popularMangaNextPageSelector(): String? = null - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - // //////////////// - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("#manga_left") - val manga = SManga.create() - manga.author = infoElement.select(".info_name:contains(Autore)").next()?.text() - manga.artist = infoElement.select(".info_name:contains(Artista)").next()?.text() - manga.genre = infoElement.select(".info_name:contains(Genere)").next()?.text() - manga.status = parseStatus(infoElement.select(".info_name:contains(Status)").next().text()) - manga.description = document.select("div.plot")?.text() - manga.thumbnail_url = infoElement.select(".cover img").attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.toLowerCase().contains("in corso") -> SManga.ONGOING - element.toLowerCase().contains("completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = ".chapter_list ul li" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select(".ch_bottom").first()?.text()?.replace("Pubblicato il ", "")?.let { - try { - SimpleDateFormat("dd-MM-yyyy", Locale.ITALY).parse(it).time - } catch (e: ParseException) { - SimpleDateFormat("H", Locale.ITALY).parse(it).time - } - } ?: 0 - return chapter - } - - protected fun getXhrPages(script_content: String, title: String): String { - val xhrHeaders = headersBuilder().add("Content-Type: application/x-www-form-urlencoded; charset=UTF-8") - .build() - - // This can be improved, i don't know how to do it with Regex - var infomanga = script_content.substringAfter("m='").substringBefore("'") - var infochapter = script_content.substringAfter("ch='").substringBefore("'") - var infoch_sub = script_content.substringAfter("chs='").substringBefore("'") - val body = RequestBody.create(null, "info[manga]=$infomanga&info[chapter]=$infochapter&info[ch_sub]=$infoch_sub&info[title]=$title") - return client.newCall(POST("$baseUrl/reader/c_i", xhrHeaders, body)) - .execute() - .asJsoup().select("body").text() - .replace("\\", "").removeSurrounding("\"") - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val script_content = document.body()!!.toString().substringAfter("current_page=").substringBefore(";") - val title = document.select("title").first().text() - val imagesJsonList = JsonParser().parse(getXhrPages(script_content, title)).asJsonArray - val image_url = imagesJsonList.get(2).string - val images_name = imagesJsonList.get(1).asJsonArray - val images_data = imagesJsonList.get(0).asJsonArray - images_name.forEachIndexed { index, imagename -> - val imageUrl = - "$baseUrl/reader$image_url" + - "${images_data.get(index).asJsonObject.get("name").string}" + - "${imagename.string}" + - "${images_data.get(index).asJsonObject.get("ex").string}" - pages.add(Page(index, "", imageUrl)) - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } -} diff --git a/src/it/hentaifantasy/AndroidManifest.xml b/src/it/hentaifantasy/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/it/hentaifantasy/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/it/hentaifantasy/build.gradle b/src/it/hentaifantasy/build.gradle deleted file mode 100644 index 123131d82..000000000 --- a/src/it/hentaifantasy/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HentaiFantasy' - pkgNameSuffix = 'it.hentaifantasy' - extClass = '.HentaiFantasy' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/it/hentaifantasy/res/mipmap-hdpi/ic_launcher.png b/src/it/hentaifantasy/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 59bb1158e..000000000 Binary files a/src/it/hentaifantasy/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/hentaifantasy/res/mipmap-mdpi/ic_launcher.png b/src/it/hentaifantasy/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 873ab3175..000000000 Binary files a/src/it/hentaifantasy/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/hentaifantasy/res/mipmap-xhdpi/ic_launcher.png b/src/it/hentaifantasy/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 84569d5fa..000000000 Binary files a/src/it/hentaifantasy/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/hentaifantasy/res/mipmap-xxhdpi/ic_launcher.png b/src/it/hentaifantasy/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 035ae99e3..000000000 Binary files a/src/it/hentaifantasy/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/hentaifantasy/res/mipmap-xxxhdpi/ic_launcher.png b/src/it/hentaifantasy/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 82cdf3787..000000000 Binary files a/src/it/hentaifantasy/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/hentaifantasy/res/web_hi_res_512.png b/src/it/hentaifantasy/res/web_hi_res_512.png deleted file mode 100644 index ef573885a..000000000 Binary files a/src/it/hentaifantasy/res/web_hi_res_512.png and /dev/null differ diff --git a/src/it/hentaifantasy/src/eu/kanade/tachiyomi/extension/it/hentaifantasy/HentaiFantasy.kt b/src/it/hentaifantasy/src/eu/kanade/tachiyomi/extension/it/hentaifantasy/HentaiFantasy.kt deleted file mode 100644 index 47f208d38..000000000 --- a/src/it/hentaifantasy/src/eu/kanade/tachiyomi/extension/it/hentaifantasy/HentaiFantasy.kt +++ /dev/null @@ -1,256 +0,0 @@ -package eu.kanade.tachiyomi.extension.it.hentaifantasy - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.regex.Pattern - -@Nsfw -class HentaiFantasy : ParsedHttpSource() { - override val name = "HentaiFantasy" - - override val baseUrl = "https://www.hentaifantasy.it/index.php" - - override val lang = "it" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - companion object { - val pagesUrlPattern by lazy { - Pattern.compile("""\"url\":\"(.*?)\"""") - } - - val dateFormat by lazy { - SimpleDateFormat("yyyy.MM.dd") - } - } - - override fun popularMangaSelector() = "div.list > div.group > div.title > a" - - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/most_downloaded/$page/", headers) - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.text().trim() - return manga - } - - override fun popularMangaNextPageSelector() = "div.next > a.gbutton:contains(»):last-of-type" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesRequest(page: Int) = - GET("$baseUrl/latest/$page/", headers) - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val tags = mutableListOf<String>() - val paths = mutableListOf<String>() - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is TagList -> - filter.state - .filter { it.state } - .map { - paths.add(it.name.toLowerCase().replace(" ", "_")) - it.id.toString() - } - .forEach { tags.add(it) } - } - } - - val searchTags = tags.size > 0 - if (!searchTags && query.length < 3) { - throw Exception("Inserisci almeno tre caratteri") - } - - val form = FormBody.Builder().apply { - if (!searchTags) { - add("search", query) - } else { - tags.forEach { - add("tag[]", it) - } - } - } - - val searchPath = if (!searchTags) { - "search" - } else if (paths.size == 1) { - "tag/${paths[0]}/$page" - } else { - "search_tags" - } - - return POST("$baseUrl/$searchPath", headers, form.build()) - } - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - val genres = mutableListOf<String>() - document.select("div#tablelist > div.row").forEach { row -> - when (row.select("div.cell > b").first().text().trim()) { - "Autore" -> manga.author = row.select("div.cell > a").text().trim() - "Genere", "Tipo" -> row.select("div.cell > a > span.label").forEach { - genres.add(it.text().trim()) - } - "Descrizione" -> manga.description = row.select("div.cell").last().text().trim() - } - } - manga.genre = genres.joinToString(", ") - manga.status = SManga.UNKNOWN - manga.thumbnail_url = document.select("div.thumbnail > img")?.attr("src") - return manga - } - - override fun mangaDetailsRequest(manga: SManga) = POST(baseUrl + manga.url, headers) - - override fun chapterListSelector() = "div.list > div.group div.element" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - element.select("div.title > a").let { - chapter.setUrlWithoutDomain(it.attr("href")) - chapter.name = it.text().trim() - } - chapter.date_upload = element.select("div.meta_r").first()?.ownText()?.substringAfterLast(", ")?.trim()?.let { - parseChapterDate(it) - } ?: 0L - return chapter - } - - private fun parseChapterDate(date: String): Long { - return when (date) { - "Oggi" -> { - Calendar.getInstance().timeInMillis - } - "Ieri" -> { - Calendar.getInstance().apply { - add(Calendar.DAY_OF_YEAR, -1) - }.timeInMillis - } - else -> { - try { - dateFormat.parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - } - } - - override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers) - - override fun pageListParse(response: Response): List<Page> { - val body = response.body()!!.string() - val pages = mutableListOf<Page>() - - val p = pagesUrlPattern - val m = p.matcher(body) - - var i = 0 - while (m.find()) { - pages.add(Page(i++, "", m.group(1)?.replace("""\\""", ""))) - } - return pages - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlRequest(page: Page) = GET(page.url) - - override fun imageUrlParse(document: Document) = "" - - private class Tag(name: String, val id: Int) : Filter.CheckBox(name) - private class TagList(title: String, tags: List<Tag>) : Filter.Group<Tag>(title, tags) - - override fun getFilterList() = FilterList( - TagList("Generi", getTagList()) - ) - - // Tags: 47 - // $("select[name='tag[]']:eq(0) > option").map((i, el) => `Tag("${$(el).text().trim()}", ${$(el).attr("value")})`).get().sort().join(",\n") - // on https://www.hentaifantasy.it/search/ - private fun getTagList() = listOf( - Tag("Ahegao", 56), - Tag("Anal", 28), - Tag("Ashikoki", 12), - Tag("Bestiality", 24), - Tag("Bizzare", 44), - Tag("Bondage", 30), - Tag("Cheating", 33), - Tag("Chubby", 57), - Tag("Dark Skin", 39), - Tag("Demon Girl", 43), - Tag("Femdom", 38), - Tag("Forced", 46), - Tag("Full color", 52), - Tag("Furry", 36), - Tag("Futanari", 18), - Tag("Group", 34), - Tag("Guro", 8), - Tag("Harem", 41), - Tag("Housewife", 51), - Tag("Incest", 11), - Tag("Lolicon", 20), - Tag("Maid", 55), - Tag("Milf", 31), - Tag("Monster Girl", 15), - Tag("Nurse", 49), - Tag("Oppai", 25), - Tag("Paizuri", 42), - Tag("Pettanko", 35), - Tag("Pissing", 32), - Tag("Public", 53), - Tag("Rape", 21), - Tag("Schoolgirl", 27), - Tag("Shotacon", 26), - Tag("Stockings", 40), - Tag("Swimsuit", 47), - Tag("Tanlines", 48), - Tag("Teacher", 50), - Tag("Tentacle", 23), - Tag("Toys", 45), - Tag("Trap", 29), - Tag("Tsundere", 54), - Tag("Uncensored", 59), - Tag("Vanilla", 19), - Tag("Yandere", 58), - Tag("Yaoi", 22), - Tag("Yuri", 14) - ) -} diff --git a/src/it/mangaeden/AndroidManifest.xml b/src/it/mangaeden/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/it/mangaeden/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/it/mangaeden/build.gradle b/src/it/mangaeden/build.gradle deleted file mode 100644 index 1e53bdc51..000000000 --- a/src/it/mangaeden/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangaeden' - pkgNameSuffix = 'it.mangaeden' - extClass = '.Mangaeden' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/it/mangaeden/res/mipmap-hdpi/ic_launcher.png b/src/it/mangaeden/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d5fe0354d..000000000 Binary files a/src/it/mangaeden/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaeden/res/mipmap-mdpi/ic_launcher.png b/src/it/mangaeden/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fba82aa13..000000000 Binary files a/src/it/mangaeden/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaeden/res/mipmap-xhdpi/ic_launcher.png b/src/it/mangaeden/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bc5643ede..000000000 Binary files a/src/it/mangaeden/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaeden/res/mipmap-xxhdpi/ic_launcher.png b/src/it/mangaeden/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 4b3eb907e..000000000 Binary files a/src/it/mangaeden/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaeden/res/mipmap-xxxhdpi/ic_launcher.png b/src/it/mangaeden/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 58d5afdc0..000000000 Binary files a/src/it/mangaeden/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaeden/res/web_hi_res_512.png b/src/it/mangaeden/res/web_hi_res_512.png deleted file mode 100644 index a4d44efcd..000000000 Binary files a/src/it/mangaeden/res/web_hi_res_512.png and /dev/null differ diff --git a/src/it/mangaeden/src/eu/kanade/tachiyomi/extension/it/mangaeden/Mangaeden.kt b/src/it/mangaeden/src/eu/kanade/tachiyomi/extension/it/mangaeden/Mangaeden.kt deleted file mode 100644 index 93dc5011c..000000000 --- a/src/it/mangaeden/src/eu/kanade/tachiyomi/extension/it/mangaeden/Mangaeden.kt +++ /dev/null @@ -1,224 +0,0 @@ -package eu.kanade.tachiyomi.extension.it.mangaeden - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class Mangaeden : ParsedHttpSource() { - - override val name = "Manga Eden" - - override val baseUrl = "https://www2.mangaeden.com" - - override val lang = "it" - - override val supportsLatest = true - - // so hcaptcha won't be triggered on images - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?order=3&page=$page", headers) - - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?order=1&page=$page", headers) - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/it/it-directory/")?.newBuilder()!!.addQueryParameter("title", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is StatusList -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("status", it) } - is Types -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("type", it) } - is GenreList -> - filter.state - .filter { !it.isIgnored() } - .forEach { genre -> url.addQueryParameter(if (genre.isIncluded()) "categoriesInc" else "categoriesExcl", genre.id) } - is TextField -> url.addQueryParameter(filter.key, filter.state) - is OrderBy -> filter.state?.let { - val sortId = it.index - url.addQueryParameter("order", if (it.ascending) "-$sortId" else "$sortId") - } - } - } - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "table#mangaList tbody tr td:first-child a" - - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.text() - } - - override fun searchMangaNextPageSelector() = "a:has(span.next)" - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val infos = document.select("div.rightbox") - - author = infos.select("a[href^=/it/it-directory/?author]").first()?.text() - artist = infos.select("a[href^=/it/it-directory/?artist]").first()?.text() - genre = infos.select("a[href^=/it/it-directory/?categoriesInc]").joinToString { it.text() } - description = document.select("h2#mangaDescription").text() - status = parseStatus(infos.select("h4:containsOwn(Stato)").first()?.nextSibling().toString()) - thumbnail_url = document.select("div.mangaImage2 > img").attr("abs:src") - } - - private fun parseStatus(status: String) = when { - status.contains("In Corso", true) -> SManga.ONGOING - status.contains("Completato", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div#leftContent > table > tbody > tr" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val a = element.select("a[href^=/it/it-manga/]").first() - - setUrlWithoutDomain(a.attr("href")) - name = a?.select("b")?.first()?.text().orEmpty() - date_upload = element.select("td.chapterDate").first()?.text()?.let { parseChapterDate(it.trim()) } ?: 0L - } - - private fun parseChapterDate(date: String): Long = - when { - "Oggi" in date -> { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - "Ieri" in date -> { - Calendar.getInstance().apply { - add(Calendar.DATE, -1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - else -> - try { - SimpleDateFormat("d MMM yyyy", Locale.ITALIAN).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("select#pageSelect option").mapIndexed { i, element -> - Page(i, element.attr("abs:value")) - } - } - - override fun imageUrlParse(document: Document): String = document.select("a.next img").attr("abs:src") - - private class NamedId(name: String, val id: Int) : Filter.CheckBox(name) - private class Genre(name: String, val id: String) : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class OrderBy : Filter.Sort( - "Order by", - arrayOf("Titolo manga", "Visite", "Capitoli", "Ultimo capitolo"), - Selection(1, false) - ) - - private class StatusList(statuses: List<NamedId>) : Filter.Group<NamedId>("Stato", statuses) - private class Types(types: List<NamedId>) : Filter.Group<NamedId>("Tipo", types) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres) - - override fun getFilterList() = FilterList( - TextField("Autore", "author"), - TextField("Artista", "artist"), - OrderBy(), - Types(types()), - StatusList(statuses()), - GenreList(genres()) - ) - - private fun types() = listOf( - NamedId("Japanese Manga", 0), - NamedId("Korean Manhwa", 1), - NamedId("Chinese Manhua", 2), - NamedId("Comic", 3), - NamedId("Doujinshi", 4) - ) - - private fun statuses() = listOf( - NamedId("In corso", 1), - NamedId("Completato", 2), - NamedId("Sospeso", 0) - ) - - private fun genres() = listOf( - - Genre("Avventura", "4e70ea8cc092255ef70073d3"), - Genre("Azione", "4e70ea8cc092255ef70073c3"), - Genre("Bara", "4e70ea90c092255ef70074b7"), - Genre("Commedia", "4e70ea8cc092255ef70073d0"), - Genre("Demenziale", "4e70ea8fc092255ef7007475"), - Genre("Dounshinji", "4e70ea93c092255ef70074e4"), - Genre("Drama", "4e70ea8cc092255ef70073f9"), - Genre("Ecchi", "4e70ea8cc092255ef70073cd"), - Genre("Fantasy", "4e70ea8cc092255ef70073c4"), - Genre("Harem", "4e70ea8cc092255ef70073d1"), - Genre("Hentai", "4e70ea90c092255ef700749a"), - Genre("Horror", "4e70ea8cc092255ef70073ce"), - Genre("Josei", "4e70ea90c092255ef70074bd"), - Genre("Magico", "4e70ea93c092255ef700751b"), - Genre("Mecha", "4e70ea8cc092255ef70073ef"), - Genre("Misteri", "4e70ea8dc092255ef700740a"), - Genre("Musica", "4e70ea8fc092255ef7007456"), - Genre("Psicologico", "4e70ea8ec092255ef7007439"), - Genre("Raccolta", "4e70ea90c092255ef70074ae"), - Genre("Romantico", "4e70ea8cc092255ef70073c5"), - Genre("Sci-Fi", "4e70ea8cc092255ef70073e4"), - Genre("Scolastico", "4e70ea8cc092255ef70073e5"), - Genre("Seinen", "4e70ea8cc092255ef70073ea"), - Genre("Sentimentale", "4e70ea8dc092255ef7007432"), - Genre("Shota", "4e70ea90c092255ef70074b8"), - Genre("Shoujo", "4e70ea8dc092255ef7007421"), - Genre("Shounen", "4e70ea8cc092255ef70073c6"), - Genre("Sovrannaturale", "4e70ea8cc092255ef70073c7"), - Genre("Splatter", "4e70ea99c092255ef70075a3"), - Genre("Sportivo", "4e70ea8dc092255ef7007426"), - Genre("Storico", "4e70ea8cc092255ef70073f4"), - Genre("Vita Quotidiana", "4e70ea8ec092255ef700743f"), - Genre("Yaoi", "4e70ea8cc092255ef70073de"), - Genre("Yuri", "4e70ea9ac092255ef70075d1") - ) -} diff --git a/src/it/mangaworld/AndroidManifest.xml b/src/it/mangaworld/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/it/mangaworld/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/it/mangaworld/build.gradle b/src/it/mangaworld/build.gradle deleted file mode 100644 index 7e580d11e..000000000 --- a/src/it/mangaworld/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangaworld' - pkgNameSuffix = 'it.mangaworld' - extClass = '.Mangaworld' - extVersionCode = 4 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/it/mangaworld/res/mipmap-hdpi/ic_launcher.png b/src/it/mangaworld/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 75be6e047..000000000 Binary files a/src/it/mangaworld/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaworld/res/mipmap-mdpi/ic_launcher.png b/src/it/mangaworld/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1c1feec17..000000000 Binary files a/src/it/mangaworld/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaworld/res/mipmap-xhdpi/ic_launcher.png b/src/it/mangaworld/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b51a673e6..000000000 Binary files a/src/it/mangaworld/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaworld/res/mipmap-xxhdpi/ic_launcher.png b/src/it/mangaworld/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ab677769..000000000 Binary files a/src/it/mangaworld/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaworld/res/mipmap-xxxhdpi/ic_launcher.png b/src/it/mangaworld/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e6f23ed49..000000000 Binary files a/src/it/mangaworld/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/mangaworld/res/web_hi_res_512.png b/src/it/mangaworld/res/web_hi_res_512.png deleted file mode 100644 index a0be9e065..000000000 Binary files a/src/it/mangaworld/res/web_hi_res_512.png and /dev/null differ diff --git a/src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt b/src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt deleted file mode 100644 index 639d806df..000000000 --- a/src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt +++ /dev/null @@ -1,323 +0,0 @@ -package eu.kanade.tachiyomi.extension.it.mangaworld - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -@Nsfw -class Mangaworld : ParsedHttpSource() { - - override val name = "Mangaworld" - override val baseUrl = "https://www.mangaworld.io" - override val lang = "it" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/archive?sort=most_read&page=$page", headers) - } - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/archive?sort=newest&page=$page", headers) - } - // LIST SELECTOR - override fun popularMangaSelector() = "div.comics-grid .entry" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - - // ELEMENT - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - // NEXT SELECTOR - // Not needed - override fun popularMangaNextPageSelector(): String? = null - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - // //////////////// - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - // nextPage is not possible because pagination is loaded after via Javascript - // 16 is the default manga-per-page. If it is less than 16 then there's no next page - val hasNextPage = mangas.size == 16 - return MangasPage(mangas, hasNextPage) - } - - override fun popularMangaParse(response: Response): MangasPage { - return latestUpdatesParse(response) - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("a.thumb img").attr("src") - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun searchMangaParse(response: Response): MangasPage { - return latestUpdatesParse(response) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/archive?page=$page")!!.newBuilder() - val pattern = "\\s+".toRegex() - val q = query - if (query.length > 0) { - url.addQueryParameter("keyword", q) - } else { - url.addQueryParameter("keyword", "") - } - - var orderBy = "" - - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> { - val genreInclude = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } - } - if (genreInclude.isNotEmpty()) { - genreInclude.forEach { genre -> - url.addQueryParameter("genre", genre) - } - } - } - is StatusList -> { - val statuses = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - statuses.add(it.id) - } - } - if (statuses.isNotEmpty()) { - statuses.forEach { status -> - url.addQueryParameter("status", status) - } - } - } - - is MTypeList -> { - val typeslist = mutableListOf<String>() - filter.state.forEach { - if (it.state == 1) { - typeslist.add(it.id) - } - } - if (typeslist.isNotEmpty()) { - typeslist.forEach { mtype -> - url.addQueryParameter("type", mtype) - } - } - } - - is SortBy -> { - orderBy = filter.toUriPart() - url.addQueryParameter("sort", orderBy) - } - is TextField -> url.addQueryParameter(filter.key, filter.state) - } - } - return GET(url.toString(), headers) - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.comic-info") - val metaData = document.select("div.comic-info").first() - - val manga = SManga.create() - manga.author = infoElement.select("a[href^=https://www.mangaworld.io/archive?author=]").first()?.text() - manga.artist = infoElement.select("a[href^=https://www.mangaworld.io/archive?artist=]")?.text() - - val genres = mutableListOf<String>() - metaData.select("div.meta-data a.badge").forEach { element -> - val genre = element.text() - genres.add(genre) - } - manga.genre = genres.joinToString(", ") - manga.status = parseStatus(infoElement.select("a[href^=https://www.mangaworld.io/archive?status=]").first().attr("href")) - - manga.description = document.select("div#noidungm")?.text() - manga.thumbnail_url = document.select(".comic-info .thumb > img").attr("src") - - return manga - } - - private fun parseStatus(element: String): Int = when { - - element.toLowerCase().contains("ongoing") -> SManga.ONGOING - element.toLowerCase().contains("completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = ".chapters-wrapper .chapter" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a.chap").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(getUrl(urlElement)) - chapter.name = urlElement.select("span.d-inline-block").first().text() - chapter.date_upload = element.select(".chap-date").last()?.text()?.let { - try { - SimpleDateFormat("dd MMMM yyyy", Locale.ITALY).parse(it).time - } catch (e: ParseException) { - SimpleDateFormat("H", Locale.ITALY).parse(it).time - } - } ?: 0 - return chapter - } - - private fun getUrl(urlElement: Element): String { - var url = urlElement.attr("href") - return when { - url.endsWith("?style=list") -> url - else -> "$url?style=list" - } - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""Capitolo\s([0-9]+)""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[1]?.value!!.toFloat() - } - } - } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - var i = 0 - document.select("div#page img.page-image").forEach { element -> - val url = element.attr("src") - i++ - if (url.length != 0) { - pages.add(Page(i, "", url)) - } - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - // private class Status : Filter.TriState("Completed") - private class TextField(name: String, val key: String) : Filter.Text(name) - private class SortBy : UriPartFilter( - "Ordina per", - arrayOf( - Pair("Rilevanza", ""), - Pair("Più recenti", "newest"), - Pair("Meno recenti", "oldest"), - Pair("A-Z", "a-z"), - Pair("Z-A", "z-a"), - Pair("Più letti", "most_read"), - Pair("Meno letti", "less_read") - ) - ) - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres) - private class MType(name: String, val id: String = name) : Filter.TriState(name) - private class MTypeList(types: List<MType>) : Filter.Group<MType>("Tipologia", types) - private class Status(name: String, val id: String = name) : Filter.TriState(name) - private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Stato", statuses) - - override fun getFilterList() = FilterList( - TextField("Anno di rilascio", "year"), - SortBy(), - StatusList(getStatusList()), - GenreList(getGenreList()), - MTypeList(getTypesList()) - ) - private fun getStatusList() = listOf( - Status("In corso", "ongoing"), - Status("Finito", "completed"), - Status("Droppato", "dropped"), - Status("In pausa", "paused"), - Status("Cancellato", "canceled") - ) - - private fun getTypesList() = listOf( - MType("Manga", "manga"), - MType("Manhua", "manhua"), - MType("Manhwa", "manhwa"), - MType("Oneshot", "oneshot"), - MType("Thai", "thai"), - MType("Vietnamita", "vietnamese") - ) - - private fun getGenreList() = listOf( - Genre("Adulti", "adulti"), - Genre("Arti Marziali", "arti-marziali"), - Genre("Avventura", "avventura"), - Genre("Azione", "azione"), - Genre("Commedia", "commedia"), - Genre("Doujinshi", "doujinshi"), - Genre("Drammatico", "drammatico"), - Genre("Ecchi", "ecchi"), - Genre("Fantasy", "fantasy"), - Genre("Gender Bender", "gender-bender"), - Genre("Harem", "harem"), - Genre("Hentai", "hentai"), - Genre("Horror", "horror"), - Genre("Josei", "josei"), - Genre("Lolicon", "lolicon"), - Genre("Maturo", "maturo"), - Genre("Mecha", "mecha"), - Genre("Mistero", "mistero"), - Genre("Psicologico", "psicologico"), - Genre("Romantico", "romantico"), - Genre("Sci-fi", "sci-fi"), - Genre("Scolastico", "scolastico"), - Genre("Seinen", "seinen"), - Genre("Shotacon", "shotacon"), - Genre("Shoujo", "shoujo"), - Genre("Shoujo Ai", "shoujo-ai"), - Genre("Shounen", "shounen"), - Genre("Shounen Ai", "shounen-ai"), - Genre("Slice of Life", "slice-of-life"), - Genre("Smut", "smut"), - Genre("Soprannaturale", "soprannaturale"), - Genre("Sport", "sport"), - Genre("Storico", "storico"), - Genre("Tragico", "tragico"), - Genre("Yaoi", "yaoi"), - Genre("Yuri", "yuri") - ) - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/it/novelleleggere/AndroidManifest.xml b/src/it/novelleleggere/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/it/novelleleggere/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/it/novelleleggere/build.gradle b/src/it/novelleleggere/build.gradle deleted file mode 100644 index 5532c282a..000000000 --- a/src/it/novelleleggere/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Novelle Leggere' - pkgNameSuffix = 'it.novelleleggere' - extClass = '.NovelleLeggere' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/it/novelleleggere/res/mipmap-hdpi/ic_launcher.png b/src/it/novelleleggere/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 587265c34..000000000 Binary files a/src/it/novelleleggere/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/novelleleggere/res/mipmap-mdpi/ic_launcher.png b/src/it/novelleleggere/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 761c59607..000000000 Binary files a/src/it/novelleleggere/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/novelleleggere/res/mipmap-xhdpi/ic_launcher.png b/src/it/novelleleggere/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index cdb9f3ddb..000000000 Binary files a/src/it/novelleleggere/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/novelleleggere/res/mipmap-xxhdpi/ic_launcher.png b/src/it/novelleleggere/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8dfb3d27d..000000000 Binary files a/src/it/novelleleggere/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/novelleleggere/res/mipmap-xxxhdpi/ic_launcher.png b/src/it/novelleleggere/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a531669e8..000000000 Binary files a/src/it/novelleleggere/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/novelleleggere/res/web_hi_res_512.png b/src/it/novelleleggere/res/web_hi_res_512.png deleted file mode 100644 index 3a6f6baa6..000000000 Binary files a/src/it/novelleleggere/res/web_hi_res_512.png and /dev/null differ diff --git a/src/it/novelleleggere/src/eu/kanade/tachiyomi/extension/it/novelleleggere/NovelleLeggere.kt b/src/it/novelleleggere/src/eu/kanade/tachiyomi/extension/it/novelleleggere/NovelleLeggere.kt deleted file mode 100644 index 6d7a33544..000000000 --- a/src/it/novelleleggere/src/eu/kanade/tachiyomi/extension/it/novelleleggere/NovelleLeggere.kt +++ /dev/null @@ -1,81 +0,0 @@ -package eu.kanade.tachiyomi.extension.it.novelleleggere - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class NovelleLeggere : ParsedHttpSource() { - - // Info - override val name: String = "Novelle Leggere" - override val baseUrl: String = "https://www.novelleleggere.com" - override val lang: String = "it" - override val supportsLatest: Boolean = false - - // Popular - override fun popularMangaRequest(page: Int): Request = GET(baseUrl) - - override fun popularMangaNextPageSelector(): String? = null - override fun popularMangaSelector(): String = "table:contains(Manga) tr:gt(0)" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - val a = element.select("a").first() - title = a.text() - setUrlWithoutDomain(a.attr("abs:href")) - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Latest Not Supported") - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Latest Not Supported") - override fun latestUpdatesSelector(): String = throw Exception("Latest Not Supported") - override fun latestUpdatesFromElement(element: Element): SManga = - throw Exception("Latest Not Supported") - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - throw Exception("Search Not Supported") - - override fun searchMangaNextPageSelector(): String? = throw Exception("Search Not Supported") - override fun searchMangaSelector(): String = throw Exception("Search Not Supported") - override fun searchMangaFromElement(element: Element): SManga = - throw Exception("Search Not Supported") - - // Details - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - thumbnail_url = document.select("div.post-content img").first().attr("abs:src") - title = document.select("div.post-content h3").text().trim() - description = - document.select("div.post-content div:contains(Trama) div.su-spoiler-content").text() - .trim() - } - - // Chapters - override fun chapterListSelector(): String = - "div.post-content div:contains(Capitoli) div.su-spoiler-content ul li a" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.text().trim() - setUrlWithoutDomain(element.attr("abs:href")) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - // Pages - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("div.post-content p>img, div.post-content figure>img").forEachIndexed { index, element -> - add(Page(index, "", element.attr("abs:src").substringBefore("?"))) - } - } - - override fun imageUrlParse(document: Document): String = - throw Exception("ImgURL Parse Not Used") -} diff --git a/src/it/perveden/AndroidManifest.xml b/src/it/perveden/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/it/perveden/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/it/perveden/build.gradle b/src/it/perveden/build.gradle deleted file mode 100644 index f53312adf..000000000 --- a/src/it/perveden/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Perveden' - pkgNameSuffix = 'it.perveden' - extClass = '.Perveden' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/it/perveden/res/mipmap-hdpi/ic_launcher.png b/src/it/perveden/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 773444ec5..000000000 Binary files a/src/it/perveden/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/perveden/res/mipmap-mdpi/ic_launcher.png b/src/it/perveden/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a4b97dcfe..000000000 Binary files a/src/it/perveden/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/perveden/res/mipmap-xhdpi/ic_launcher.png b/src/it/perveden/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index cdf36f539..000000000 Binary files a/src/it/perveden/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/perveden/res/mipmap-xxhdpi/ic_launcher.png b/src/it/perveden/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 90278a445..000000000 Binary files a/src/it/perveden/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/perveden/res/mipmap-xxxhdpi/ic_launcher.png b/src/it/perveden/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0db019b95..000000000 Binary files a/src/it/perveden/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/it/perveden/res/web_hi_res_512.png b/src/it/perveden/res/web_hi_res_512.png deleted file mode 100644 index 1476a57af..000000000 Binary files a/src/it/perveden/res/web_hi_res_512.png and /dev/null differ diff --git a/src/it/perveden/src/eu/kanade/tachiyomi/extension/it/perveden/Perveden.kt b/src/it/perveden/src/eu/kanade/tachiyomi/extension/it/perveden/Perveden.kt deleted file mode 100644 index e4e2fcff4..000000000 --- a/src/it/perveden/src/eu/kanade/tachiyomi/extension/it/perveden/Perveden.kt +++ /dev/null @@ -1,417 +0,0 @@ -package eu.kanade.tachiyomi.extension.it.perveden - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -@Nsfw -class Perveden : ParsedHttpSource() { - - override val name = "PervEden" - - override val baseUrl = "https://www.perveden.com" - - override val lang = "it" - - override val supportsLatest = true - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?order=3&page=$page", headers) - - override fun latestUpdatesSelector() = searchMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/it/it-directory/?page=$page", headers) - - override fun popularMangaSelector() = searchMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element) - - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/it/it-directory/")?.newBuilder()!!.addQueryParameter("title", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is StatusList -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("status", it) } - is Types -> - filter.state - .filter { it.state } - .map { it.id.toString() } - .forEach { url.addQueryParameter("type", it) } - is TextField -> url.addQueryParameter(filter.key, filter.state) - is OrderBy -> filter.state?.let { - val sortId = it.index - url.addQueryParameter("order", if (it.ascending) "-$sortId" else "$sortId") - } - is GenreField -> filter.state.toLowerCase(Locale.ENGLISH).split(',', ';').forEach { - val id = genres[it.trim()] - if (id != null) url.addQueryParameter(filter.key, id) - } - } - } - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "table#mangaList > tbody > tr:has(td:gt(1))" - - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - element.select("td > a").first()?.let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - } - - override fun searchMangaNextPageSelector() = "a:has(span.next)" - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val infos = document.select("div.rightbox") - - author = infos.select("a[href^=/it/it-directory/?author]").first()?.text() - artist = infos.select("a[href^=/it/it-directory/?artist]").first()?.text() - genre = infos.select("a[href^=/it/it-directory/?categoriesInc]").joinToString { it.text() } - description = document.select("h2#mangaDescription").text() - status = parseStatus(infos.select("h4:containsOwn(Stato)").first()?.nextSibling().toString()) - val img = infos.select("div.mangaImage2 > img").first()?.attr("src") - if (!img.isNullOrBlank()) thumbnail_url = img.let { "https:$it" } - } - - private fun parseStatus(status: String) = when { - status.contains("In Corso", true) -> SManga.ONGOING - status.contains("Completato", true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div#leftContent > table > tbody > tr" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val a = element.select("a[href^=/it/it-manga/]").first() - - setUrlWithoutDomain(a?.attr("href").orEmpty()) - name = a?.select("b")?.first()?.text().orEmpty() - date_upload = element.select("td.chapterDate").first()?.text()?.let { parseChapterDate(it.trim()) } ?: 0L - } - - private fun parseChapterDate(date: String): Long = - when { - "Oggi" in date -> { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - "Ieri" in date -> { - Calendar.getInstance().apply { - add(Calendar.DATE, -1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } - else -> - try { - SimpleDateFormat("d MMM yyyy", Locale.ITALIAN).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("option[value^=/it/it-manga/]").forEach { - add(Page(size, "$baseUrl${it.attr("value")}")) - } - } - - override fun imageUrlParse(document: Document): String = document.select("a#nextA.next > img").first()?.attr("src").let { "https:$it" } - - private class NamedId(name: String, val id: Int) : Filter.CheckBox(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class GenreField(name: String, val key: String) : Filter.Text(name) - private class OrderBy : Filter.Sort( - "Ordina per", - arrayOf("Titolo manga", "Visite", "Capitoli", "Ultimo capitolo"), - Selection(1, false) - ) - - private class StatusList(statuses: List<NamedId>) : Filter.Group<NamedId>("Stato", statuses) - private class Types(types: List<NamedId>) : Filter.Group<NamedId>("Tipo", types) - - override fun getFilterList() = FilterList( - TextField("Autore", "author"), - TextField("Artista", "artist"), - GenreField("Generi inclusi", "categoriesInc"), - GenreField("Generi esclusi", "categoriesExcl"), - OrderBy(), - Types(types()), - StatusList(statuses()) - ) - - private fun types() = listOf( - NamedId("Japanese Manga", 0), - NamedId("Korean Manhwa", 1), - NamedId("Chinese Manhua", 2), - NamedId("Comic", 3), - NamedId("Doujinshi", 4) - ) - - private fun statuses() = listOf( - NamedId("In corso", 1), - NamedId("Completato", 2), - NamedId("Sospeso", 0) - ) - - private val genres = mapOf( - Pair("commedia", "4e70ea9ac092255ef70075d8"), - Pair("ecchi", "4e70ea9ac092255ef70075d9"), - Pair("age progression", "5782b043719a16947390104a"), - Pair("ahegao", "577e6f90719a168e7d256a3f"), - Pair("anal", "577e6f90719a168e7d256a3b"), - Pair("angel", "577e724a719a168ef96a74d6"), - Pair("apron", "577e720a719a166f4719a7be"), - Pair("armpit licking", "577e71db719a166f4719a3e7"), - Pair("assjob", "58474a08719a1668eeeea29b"), - Pair("aunt", "577e6f8d719a168e7d256a20"), - Pair("bbw", "5782ae42719a1675f68a6e29"), - Pair("bdsm", "577e723d719a168ef96a7416"), - Pair("bestiality", "57ad8919719a1629a0a327cf"), - Pair("big areolae", "577e7226719a166f4719a9d0"), - Pair("big ass", "577e6f8d719a168e7d256a21"), - Pair("big balls", "577e7267719a168ef96a76ee"), - Pair("big breasts", "577e6f8d719a168e7d256a1c"), - Pair("big clit", "57ef0396719a163dffb8fdff"), - Pair("big nipples", "5782ae42719a1675f68a6e2a"), - Pair("big penis", "577e7267719a168ef96a76ef"), - Pair("bike shorts", "577e7210719a166f4719a820"), - Pair("bikini", "577e6f91719a168e7d256a77"), - Pair("birth", "577e7273719a168ef96a77cf"), - Pair("blackmail", "577e6f91719a168e7d256a78"), - Pair("blindfold", "577e7208719a166f4719a78d"), - Pair("blood", "577e7295719a168ef96a79e6"), - Pair("bloomers", "5782b051719a1694739010ee"), - Pair("blowjob", "577e6f8d719a168e7d256a22"), - Pair("blowjob face", "577e71eb719a166f4719a544"), - Pair("body modification", "577e6f93719a168e7d256a8e"), - Pair("bodystocking", "5782b05c719a169473901151"), - Pair("bodysuit", "577e6f90719a168e7d256a42"), - Pair("bondage", "577e6f90719a168e7d256a45"), - Pair("breast expansion", "577e71c3719a166f4719a235"), - Pair("bukkake", "577e7210719a166f4719a821"), - Pair("bunny girl", "577e7224719a166f4719a9b9"), - Pair("business suit", "577e71e5719a166f4719a4b2"), - Pair("catgirl", "577e71d5719a166f4719a366"), - Pair("centaur", "577e7297719a168ef96a7a06"), - Pair("cervix penetration", "577e7273719a168ef96a77d0"), - Pair("cheating", "577e71b5719a166f4719a13b"), - Pair("cheerleader", "57c0a6de719a1641240e9257"), - Pair("chikan", "5782b0c6719a1679528762ac"), - Pair("chinese dress", "5782b059719a169473901131"), - Pair("chloroform", "577e6f92719a168e7d256a7f"), - Pair("christmas", "5782af2b719a169473900752"), - Pair("clit growth", "57ef0396719a163dffb8fe00"), - Pair("collar", "577e6f93719a168e7d256a8f"), - Pair("condom", "577e71d5719a166f4719a36c"), - Pair("corruption", "577e6f90719a168e7d256a41"), - Pair("cosplaying", "5782b185719a167952876944"), - Pair("cousin", "577e7283719a168ef96a78c3"), - Pair("cow", "5865d767719a162cce299571"), - Pair("cunnilingus", "577e6f8d719a168e7d256a23"), - Pair("dark skin", "577e6f90719a168e7d256a55"), - Pair("daughter", "577e7250719a168ef96a7539"), - Pair("deepthroat", "577e6f90719a168e7d256a3c"), - Pair("defloration", "577e6f92719a168e7d256a82"), - Pair("demon girl", "577e7218719a166f4719a8c8"), - Pair("dick growth", "577e6f93719a168e7d256a90"), - Pair("dickgirl on dickgirl", "5782af0e719a16947390067a"), - Pair("dog girl", "577e7218719a166f4719a8c9"), - Pair("double penetration", "577e6f90719a168e7d256a3d"), - Pair("double vaginal", "577e7226719a166f4719a9d1"), - Pair("drugs", "577e71da719a166f4719a3cb"), - Pair("drunk", "577e7199719a16697b9853ea"), - Pair("elf", "577e6f93719a168e7d256a91"), - Pair("enema", "5782aff7719a169473900d8a"), - Pair("exhibitionism", "577e72a7719a168ef96a7b26"), - Pair("eyemask", "577e7208719a166f4719a78e"), - Pair("facesitting", "577e7230719a166f4719aa8c"), - Pair("females only", "577e6f90719a168e7d256a44"), - Pair("femdom", "577e6f8c719a168e7d256a13"), - Pair("filming", "577e7242719a168ef96a7465"), - Pair("fingering", "577e6f90719a168e7d256a5d"), - Pair("fisting", "57c349e1719a1625b42603f4"), - Pair("foot licking", "5782b152719a16795287677d"), - Pair("footjob", "577e6f8d719a168e7d256a17"), - Pair("freckles", "5782ae42719a1675f68a6e2b"), - Pair("fundoshi", "577e71d9719a166f4719a3bf"), - Pair("furry", "5782ae45719a1675f68a6e49"), - Pair("futanari", "577e6f92719a168e7d256a80"), - Pair("gag", "577e6f90719a168e7d256a56"), - Pair("gaping", "577e7210719a166f4719a822"), - Pair("garter belt", "577e7201719a166f4719a704"), - Pair("glasses", "577e6f90719a168e7d256a5e"), - Pair("gothic lolita", "577e7201719a166f4719a705"), - Pair("group", "577e726e719a168ef96a7764"), - Pair("gyaru", "577e6f91719a168e7d256a79"), - Pair("hairjob", "57bcea9f719a1687ea2bc092"), - Pair("hairy", "577e7250719a168ef96a753a"), - Pair("hairy armpits", "5782b13c719a16795287669c"), - Pair("handjob", "577e71c8719a166f4719a29b"), - Pair("harem", "577e71c3719a166f4719a239"), - Pair("heterochromia", "577e7201719a166f4719a706"), - Pair("hotpants", "585b302d719a1648da4f0389"), - Pair("huge breasts", "577e71d9719a166f4719a3c0"), - Pair("huge penis", "585b302d719a1648da4f038a"), - Pair("human on furry", "577e7203719a166f4719a722"), - Pair("human pet", "577e6f90719a168e7d256a57"), - Pair("humiliation", "577e7210719a166f4719a823"), - Pair("impregnation", "577e6f90719a168e7d256a47"), - Pair("incest", "577e6f93719a168e7d256a92"), - Pair("inflation", "577e7273719a168ef96a77d1"), - Pair("insect girl", "577e71fc719a166f4719a692"), - Pair("inverted nipples", "5813993a719a165f236ddacd"), - Pair("kimono", "577e723d719a168ef96a7417"), - Pair("kissing", "5782ae4f719a1675f68a6ece"), - Pair("lactation", "577e6f93719a168e7d256a93"), - Pair("latex", "577e6f90719a168e7d256a58"), - Pair("layer cake", "577e7230719a166f4719aa8d"), - Pair("leg lock", "57b7c0c2719a169265b768bd"), - Pair("leotard", "579b141e719a16881d14ccfe"), - Pair("lingerie", "577e71fc719a166f4719a693"), - Pair("living clothes", "577e6f90719a168e7d256a49"), - Pair("lizard girl", "5782b127719a1679528765e9"), - Pair("lolicon", "5782af84719a1694739009b5"), - Pair("long tongue", "5782b158719a1679528767d5"), - Pair("machine", "57ef0396719a163dffb8fe01"), - Pair("magical girl", "577e71c3719a166f4719a236"), - Pair("maid", "5782ae3f719a1675f68a6e19"), - Pair("male on dickgirl", "577e7267719a168ef96a76f0"), - Pair("masked face", "57c349e1719a1625b42603f5"), - Pair("masturbation", "577e71b5719a166f4719a13c"), - Pair("mermaid", "578d3c5b719a164fa798c09e"), - Pair("metal armor", "5782b158719a1679528767d6"), - Pair("miko", "577e726e719a168ef96a7765"), - Pair("milf", "577e6f8d719a168e7d256a24"), - Pair("military", "577e6f8d719a168e7d256a18"), - Pair("milking", "577e6f93719a168e7d256a94"), - Pair("mind break", "577e6f90719a168e7d256a4b"), - Pair("mind control", "577e6f90719a168e7d256a4d"), - Pair("monster girl", "577e6f90719a168e7d256a4f"), - Pair("monster girl", "577e6f90719a168e7d256a46"), - Pair("moral degeneration", "577e71da719a166f4719a3cc"), - Pair("mother", "577e71c7719a166f4719a293"), - Pair("mouse girl", "5782ae45719a1675f68a6e4a"), - Pair("multiple breasts", "5782ae45719a1675f68a6e4b"), - Pair("multiple penises", "577e722a719a166f4719aa29"), - Pair("muscle", "577e7250719a168ef96a753c"), - Pair("nakadashi", "577e6f8e719a168e7d256a26"), - Pair("netorare", "577e71c7719a166f4719a294"), - Pair("niece", "5782b10a719a1679528764b5"), - Pair("nurse", "577e6f8d719a168e7d256a1d"), - Pair("oil", "5782af5e719a1694739008b1"), - Pair("onahole", "582324e5719a1674f99b3444"), - Pair("orgasm denial", "577e725d719a168ef96a762f"), - Pair("paizuri", "577e6f90719a168e7d256a3e"), - Pair("pantyhose", "577e6f8d719a168e7d256a19"), - Pair("pantyjob", "577e7276719a168ef96a77f9"), - Pair("parasite", "577e6f90719a168e7d256a50"), - Pair("pasties", "5782b029719a169473900f3b"), - Pair("piercing", "577e6f90719a168e7d256a59"), - Pair("plant girl", "577e71f4719a166f4719a5fa"), - Pair("policewoman", "57af673b719a1655a6ca8b58"), - Pair("ponygirl", "577e6f90719a168e7d256a5a"), - Pair("possession", "5782aff7719a169473900d8b"), - Pair("pregnant", "577e71da719a166f4719a3cd"), - Pair("prolapse", "5782cc79719a165f600844e0"), - Pair("prostitution", "577e7242719a168ef96a7466"), - Pair("pubic stubble", "577e71da719a166f4719a3ce"), - Pair("public use", "5782cc79719a165f600844e1"), - Pair("rape", "577e6f90719a168e7d256a51"), - Pair("rimjob", "577e725f719a168ef96a765e"), - Pair("robot", "5782b144719a1679528766f3"), - Pair("ryona", "577e723e719a168ef96a7424"), - Pair("saliva", "5884ed6f719a1678dfbb2258"), - Pair("scar", "5782b081719a167952876168"), - Pair("school swimsuit", "5782b05f719a169473901177"), - Pair("schoolgirl uniform", "577e7199719a16697b9853e6"), - Pair("selfcest", "5782b152719a16795287677e"), - Pair("sex toys", "577e6f90719a168e7d256a5b"), - Pair("sheep girl", "5782affa719a169473900da2"), - Pair("shemale", "577e7267719a168ef96a76f1"), - Pair("shibari", "577e72a6719a168ef96a7b18"), - Pair("shimapan", "5782aebd719a1694739004c5"), - Pair("sister", "577e6f8c719a168e7d256a14"), - Pair("slave", "577e71b4719a166f4719a138"), - Pair("sleeping", "577e71e5719a166f4719a4b3"), - Pair("slime", "577e6f93719a168e7d256a95"), - Pair("slime girl", "577e6f90719a168e7d256a48"), - Pair("small breasts", "577e6f90719a168e7d256a5f"), - Pair("smell", "577e7210719a166f4719a824"), - Pair("snake girl", "577e721e719a166f4719a94b"), - Pair("sole dickgirl", "582324e5719a1674f99b3445"), - Pair("sole female", "577e6f91719a168e7d256a7a"), - Pair("solo action", "5782afbf719a169473900ba2"), - Pair("spanking", "577e7199719a16697b9853e7"), - Pair("squirting", "577e7250719a168ef96a753d"), - Pair("stockings", "577e6f8d719a168e7d256a1a"), - Pair("stomach deformation", "5782aef2719a169473900606"), - Pair("strap-on", "577e71d5719a166f4719a367"), - Pair("stuck in wall", "5782aecf719a16947390055b"), - Pair("sundress", "577e7216719a166f4719a8a2"), - Pair("sweating", "577e71b5719a166f4719a13d"), - Pair("swimsuit", "577e71d3719a166f4719a342"), - Pair("swinging", "577e7203719a166f4719a723"), - Pair("syringe", "577e71da719a166f4719a3cf"), - Pair("tall girl", "577e71d9719a166f4719a3c1"), - Pair("tanlines", "577e6f91719a168e7d256a7b"), - Pair("teacher", "577e7199719a16697b9853e8"), - Pair("tentacles", "577e6f90719a168e7d256a52"), - Pair("thigh high boots", "577e6f93719a168e7d256a96"), - Pair("tiara", "5782cc74719a165f600844d3"), - Pair("tights", "5782b059719a169473901132"), - Pair("tomboy", "577e7201719a166f4719a6fb"), - Pair("torture", "577e725d719a168ef96a7630"), - Pair("tracksuit", "5782b146719a167952876708"), - Pair("transformation", "577e6f90719a168e7d256a4a"), - Pair("tribadism", "577e6f90719a168e7d256a60"), - Pair("tube", "577e7208719a166f4719a78f"), - Pair("tutor", "5782af34719a1694739007a3"), - Pair("twins", "577e726a719a168ef96a7729"), - Pair("unusual pupils", "577e6f90719a168e7d256a53"), - Pair("urethra insertion", "5877c07f719a163627a2ceb0"), - Pair("urination", "577e7210719a166f4719a825"), - Pair("vaginal sticker", "577e721c719a166f4719a930"), - Pair("vomit", "5782ae45719a1675f68a6e4c"), - Pair("vore", "577e6f8c719a168e7d256a15"), - Pair("voyeurism", "583ca1ef719a161795a60847"), - Pair("waitress", "5782ae3f719a1675f68a6e1a"), - Pair("widow", "5782b13c719a16795287669d"), - Pair("wings", "5782b158719a1679528767d7"), - Pair("witch", "577e6f93719a168e7d256a97"), - Pair("wolf girl", "577e724c719a168ef96a74fd"), - Pair("wrestling", "577e7230719a166f4719aa8e"), - Pair("x-ray", "577e6f90719a168e7d256a40"), - Pair("yandere", "577e7295719a168ef96a79e7"), - Pair("yuri", "577e6f90719a168e7d256a4c") - ) -} diff --git a/src/ja/mangaraw/AndroidManifest.xml b/src/ja/mangaraw/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ja/mangaraw/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ja/mangaraw/build.gradle b/src/ja/mangaraw/build.gradle deleted file mode 100644 index 4daf11c11..000000000 --- a/src/ja/mangaraw/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaRaw' - pkgNameSuffix = 'ja.mangaraw' - extClass = '.MangaRawFactory' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ja/mangaraw/res/mipmap-hdpi/ic_launcher.png b/src/ja/mangaraw/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 601d86661..000000000 Binary files a/src/ja/mangaraw/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/mangaraw/res/mipmap-mdpi/ic_launcher.png b/src/ja/mangaraw/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 969acba92..000000000 Binary files a/src/ja/mangaraw/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/mangaraw/res/mipmap-xhdpi/ic_launcher.png b/src/ja/mangaraw/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 18380c9ca..000000000 Binary files a/src/ja/mangaraw/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/mangaraw/res/mipmap-xxhdpi/ic_launcher.png b/src/ja/mangaraw/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7d6b77d4f..000000000 Binary files a/src/ja/mangaraw/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/mangaraw/res/mipmap-xxxhdpi/ic_launcher.png b/src/ja/mangaraw/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6c853797b..000000000 Binary files a/src/ja/mangaraw/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/mangaraw/res/web_hi_res_512.png b/src/ja/mangaraw/res/web_hi_res_512.png deleted file mode 100644 index ba73ec67f..000000000 Binary files a/src/ja/mangaraw/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ja/mangaraw/src/eu/kanade/tachiyomi/extension/ja/mangaraw/MangaRaw.kt b/src/ja/mangaraw/src/eu/kanade/tachiyomi/extension/ja/mangaraw/MangaRaw.kt deleted file mode 100644 index c92ace123..000000000 --- a/src/ja/mangaraw/src/eu/kanade/tachiyomi/extension/ja/mangaraw/MangaRaw.kt +++ /dev/null @@ -1,83 +0,0 @@ -package eu.kanade.tachiyomi.extension.ja.mangaraw - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Protocol -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -abstract class MangaRaw( - override val name: String, - override val baseUrl: String -) : ParsedHttpSource() { - - override val lang = "ja" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .protocols(listOf(Protocol.HTTP_1_1)) - .build() - - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/seachlist/page/$page/?cat=-1", headers) - - override fun popularMangaSelector() = "article" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("a:has(img)").attr("href")) - title = element.select("img").attr("alt").substringBefore("(RAW – Free)").trim() - thumbnail_url = element.select("img").attr("abs:src") - } - - override fun popularMangaNextPageSelector() = ".next.page-numbers" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/newmanga/page/$page", headers) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/page/$page/?s=$query", headers) - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - genre = document.select("p.has-text-color:has(strong) a").joinToString { it.text() } - description = document.select("p.has-text-color:not(:has(strong))").first().text() - thumbnail_url = document.select(".wp-block-image img").attr("abs:src") - } - - override fun chapterListSelector() = ".chapList a" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.text().trim() - } - - override fun pageListParse(document: Document): List<Page> { - return document.select(".wp-block-image > img").mapIndexed { i, element -> - val attribute = if (element.hasAttr("data-src")) "data-src" else "src" - Page(i, "", element.attr(attribute)) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun getFilterList() = FilterList() -} diff --git a/src/ja/mangaraw/src/eu/kanade/tachiyomi/extension/ja/mangaraw/MangaRawFactory.kt b/src/ja/mangaraw/src/eu/kanade/tachiyomi/extension/ja/mangaraw/MangaRawFactory.kt deleted file mode 100644 index b2a6d144c..000000000 --- a/src/ja/mangaraw/src/eu/kanade/tachiyomi/extension/ja/mangaraw/MangaRawFactory.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.tachiyomi.extension.ja.mangaraw - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class MangaRawFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - Manga1000(), - Manga1001() - ) -} - -class Manga1000 : MangaRaw("Manga1000", "https://manga1000.com") -class Manga1001 : MangaRaw("Manga1001", "https://manga1001.com") diff --git a/src/ja/nikkangecchan/AndroidManifest.xml b/src/ja/nikkangecchan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ja/nikkangecchan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ja/nikkangecchan/build.gradle b/src/ja/nikkangecchan/build.gradle deleted file mode 100644 index 2a1337d09..000000000 --- a/src/ja/nikkangecchan/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Nikkangecchan' - pkgNameSuffix = 'ja.nikkangecchan' - extClass = '.Nikkangecchan' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ja/nikkangecchan/res/mipmap-hdpi/ic_launcher.png b/src/ja/nikkangecchan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0f59927f1..000000000 Binary files a/src/ja/nikkangecchan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/nikkangecchan/res/mipmap-mdpi/ic_launcher.png b/src/ja/nikkangecchan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 70e48fc27..000000000 Binary files a/src/ja/nikkangecchan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/nikkangecchan/res/mipmap-xhdpi/ic_launcher.png b/src/ja/nikkangecchan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 63c6eb20e..000000000 Binary files a/src/ja/nikkangecchan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/nikkangecchan/res/mipmap-xxhdpi/ic_launcher.png b/src/ja/nikkangecchan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 5f9f02704..000000000 Binary files a/src/ja/nikkangecchan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/nikkangecchan/res/mipmap-xxxhdpi/ic_launcher.png b/src/ja/nikkangecchan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d7166dac5..000000000 Binary files a/src/ja/nikkangecchan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/nikkangecchan/res/web_hi_res_512.png b/src/ja/nikkangecchan/res/web_hi_res_512.png deleted file mode 100644 index fe0e4d059..000000000 Binary files a/src/ja/nikkangecchan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ja/nikkangecchan/src/eu/kanade/tachiyomi/extension/ja/nikkangecchan/Nikkangecchan.kt b/src/ja/nikkangecchan/src/eu/kanade/tachiyomi/extension/ja/nikkangecchan/Nikkangecchan.kt deleted file mode 100644 index 8550824d3..000000000 --- a/src/ja/nikkangecchan/src/eu/kanade/tachiyomi/extension/ja/nikkangecchan/Nikkangecchan.kt +++ /dev/null @@ -1,130 +0,0 @@ -package eu.kanade.tachiyomi.extension.ja.nikkangecchan - -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable - -class Nikkangecchan : ParsedHttpSource() { - - override val name = "Nikkangecchan" - - override val baseUrl = "https://nikkangecchan.jp" - - override val lang = "ja" - - override val supportsLatest = false - - private val catalogHeaders = Headers.Builder() - .apply { - add("User-Agent", USER_AGENT) - add("Referer", baseUrl) - } - .build() - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, catalogHeaders) - - override fun popularMangaSelector(): String = ".contentInner > figure" - - override fun popularMangaFromElement(element: Element): SManga { - val imgBox = element.select(".imgBox").first() - val detailBox = element.select(".detailBox").last() - - return SManga.create().apply { - title = detailBox.select("h3").first().text() - thumbnail_url = baseUrl + imgBox.select("a > img").first().attr("src") - setUrlWithoutDomain(baseUrl + imgBox.select("a").first().attr("href")) - } - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return super.fetchSearchManga(page, query, filters) - .map { - val filtered = it.mangas.filter { e -> e.title.contains(query, true) } - MangasPage(filtered, false) - } - } - - // Does not have search, use complete list (in popular) instead. - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - popularMangaRequest(page) - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga { - val detailBox = document.select("#comicDetail .detailBox") - - return SManga.create().apply { - title = detailBox.select("h3").first().text() - author = detailBox.select(".author").first().text() - artist = detailBox.select(".author").first().text() - description = document.select(".description").first().text() - status = SManga.ONGOING - } - } - - override fun chapterListSelector(): String = ".episodeBox" - - override fun chapterListParse(response: Response): List<SChapter> = - super.chapterListParse(response).reversed() - - override fun chapterFromElement(element: Element): SChapter { - val episodePage = element.select(".episode-page").first() - val title = element.select("h4.episodeTitle").first().text() - val dataTitle = episodePage.attr("data-title").substringBefore("|").trim() - - return SChapter.create().apply { - name = "$title - $dataTitle" - chapter_number = element.select("h4.episodeTitle").first().text().toFloatOrNull() ?: -1f - scanlator = "Akita Publishing" - setUrlWithoutDomain(baseUrl + episodePage.attr("data-src").substringBeforeLast("/")) - } - } - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return Observable.just(listOf(Page(0, chapter.url, "$baseUrl${chapter.url}/image"))) - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val headers = Headers.Builder() - .apply { - add("User-Agent", USER_AGENT) - add("Referer", baseUrl + page.url.substringBeforeLast("/")) - } - - return GET(page.imageUrl!!, headers.build()) - } - - override fun latestUpdatesSelector() = "" - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("This method should not be called!") - - override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("This method should not be called!") - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun pageListRequest(chapter: SChapter): Request = throw Exception("This method should not be called!") - - override fun pageListParse(document: Document): List<Page> = throw Exception("This method should not be called!") - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36" - } -} diff --git a/src/ja/rawdevart/AndroidManifest.xml b/src/ja/rawdevart/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ja/rawdevart/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ja/rawdevart/build.gradle b/src/ja/rawdevart/build.gradle deleted file mode 100644 index cb3b02fe2..000000000 --- a/src/ja/rawdevart/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Rawdevart' - pkgNameSuffix = 'ja.rawdevart' - extClass = '.Rawdevart' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ja/rawdevart/res/mipmap-hdpi/ic_launcher.png b/src/ja/rawdevart/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index dc85536eb..000000000 Binary files a/src/ja/rawdevart/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/rawdevart/res/mipmap-mdpi/ic_launcher.png b/src/ja/rawdevart/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2f3891937..000000000 Binary files a/src/ja/rawdevart/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/rawdevart/res/mipmap-xhdpi/ic_launcher.png b/src/ja/rawdevart/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d8c8c0008..000000000 Binary files a/src/ja/rawdevart/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/rawdevart/res/mipmap-xxhdpi/ic_launcher.png b/src/ja/rawdevart/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2762e913e..000000000 Binary files a/src/ja/rawdevart/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/rawdevart/res/mipmap-xxxhdpi/ic_launcher.png b/src/ja/rawdevart/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6b09149dd..000000000 Binary files a/src/ja/rawdevart/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/rawdevart/res/web_hi_res_512.png b/src/ja/rawdevart/res/web_hi_res_512.png deleted file mode 100644 index d135b815d..000000000 Binary files a/src/ja/rawdevart/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ja/rawdevart/src/eu/kanade/tachiyomi/extension/ja/rawdevart/Rawdevart.kt b/src/ja/rawdevart/src/eu/kanade/tachiyomi/extension/ja/rawdevart/Rawdevart.kt deleted file mode 100644 index c9541b02c..000000000 --- a/src/ja/rawdevart/src/eu/kanade/tachiyomi/extension/ja/rawdevart/Rawdevart.kt +++ /dev/null @@ -1,351 +0,0 @@ -package eu.kanade.tachiyomi.extension.ja.rawdevart - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -class Rawdevart : ParsedHttpSource() { - - override val name = "Rawdevart" - - override val baseUrl = "https://rawdevart.com" - - override val lang = "ja" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun latestUpdatesRequest(page: Int) = - GET("$baseUrl/comic/?page=$page&lister=0") - - override fun latestUpdatesSelector() = "div.row div.hovereffect" - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - val item = element.select("a.head") - manga.setUrlWithoutDomain(item.attr("href")) - manga.title = item.text() - manga.thumbnail_url = element.select("img").attr("abs:src") - - return manga - } - - override fun latestUpdatesNextPageSelector() = "li.page-item:not(.disabled) a[aria-label=next]" - - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/comic/?page=$page&lister=5") - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search/")!!.newBuilder() - url.addQueryParameter("page", page.toString()) - url.addQueryParameter("title", query) - filters.forEach { filter -> - when (filter) { - is AuthorFilter -> { - url.addQueryParameter("author", filter.state) - } - is ArtistFilter -> { - url.addQueryParameter("artist", filter.state) - } - is SortFilter -> { - url.addQueryParameter("lister", filter.toUriPart()) - } - is TypeFilter -> { - val typeToExclude = mutableListOf<String>() - val typeToInclude = mutableListOf<String>() - filter.state.forEach { content -> - if (content.isExcluded()) - typeToExclude.add(content.id) - else if (content.isIncluded()) - typeToInclude.add(content.id) - } - if (typeToExclude.isNotEmpty()) { - url.addQueryParameter( - "ctype_exc", - typeToExclude - .joinToString(",") - ) - } - if (typeToInclude.isNotEmpty()) { - url.addQueryParameter( - "ctype_inc", - typeToInclude - .joinToString(",") - ) - } - } - is StatusFilter -> { - val statusToExclude = mutableListOf<String>() - val statusToInclude = mutableListOf<String>() - filter.state.forEach { content -> - if (content.isExcluded()) - statusToExclude.add(content.id) - else if (content.isIncluded()) - statusToInclude.add(content.id) - } - if (statusToExclude.isNotEmpty()) { - url.addQueryParameter( - "status_exc", - statusToExclude - .joinToString(",") - ) - } - if (statusToInclude.isNotEmpty()) { - url.addQueryParameter( - "status_inc", - statusToInclude - .joinToString(",") - ) - } - } - is GenreFilter -> { - val genreToExclude = mutableListOf<String>() - val genreToInclude = mutableListOf<String>() - filter.state.forEach { content -> - if (content.isExcluded()) - genreToExclude.add(content.id) - else if (content.isIncluded()) - genreToInclude.add(content.id) - } - if (genreToExclude.isNotEmpty()) { - url.addQueryParameter( - "genre_exc", - genreToExclude - .joinToString(",") - ) - } - if (genreToInclude.isNotEmpty()) { - url.addQueryParameter( - "genre_inc", - genreToInclude - .joinToString(",") - ) - } - } - } - } - - return GET(url.build().toString(), headers) - } - - override fun searchMangaSelector() = latestUpdatesSelector() - - override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun mangaDetailsRequest(manga: SManga): Request { - if (manga.url.startsWith("http")) { - return GET(manga.url, headers) - } - return super.mangaDetailsRequest(manga) - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.manga-main") - val manga = SManga.create() - val status = infoElement.select("th:contains(Status) + td").text() - val genres = mutableListOf<String>() - infoElement.select("div.genres a").forEach { element -> - val genre = element.text() - genres.add(genre) - } - manga.title = infoElement.select("h1").text() - manga.author = infoElement.select("th:contains(Author) + td").text() - manga.artist = infoElement.select("th:contains(Artist) + td").text() - manga.status = parseStatus(status) - manga.genre = genres.joinToString(", ") - manga.description = infoElement.select("div.description").text() - .substringAfter("Description ") - manga.thumbnail_url = infoElement.select("img.img-fluid.not-lazy").attr("abs:src") - - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Finished") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListRequest(manga: SManga) = chapterListRequest(manga.url, 1) - - private fun chapterListRequest(mangaUrl: String, page: Int): Request { - return GET("$baseUrl$mangaUrl?page=$page", headers) - } - - override fun chapterListSelector() = "div.list-group-item" - - override fun chapterListParse(response: Response): List<SChapter> { - var document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - var nextPage = 2 - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - while (document.select(paginationNextPageSelector).isNotEmpty()) { - val currentPage = document.select("form#filter_form").attr("action") - document = client.newCall(chapterListRequest(currentPage, nextPage)).execute().asJsoup() - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - nextPage++ - } - - return chapters - } - - private val paginationNextPageSelector = latestUpdatesNextPageSelector() - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.select("a").attr("href")) - chapter.name = element.select("div.rounded-0 span.text-truncate").text() - chapter.date_upload = element.select("span.mr-2").text().let { - try { - when { - it.contains("ago") -> Date(System.currentTimeMillis() - it.split("\\s".toRegex())[0].toLong() * 60 * 60 * 1000).time - it.contains("Yesterday") -> Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000).time - it.contains(".") -> SimpleDateFormat("MMM. dd, yyyy", Locale.US).parse(it)?.time ?: 0L - else -> SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it)?.time ?: 0L - } - } catch (e: Exception) { - Date(System.currentTimeMillis()).time - } - } - - return chapter - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - document.select("img.not-lazy[data-src]").forEachIndexed { i, img -> - pages.add(Page(i, "", img.attr("data-src"))) - } - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - private class AuthorFilter : Filter.Text("Author") - - private class ArtistFilter : Filter.Text("Artist") - - private class SortFilter : UriPartFilter( - "Sort By", - arrayOf( - Pair("<select>", ""), - Pair("Latest", "0"), - Pair("A-Z", "1"), - Pair("Z-A", "2"), - Pair("Star", "3"), - Pair("Bookmark", "4"), - Pair("View", "5") - ) - ) - - private class TypeFilter(type: List<Tag>) : Filter.Group<Tag>("Types", type) - - private class StatusFilter(status: List<Tag>) : Filter.Group<Tag>("Status", status) - - private class GenreFilter(genres: List<Tag>) : Filter.Group<Tag>("Genres", genres) - - override fun getFilterList() = FilterList( - Filter.Header("Combine Sort filter with other filters."), - Filter.Separator(), - AuthorFilter(), - ArtistFilter(), - SortFilter(), - TypeFilter(getTypeList()), - StatusFilter(getStatusList()), - GenreFilter(getGenreList()) - ) - - private fun getTypeList() = listOf( - Tag("0", "Manga"), - Tag("1", "Webtoon"), - Tag("2", "Manhwa - Korean"), - Tag("3", "Manhua - Chinese"), - Tag("4", "Comic"), - Tag("5", "Doujinshi") - ) - - private fun getStatusList() = listOf( - Tag("0", "Ongoing"), - Tag("1", "Haitus"), - Tag("2", "Axed"), - Tag("3", "Unknown"), - Tag("4", "Finished") - ) - - private fun getGenreList() = listOf( - Tag("29", "4-koma"), - Tag("1", "Action"), - Tag("37", "Adult"), - Tag("2", "Adventure"), - Tag("3", "Comedy"), - Tag("33", "Cooking"), - Tag("4", "Crime"), - Tag("5", "Drama"), - Tag("30", "Ecchi"), - Tag("6", "Fantasy"), - Tag("34", "Gender Bender"), - Tag("31", "Gore"), - Tag("39", "Harem"), - Tag("7", "Historical"), - Tag("8", "Horror"), - Tag("9", "Isekai"), - Tag("42", "Josei"), - Tag("35", "Martial Arts"), - Tag("36", "Mature"), - Tag("10", "Mecha"), - Tag("11", "Medical"), - Tag("38", "Music"), - Tag("12", "Mystery"), - Tag("13", "Philosophical"), - Tag("14", "Psychological"), - Tag("15", "Romance"), - Tag("40", "School Life"), - Tag("16", "Sci-Fi"), - Tag("41", "Seinen"), - Tag("28", "Shoujo"), - Tag("17", "Shoujo Ai"), - Tag("27", "Shounen"), - Tag("18", "Shounen Ai"), - Tag("19", "Slice of Life"), - Tag("32", "Smut"), - Tag("20", "Sports"), - Tag("21", "Super Powers"), - Tag("43", "Supernatural"), - Tag("22", "Thriller"), - Tag("23", "Tragedy"), - Tag("24", "Wuxia"), - Tag("25", "Yaoi"), - Tag("26", "Yuri") - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private class Tag(val id: String, name: String) : Filter.TriState(name) -} diff --git a/src/ja/senmanga/AndroidManifest.xml b/src/ja/senmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ja/senmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ja/senmanga/build.gradle b/src/ja/senmanga/build.gradle deleted file mode 100644 index 880859102..000000000 --- a/src/ja/senmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Sen Manga' - pkgNameSuffix = 'ja.senmanga' - extClass = '.SenManga' - extVersionCode = 5 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ja/senmanga/res/mipmap-hdpi/ic_launcher.png b/src/ja/senmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ef6c85728..000000000 Binary files a/src/ja/senmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/senmanga/res/mipmap-mdpi/ic_launcher.png b/src/ja/senmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fe37410fe..000000000 Binary files a/src/ja/senmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/senmanga/res/mipmap-xhdpi/ic_launcher.png b/src/ja/senmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7d54b459b..000000000 Binary files a/src/ja/senmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/senmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ja/senmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 52882adca..000000000 Binary files a/src/ja/senmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/senmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ja/senmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 418a8dbfc..000000000 Binary files a/src/ja/senmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/senmanga/res/web_hi_res_512.png b/src/ja/senmanga/res/web_hi_res_512.png deleted file mode 100644 index 0d4557fd6..000000000 Binary files a/src/ja/senmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ja/senmanga/src/eu/kanade/tachiyomi/extension/ja/senmanga/SenManga.kt b/src/ja/senmanga/src/eu/kanade/tachiyomi/extension/ja/senmanga/SenManga.kt deleted file mode 100644 index 9aec9a1aa..000000000 --- a/src/ja/senmanga/src/eu/kanade/tachiyomi/extension/ja/senmanga/SenManga.kt +++ /dev/null @@ -1,203 +0,0 @@ -package eu.kanade.tachiyomi.extension.ja.senmanga - -import android.annotation.SuppressLint -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -/** - * Sen Manga source - */ - -class SenManga : ParsedHttpSource() { - override val lang: String = "ja" - - override val supportsLatest = true - override val name = "Sen Manga" - override val baseUrl = "https://raw.senmanga.com" - - @SuppressLint("DefaultLocale") - override val client = super.client.newBuilder().addInterceptor { - // Intercept any image requests and add a referer to them - // Enables bandwidth stealing feature - val request = if (it.request().url().pathSegments().firstOrNull()?.trim()?.toLowerCase() == "viewer") { - it.request().newBuilder() - .addHeader( - "Referer", - it.request().url().newBuilder() - .removePathSegment(0) - .toString() - ) - .build() - } else it.request() - it.proceed(request) - }.build()!! - - override fun popularMangaSelector() = "div.item" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.select("a").let { - setUrlWithoutDomain(it.attr("href")) - title = it.select("div.series-title").text() - } - thumbnail_url = element.select("img").attr("abs:src") - } - - override fun popularMangaNextPageSelector() = "ul.pagination a[rel=next]" - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/directory/popular?page=$page") - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("s", query) - .addQueryParameter("page", page.toString()) - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - val genreInclude = filter.state.filter { it.isIncluded() }.joinToString("%2C") { it.id } - val genreExclude = filter.state.filter { it.isExcluded() }.joinToString("%2C") { it.id } - url.addQueryParameter("genre", genreInclude) - url.addQueryParameter("nogenre", genreExclude) - } - is SortFilter -> url.addQueryParameter("sort", filter.toUriPart()) - } - } - return GET(url.toString(), headers) - } - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select("h1.series").text() - - thumbnail_url = document.select("div.cover img").first().attr("src") - - description = document.select("div.summary").first().text() - - val seriesElement = document.select("div.series-desc .info ") - - genre = seriesElement.select(".item:eq(0)").text().substringAfter(": ") - status = seriesElement.select(".item:eq(1)").first()?.text().orEmpty().let { parseStatus(it.substringAfter("Status:")) } - author = seriesElement.select(".item:eq(3)").text().substringAfter(": ") - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Complete") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/directory/last_update?page=$page", headers) - } - - override fun chapterListSelector() = "ul.chapter-list li" - - @SuppressLint("DefaultLocale") - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val linkElement = element.getElementsByTag("a") - - setUrlWithoutDomain(linkElement.attr("href")) - - name = linkElement.first().text() - - chapter_number = element.child(0).text().trim().toFloatOrNull() ?: -1f - - date_upload = parseDate(element.select("time").attr("datetime")) - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0 - } - - override fun pageListParse(document: Document): List<Page> { - return listOf(1..document.select("select[name=page] option:last-of-type").first().text().toInt()).flatten().map { i -> - Page(i - 1, "", "${document.location().replace(baseUrl, "$baseUrl/viewer")}/$i") - } - } - - override fun imageUrlParse(document: Document) = - throw UnsupportedOperationException("This method should not be called!") - - override fun getFilterList() = FilterList( - GenreFilter(getGenreList()), - SortFilter() - ) - - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Genre", genres) - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first - } - - private class SortFilter : UriPartFilter( - "Sort By", - arrayOf( - Pair("total_views", "Total Views"), - Pair("title", "Title"), - Pair("rank", "Rank"), - Pair("last_update", "Last Update") - ) - ) - - private fun getGenreList(): List<Genre> = listOf( - Genre("Action", "Action"), - Genre("Adult", "Adult"), - Genre("Adventure", "Adventure"), - Genre("Comedy", "Comedy"), - Genre("Cooking", "Cooking"), - Genre("Drama", "Drama"), - Genre("Ecchi", "Ecchi"), - Genre("Fantasy", "Fantasy"), - Genre("Gender Bender", "Gender+Bender"), - Genre("Harem", "Harem"), - Genre("Historical", "Historical"), - Genre("Horror", "Horror"), - Genre("Josei", "Josei"), - Genre("Light Novel", "Light+Novel"), - Genre("Martial Arts", "Martial+Arts"), - Genre("Mature", "Mature"), - Genre("Music", "Music"), - Genre("Mystery", "Mystery"), - Genre("Psychological", "Psychological"), - Genre("Romance", "Romance"), - Genre("School Life", "School+Life"), - Genre("Sci-Fi", "Sci+Fi"), - Genre("Seinen", "Seinen"), - Genre("Shoujo", "Shoujo"), - Genre("Shoujo Ai", "Shoujo+Ai"), - Genre("Shounen", "Shounen"), - Genre("Shounen Ai", "Shounen+Ai"), - Genre("Slice of Life", "Slice+of+Life"), - Genre("Smut", "Smut"), - Genre("Sports", "Sports"), - Genre("Supernatural", "Supernatural"), - Genre("Tragedy", "Tragedy"), - Genre("Webtoons", "Webtoons"), - Genre("Yaoi", "Yaoi"), - Genre("Yuri", "Yuri") - ) -} diff --git a/src/ja/shonenjumpplus/AndroidManifest.xml b/src/ja/shonenjumpplus/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ja/shonenjumpplus/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ja/shonenjumpplus/build.gradle b/src/ja/shonenjumpplus/build.gradle deleted file mode 100644 index ccd65c1c1..000000000 --- a/src/ja/shonenjumpplus/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Shonen Jump+' - pkgNameSuffix = 'ja.shonenjumpplus' - extClass = '.ShonenJumpPlus' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ja/shonenjumpplus/res/mipmap-hdpi/ic_launcher.png b/src/ja/shonenjumpplus/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e9eb56a57..000000000 Binary files a/src/ja/shonenjumpplus/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/shonenjumpplus/res/mipmap-mdpi/ic_launcher.png b/src/ja/shonenjumpplus/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3da83cd78..000000000 Binary files a/src/ja/shonenjumpplus/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/shonenjumpplus/res/mipmap-xhdpi/ic_launcher.png b/src/ja/shonenjumpplus/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 3b8ee05b7..000000000 Binary files a/src/ja/shonenjumpplus/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/shonenjumpplus/res/mipmap-xxhdpi/ic_launcher.png b/src/ja/shonenjumpplus/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e801c5560..000000000 Binary files a/src/ja/shonenjumpplus/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/shonenjumpplus/res/mipmap-xxxhdpi/ic_launcher.png b/src/ja/shonenjumpplus/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 027d43fe5..000000000 Binary files a/src/ja/shonenjumpplus/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ja/shonenjumpplus/res/web_hi_res_512.png b/src/ja/shonenjumpplus/res/web_hi_res_512.png deleted file mode 100644 index 0e6dc3ea4..000000000 Binary files a/src/ja/shonenjumpplus/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ja/shonenjumpplus/src/eu/kanade/tachiyomi/extension/ja/shonenjumpplus/ShonenJumpPlus.kt b/src/ja/shonenjumpplus/src/eu/kanade/tachiyomi/extension/ja/shonenjumpplus/ShonenJumpPlus.kt deleted file mode 100644 index c5be2d3cf..000000000 --- a/src/ja/shonenjumpplus/src/eu/kanade/tachiyomi/extension/ja/shonenjumpplus/ShonenJumpPlus.kt +++ /dev/null @@ -1,287 +0,0 @@ -package eu.kanade.tachiyomi.extension.ja.shonenjumpplus - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.io.ByteArrayOutputStream -import java.io.InputStream -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale -import kotlin.math.floor - -class ShonenJumpPlus : ParsedHttpSource() { - - override val name = "Shonen Jump+" - - override val baseUrl = "https://shonenjumpplus.com" - - override val lang = "ja" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor { imageIntercept(it) } - .build() - - private val dayOfWeek: String by lazy { - Calendar.getInstance() - .getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.US)!! - .toLowerCase(Locale.US) - } - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/series", headers) - - override fun popularMangaSelector(): String = "ul.series-list li a" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h2.series-list-title").text() - thumbnail_url = element.select("div.series-list-thumb img") - .attr("data-src") - setUrlWithoutDomain(element.attr("href")) - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesSelector(): String = "h2.series-list-date-week.$dayOfWeek + ul.series-list li a" - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotEmpty()) { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("q", query) - - return GET(url.toString(), headers) - } - - val listMode = (filters[0] as SeriesListModeFilter).state - return GET("$baseUrl/series/${LIST_MODES[listMode].second}", headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - if (response.request().url().toString().contains("search")) - return super.searchMangaParse(response) - - return popularMangaParse(response) - } - - override fun searchMangaSelector() = "ul.search-series-list li" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("div.title-box p.series-title").text() - thumbnail_url = element.select("div.thmb-container a img").attr("src") - setUrlWithoutDomain(element.select("div.thmb-container a").attr("href")) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select("section.series-information div.series-header") - - title = infoElement.select("h1.series-header-title").text() - author = infoElement.select("h2.series-header-author").text() - artist = author - description = infoElement.select("p.series-header-description").text() - thumbnail_url = infoElement.select("div.series-header-image-wrapper img") - .attr("data-src") - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val readableProductList = document.select("div.js-readable-product-list").first()!! - val latestListEndpoint = HttpUrl.parse(readableProductList.attr("data-latest-list-endpoint"))!! - val firstListEndpoint = HttpUrl.parse(readableProductList.attr("data-first-list-endpoint"))!! - val numberSince = latestListEndpoint.queryParameter("number_since")!!.toInt() - .coerceAtLeast(firstListEndpoint.queryParameter("number_since")!!.toInt()) - - val newHeaders = headers.newBuilder() - .set("Referer", response.request().url().toString()) - .build() - var readMoreEndpoint = firstListEndpoint.newBuilder() - .setQueryParameter("number_since", numberSince.toString()) - .toString() - - val chapters = mutableListOf<SChapter>() - - var request = GET(readMoreEndpoint, newHeaders) - var result = client.newCall(request).execute() - - while (result.code() != 404) { - val json = result.asJsonObject() - readMoreEndpoint = json["nextUrl"].string - val tempDocument = Jsoup.parse(json["html"].string, response.request().url().toString()) - - chapters += tempDocument - .select("ul.series-episode-list " + chapterListSelector()) - .map { element -> chapterFromElement(element) } - - request = GET(readMoreEndpoint, newHeaders) - result = client.newCall(request).execute() - } - - return chapters - } - - override fun chapterListSelector() = "li.episode:has(span.series-episode-list-is-free)" - - override fun chapterFromElement(element: Element): SChapter { - val info = element.select("a.series-episode-list-container").first() ?: element - val mangaUrl = element.ownerDocument().location() - - return SChapter.create().apply { - name = info.select("h4.series-episode-list-title").text() - date_upload = info.select("span.series-episode-list-date").first() - ?.text().orEmpty() - .tryParseDate() - scanlator = "集英社" - setUrlWithoutDomain(if (info.tagName() == "a") info.attr("href") else mangaUrl) - } - } - - override fun pageListParse(document: Document): List<Page> { - val episodeJson = document.select("script#episode-json") - .attr("data-value") - .let { JSON_PARSER.parse(it).obj } - - return episodeJson["readableProduct"]["pageStructure"]["pages"].asJsonArray - .filter { it["type"].string == "main" } - .mapIndexed { i, pageObj -> - val imageUrl = HttpUrl.parse(pageObj["src"].string)!!.newBuilder() - .addQueryParameter("width", pageObj["width"].string) - .addQueryParameter("height", pageObj["height"].string) - .toString() - Page(i, document.location(), imageUrl) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private class SeriesListModeFilter : Filter.Select<String>( - "一覧", - LIST_MODES.map { it.first }.toTypedArray() - ) - - override fun getFilterList(): FilterList = FilterList(SeriesListModeFilter()) - - private fun imageIntercept(chain: Interceptor.Chain): Response { - var request = chain.request() - - if (!request.url().toString().startsWith(CDN_URL)) { - return chain.proceed(request) - } - - val width = request.url().queryParameter("width")!!.toInt() - val height = request.url().queryParameter("height")!!.toInt() - - val newUrl = request.url().newBuilder() - .removeAllQueryParameters("width") - .removeAllQueryParameters("height") - .build() - request = request.newBuilder().url(newUrl).build() - - val response = chain.proceed(request) - val image = decodeImage(response.body()!!.byteStream(), width, height) - val body = ResponseBody.create(MediaType.parse("image/png"), image) - return response.newBuilder().body(body).build() - } - - private fun decodeImage(image: InputStream, width: Int, height: Int): ByteArray { - val input = BitmapFactory.decodeStream(image) - val cWidth = (floor(width.toDouble() / (DIVIDE_NUM * MULTIPLE)) * MULTIPLE).toInt() - val cHeight = (floor(height.toDouble() / (DIVIDE_NUM * MULTIPLE)) * MULTIPLE).toInt() - - val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(result) - - val imageRect = Rect(0, 0, width, height) - canvas.drawBitmap(input, imageRect, imageRect, null) - - for (e in 0 until DIVIDE_NUM * DIVIDE_NUM) { - val x = e % DIVIDE_NUM * cWidth - val y = (floor(e.toFloat() / DIVIDE_NUM) * cHeight).toInt() - val cellSrc = Rect(x, y, x + cWidth, y + cHeight) - - val row = floor(e.toFloat() / DIVIDE_NUM).toInt() - val dstE = e % DIVIDE_NUM * DIVIDE_NUM + row - val dstX = dstE % DIVIDE_NUM * cWidth - val dstY = (floor(dstE.toFloat() / DIVIDE_NUM) * cHeight).toInt() - val cellDst = Rect(dstX, dstY, dstX + cWidth, dstY + cHeight) - canvas.drawBitmap(input, cellSrc, cellDst, null) - } - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - return output.toByteArray() - } - - private fun String.tryParseDate(): Long { - return try { - DATE_PARSER.parse(this)!!.time - } catch (e: ParseException) { - 0L - } - } - - private fun Response.asJsonObject(): JsonObject = JSON_PARSER.parse(body()!!.string()).obj - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36" - private val JSON_PARSER by lazy { JsonParser() } - private val DATE_PARSER by lazy { SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH) } - - private val LIST_MODES = listOf( - Pair("ジャンプ+連載一覧", ""), - Pair("ジャンプ+読切シリーズ", "oneshot"), - Pair("連載終了作品", "finished") - ) - - private const val CDN_URL = "https://cdn-ak-img.shonenjumpplus.com" - private const val DIVIDE_NUM = 4 - private const val MULTIPLE = 8 - } -} diff --git a/src/ko/jmana/AndroidManifest.xml b/src/ko/jmana/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ko/jmana/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ko/jmana/build.gradle b/src/ko/jmana/build.gradle deleted file mode 100644 index 7ee85a0af..000000000 --- a/src/ko/jmana/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'JMana' - pkgNameSuffix = 'ko.jmana' - extClass = '.JMana' - extVersionCode = 11 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ko/jmana/res/mipmap-hdpi/ic_launcher.png b/src/ko/jmana/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c94effc07..000000000 Binary files a/src/ko/jmana/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/jmana/res/mipmap-mdpi/ic_launcher.png b/src/ko/jmana/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index affd710a9..000000000 Binary files a/src/ko/jmana/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/jmana/res/mipmap-xhdpi/ic_launcher.png b/src/ko/jmana/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 412985531..000000000 Binary files a/src/ko/jmana/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/jmana/res/mipmap-xxhdpi/ic_launcher.png b/src/ko/jmana/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 75afb889a..000000000 Binary files a/src/ko/jmana/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/jmana/res/mipmap-xxxhdpi/ic_launcher.png b/src/ko/jmana/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d0aee221a..000000000 Binary files a/src/ko/jmana/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/jmana/res/web_hi_res_512.png b/src/ko/jmana/res/web_hi_res_512.png deleted file mode 100644 index 1375e37eb..000000000 Binary files a/src/ko/jmana/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ko/jmana/src/eu/kanade/tachiyomi/extension/ko/jmana/JMana.kt b/src/ko/jmana/src/eu/kanade/tachiyomi/extension/ko/jmana/JMana.kt deleted file mode 100644 index 47978a2e7..000000000 --- a/src/ko/jmana/src/eu/kanade/tachiyomi/extension/ko/jmana/JMana.kt +++ /dev/null @@ -1,230 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.jmana - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.widget.Toast -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale - -/** - * JMana Source - **/ -class JMana : ConfigurableSource, ParsedHttpSource() { - override val name = "JMana" - override val baseUrl: String by lazy { getPrefBaseUrl()!!.removeSuffix("/") } - override val lang: String = "ko" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - - private fun String.cleaned() = this.replace(" ", "%20").replace(Regex("/[0-9]+(?!.*?/)"), "") - - override fun popularMangaSelector() = "div.conts > ul > li a" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - setUrlWithoutDomain(element.attr("href").cleaned()) - title = element.select("span.price").text() - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("This method should not be called!") - - // Do not add page parameter if page is 1 to prevent tracking. - override fun popularMangaRequest(page: Int) = GET("$baseUrl/comic_main_frame?tag=null&keyword=null&chosung=null&page=${page - 1}", headers) - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - - // Can not detect what page is last page but max mangas are 15 per page. - val hasNextPage = mangas.size == 15 - - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaSelector() = popularMangaSelector() - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("This method should not be called!") - override fun searchMangaParse(response: Response) = popularMangaParse(response) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comic_main_frame?page=${page - 1}&keyword=$query", headers) - - override fun mangaDetailsParse(document: Document): SManga { - val descriptionElement = document.select(".media > .row > .media-body.col-9 > div") - - val manga = SManga.create() - descriptionElement - .map { it.text() } - .forEach { text -> - when { - DETAIL_TITLE in text -> manga.title = text.substringAfter(DETAIL_TITLE).trim() - DETAIL_AUTHOR in text -> manga.author = text.substringAfter(DETAIL_AUTHOR).trim() - DETAIL_GENRE in text -> manga.genre = text.substringAfter("장르 : [").substringBefore("]").trim() - } - } - manga.description = descriptionElement.select("#desc").text().substringAfter(DETAIL_DESCRIPTION).trim() - manga.thumbnail_url = document.select("div.media-left img").attr("abs:src") - manga.status = SManga.UNKNOWN - return manga - } - - override fun chapterListSelector() = "div.section > .post > .post-content-list" - - override fun chapterFromElement(element: Element): SChapter { - val linkElement = element.select(".entry-title a") - val rawName = linkElement.text() - - return SChapter.create().apply { - url = HttpUrl.parse(linkElement.attr("abs:href"))!!.let { "${it.encodedPath()}?${it.encodedQuery()}" } - chapter_number = parseChapterNumber(rawName) - name = rawName.trim() - date_upload = parseChapterDate(element.select("li.publish-date span").last().text()) - } - } - - private fun parseChapterNumber(name: String): Float { - try { - if (name.contains("[단편]")) return 1f - // `특별` means `Special`, so It can be buggy. so pad `편`(Chapter) to prevent false return - if (name.contains("번외") || name.contains("특별편")) return -2f - val regex = Regex("([0-9]+)(?:[-.]([0-9]+))?(?:화)") - val (ch_primal, ch_second) = regex.find(name)!!.destructured - return (ch_primal + if (ch_second.isBlank()) "" else ".$ch_second").toFloatOrNull() ?: -1f - } catch (e: Exception) { - e.printStackTrace() - return -1f - } - } - - private fun parseChapterDate(date: String): Long { - return try { - SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).parse(date)?.time ?: 0 - } catch (e: Exception) { - e.printStackTrace() - 0 - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("ul.view img").mapIndexed { i, img -> - Page(i, "", if (img.hasAttr("data-src")) img.attr("abs:data-src") else img.attr("abs:src")) - } - } - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/comic_recent", headers) - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = mutableListOf<SManga>() - val lastPage = document.select("select#page option:last-of-type").text() - val currentPage = document.select("select#page option[selected]").text() - - document.select(latestUpdatesSelector()).map { mangas.add(latestUpdatesFromElement(it)) } - - return MangasPage(mangas.distinctBy { it.url }, currentPage < lastPage) - } - - override fun latestUpdatesSelector() = "div.contents div.detail ul:not(:first-of-type) li:has(a.btn)" - - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - setUrlWithoutDomain(element.select("a.btn").attr("href").cleaned()) - title = element.select("div.info a").text() - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("This method should not be called!") - - // We are able to get the image URL directly from the page list - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!") - - companion object { - const val DETAIL_TITLE = "제목 : " - const val DETAIL_GENRE = "장르 : " - const val DETAIL_AUTHOR = "작가 : " - const val DETAIL_DESCRIPTION = "설명 : " - const val DEFAULT_BASEURL = "https://003.jmana2.net" - private const val BASE_URL_PREF_TITLE = "Override BaseUrl" - private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_NAME}" - private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Update extension will erase this setting." - private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting." - } - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply { - key = BASE_URL_PREF_TITLE - title = BASE_URL_PREF_TITLE - summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(DEFAULT_BASEURL) - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "Default: $DEFAULT_BASEURL" - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val baseUrlPref = EditTextPreference(screen.context).apply { - key = BASE_URL_PREF_TITLE - title = BASE_URL_PREF_TITLE - summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(DEFAULT_BASEURL) - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "Default: $DEFAULT_BASEURL" - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - } - - private fun getPrefBaseUrl() = preferences.getString(BASE_URL_PREF, DEFAULT_BASEURL) -} diff --git a/src/ko/navercomic/AndroidManifest.xml b/src/ko/navercomic/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ko/navercomic/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ko/navercomic/build.gradle b/src/ko/navercomic/build.gradle deleted file mode 100644 index 889962222..000000000 --- a/src/ko/navercomic/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Naver Comic' - pkgNameSuffix = 'ko.navercomic' - extClass = '.NaverComicFactory' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ko/navercomic/res/mipmap-hdpi/ic_launcher.png b/src/ko/navercomic/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f057cebfd..000000000 Binary files a/src/ko/navercomic/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/navercomic/res/mipmap-mdpi/ic_launcher.png b/src/ko/navercomic/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1e514076b..000000000 Binary files a/src/ko/navercomic/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/navercomic/res/mipmap-xhdpi/ic_launcher.png b/src/ko/navercomic/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index dd227e4a8..000000000 Binary files a/src/ko/navercomic/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/navercomic/res/mipmap-xxhdpi/ic_launcher.png b/src/ko/navercomic/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 39dd99919..000000000 Binary files a/src/ko/navercomic/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/navercomic/res/mipmap-xxxhdpi/ic_launcher.png b/src/ko/navercomic/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0c9abfb46..000000000 Binary files a/src/ko/navercomic/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/navercomic/res/web_hi_res_512.png b/src/ko/navercomic/res/web_hi_res_512.png deleted file mode 100644 index fbd67fedf..000000000 Binary files a/src/ko/navercomic/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComic.kt b/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComic.kt deleted file mode 100644 index a9c9fd204..000000000 --- a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComic.kt +++ /dev/null @@ -1,95 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.navercomic - -import android.annotation.SuppressLint -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Response -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class NaverWebtoon : NaverComicBase("webtoon") { - override val name = "Naver Webtoon" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/$mType/weekday.nhn") - override fun popularMangaSelector() = ".list_area.daily_all .col ul > li" - override fun popularMangaNextPageSelector() = null - override fun popularMangaFromElement(element: Element): SManga { - val thumb = element.select("div.thumb img").first().attr("src") - val title = element.select("a.title").first() - - val manga = SManga.create() - manga.url = title.attr("href").substringBefore("&week") - manga.title = title.text().trim() - manga.thumbnail_url = thumb - return manga - } - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/$mType/weekday.nhn?order=Update") - override fun latestUpdatesSelector() = ".list_area.daily_all .col.col_selected ul > li" - override fun latestUpdatesNextPageSelector() = null - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) -} - -class NaverBestChallenge : NaverComicChallengeBase("bestChallenge") { - override val name = "Naver Webtoon Best Challenge" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn?m=main&order=StarScore") - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn?m=main&order=Update") -} - -class NaverChallenge : NaverComicChallengeBase("challenge") { - override val name = "Naver Webtoon Challenge" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn") - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/genre/$mType.nhn?m=list&order=Update") - - // Chapter list is paginated, but there are no mobile pages to work with - override fun chapterListRequest(manga: SManga) = GET("$baseUrl${manga.url}", headers) - - override fun chapterListSelector() = "tbody tr:not([class])" - - override fun chapterListParse(response: Response): List<SChapter> { - var document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - while (document.select(paginationNextPageSelector).hasText()) { - document.select(paginationNextPageSelector).let { - document = client.newCall(GET(it.attr("abs:href"))).execute().asJsoup() - document.select(chapterListSelector()).map { element -> chapters.add(chapterFromElement(element)) } - } - } - return chapters - } - - override val paginationNextPageSelector = "div.paginate a.next" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - element.select("td + td a").let { - val rawName = it.text() - chapter.url = it.attr("href") - chapter.chapter_number = parseChapterNumber(rawName) - chapter.name = rawName - chapter.date_upload = parseChapterDate(element.select("td.num").text().trim()) - } - return chapter - } - - @SuppressLint("SimpleDateFormat") - private fun parseChapterDate(date: String): Long { - return if (date.contains(":")) { - Calendar.getInstance().timeInMillis - } else { - return try { - SimpleDateFormat("yyyy.MM.dd", Locale.KOREA).parse(date)?.time ?: 0L - } catch (e: Exception) { - e.printStackTrace() - 0 - } - } - } -} diff --git a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicBase.kt b/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicBase.kt deleted file mode 100644 index 8285b7506..000000000 --- a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicBase.kt +++ /dev/null @@ -1,158 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.navercomic - -import android.annotation.SuppressLint -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -abstract class NaverComicBase(protected val mType: String) : ParsedHttpSource() { - override val lang: String = "ko" - override val baseUrl: String = "https://comic.naver.com" - private val mobileUrl = "https://m.comic.naver.com" - override val supportsLatest = true - override val client: OkHttpClient = network.client - - private val mobileHeaders = super.headersBuilder() - .add("Referer", mobileUrl) - .build() - - override fun searchMangaSelector() = ".resultList > li h5 > a" - override fun searchMangaNextPageSelector() = ".paginate a.next" - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/search.nhn?m=$mType&keyword=$query&type=title&page=$page") - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = element.attr("href").substringBefore("&week").substringBefore("&listPage=") - manga.title = element.text().trim() - return manga - } - - override fun chapterListSelector() = "div#ct > ul.section_episode_list li.item" - - // Chapter list is paginated, use mobile version of site for speed and data savings - override fun chapterListRequest(manga: SManga) = chapterListRequest(manga.url, 1) - - private fun chapterListRequest(mangaUrl: String, page: Int): Request { - return GET("$mobileUrl$mangaUrl&page=$page", mobileHeaders) - } - - override fun chapterListParse(response: Response): List<SChapter> { - var document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - var nextPage = 2 - while (document.select(paginationNextPageSelector).isNotEmpty()) { - document.select(paginationNextPageSelector).let { - document = client.newCall(chapterListRequest(it.attr("href"), nextPage)).execute().asJsoup() - document.select(chapterListSelector()).map { element -> chapters.add(chapterFromElement(element)) } - nextPage++ - } - } - return chapters - } - - open val paginationNextPageSelector = "a.btn_next:not(.disabled)" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - val rawName = element.select("span.name").text() - chapter.url = element.select("a").attr("href") - chapter.chapter_number = parseChapterNumber(rawName) - chapter.name = rawName - chapter.date_upload = parseChapterDate(element.select("span.date").text().trim()) - - return chapter - } - - protected fun parseChapterNumber(name: String): Float { - try { - if (name.contains("[단편]")) return 1f - // `특별` means `Special`, so It can be buggy. so pad `편`(Chapter) to prevent false return - if (name.contains("번외") || name.contains("특별편")) return -2f - val regex = Regex("([0-9]+)(?:[-.]([0-9]+))?(?:화)") - val (ch_primal, ch_second) = regex.find(name)!!.destructured - return (ch_primal + if (ch_second.isBlank()) "" else ".$ch_second").toFloatOrNull() ?: -1f - } catch (e: Exception) { - e.printStackTrace() - return -1f - } - } - - @SuppressLint("SimpleDateFormat") - private fun parseChapterDate(date: String): Long { - return if (date.contains(":")) { - Calendar.getInstance().timeInMillis - } else { - return try { - SimpleDateFormat("yy.MM.dd", Locale.KOREA).parse(date)?.time ?: 0L - } catch (e: Exception) { - e.printStackTrace() - 0 - } - } - } - - override fun mangaDetailsParse(document: Document): SManga { - val element = document.select(".comicinfo") - val titleElement = element.select(".detail > h2") - - val manga = SManga.create() - manga.title = titleElement.first().ownText().trim() - manga.author = titleElement.select("span").text().trim() - manga.description = document.select("div.detail p").text().trim() - manga.thumbnail_url = element.select(".thumb > a > img").last().attr("src") - return manga - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - try { - document.select(".wt_viewer img") - .map { - it.attr("src") - } - .forEach { - pages.add(Page(pages.size, "", it)) - } - } catch (e: Exception) { - e.printStackTrace() - } - - return pages - } - - // We are able to get the image URL directly from the page list - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!") - - override fun getFilterList() = FilterList() -} - -abstract class NaverComicChallengeBase(mType: String) : NaverComicBase(mType) { - override fun popularMangaSelector() = ".weekchallengeBox tbody td:not([class])" - override fun popularMangaNextPageSelector(): String? = ".paginate .page_wrap a.next" - override fun popularMangaFromElement(element: Element): SManga { - val thumb = element.select("a img").first().attr("src") - val title = element.select(".challengeTitle a").first() - - val manga = SManga.create() - manga.url = title.attr("href").substringBefore("&week") - manga.title = title.text().trim() - manga.thumbnail_url = thumb - return manga - } - - override fun latestUpdatesSelector() = popularMangaSelector() - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) -} diff --git a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicFactory.kt b/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicFactory.kt deleted file mode 100644 index 0daff2d19..000000000 --- a/src/ko/navercomic/src/eu/kanade/tachiyomi/extension/ko/navercomic/NaverComicFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.navercomic - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class NaverComicFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - NaverWebtoon(), - NaverBestChallenge(), - NaverChallenge() - ) -} diff --git a/src/ko/newtoki/AndroidManifest.xml b/src/ko/newtoki/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ko/newtoki/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ko/newtoki/build.gradle b/src/ko/newtoki/build.gradle deleted file mode 100644 index f0d1bfa29..000000000 --- a/src/ko/newtoki/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'NewToki / ManaToki' - pkgNameSuffix = 'ko.newtoki' - extClass = '.NewTokiFactory' - extVersionCode = 21 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ko/newtoki/res/mipmap-hdpi/ic_launcher.png b/src/ko/newtoki/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8fc02e34c..000000000 Binary files a/src/ko/newtoki/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/newtoki/res/mipmap-mdpi/ic_launcher.png b/src/ko/newtoki/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 70406da2d..000000000 Binary files a/src/ko/newtoki/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/newtoki/res/mipmap-xhdpi/ic_launcher.png b/src/ko/newtoki/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 14dc08f35..000000000 Binary files a/src/ko/newtoki/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/newtoki/res/mipmap-xxhdpi/ic_launcher.png b/src/ko/newtoki/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7b6203578..000000000 Binary files a/src/ko/newtoki/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/newtoki/res/mipmap-xxxhdpi/ic_launcher.png b/src/ko/newtoki/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 912d2ab8b..000000000 Binary files a/src/ko/newtoki/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/newtoki/res/web_hi_res_512.png b/src/ko/newtoki/res/web_hi_res_512.png deleted file mode 100644 index 8c2b8e07a..000000000 Binary files a/src/ko/newtoki/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/ManaToki.kt b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/ManaToki.kt deleted file mode 100644 index 1ddc4c92f..000000000 --- a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/ManaToki.kt +++ /dev/null @@ -1,256 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.newtoki - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CacheControl -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element -import rx.Observable -import java.util.concurrent.TimeUnit - -/* - * ManaToki is too big to support in a Factory File., So split into separate file. - */ - -class ManaToki(domainNumber: Long) : NewToki("ManaToki", "https://manatoki$domainNumber.net", "comic") { - // / ! DO NOT CHANGE THIS ! Only the site name changed from newtoki. - override val id by lazy { generateSourceId("NewToki", lang, versionId) } - override val supportsLatest by lazy { getExperimentLatest() } - - override fun latestUpdatesSelector() = ".media.post-list" - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/update?hid=update&page=$page") - override fun latestUpdatesNextPageSelector() = "nav.pg_wrap > .pg > strong" - override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { - // if this is true, Handle Only 10 mangas with accurate Details per page. (Real Latest Page has 70 mangas.) - // Else, Parse from Latest page. which is incomplete. - val isParseWithDetail = getLatestWithDetail() - val reqPage = if (isParseWithDetail) ((page - 1) / 7 + 1) else page - return rateLimitedClient.newCall(latestUpdatesRequest(reqPage)) - .asObservableSuccess() - .map { response -> - if (isParseWithDetail) latestUpdatesParseWithDetailPage(response, page) - else latestUpdatesParseWithLatestPage(response) - } - } - - private fun latestUpdatesParseWithDetailPage(response: Response, page: Int): MangasPage { - val document = response.asJsoup() - - // given cache time to prevent repeated lots of request in latest. - val cacheControl = CacheControl.Builder().maxAge(28, TimeUnit.DAYS).maxStale(28, TimeUnit.DAYS).build() - - val rm = 70 * ((page - 1) / 7) - val min = (page - 1) * 10 - rm - val max = page * 10 - rm - val elements = document.select("${latestUpdatesSelector()} p > a").slice(min until max) - val mangas = elements.map { element -> - val url = element.attr("abs:href") - val manga = mangaDetailsParse(rateLimitedClient.newCall(GET(url, cache = cacheControl)).execute()) - manga.url = getUrlPath(url) - manga - } - - val hasNextPage = try { - !document.select(popularMangaNextPageSelector()).text().contains("10") - } catch (_: Exception) { - false - } - - return MangasPage(mangas, hasNextPage) - } - - private fun latestUpdatesParseWithLatestPage(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(latestUpdatesSelector()).map { element -> - latestUpdatesElementParse(element) - } - - val hasNextPage = try { - !document.select(popularMangaNextPageSelector()).text().contains("10") - } catch (_: Exception) { - false - } - - return MangasPage(mangas, hasNextPage) - } - - private fun latestUpdatesElementParse(element: Element): SManga { - val linkElement = element.select("a.btn-primary") - val rawTitle = element.select(".post-subject > a").first().ownText().trim() - - // TODO: Make Clear Regex. - val chapterRegex = Regex("""((?:\s+)(?:(?:(?:[0-9]+권)?(?:[0-9]+부)?(?:[0-9]*?시즌[0-9]*?)?)?(?:\s*)(?:(?:[0-9]+)(?:[-.](?:[0-9]+))?)?(?:\s*[~,]\s*)?(?:[0-9]+)(?:[-.](?:[0-9]+))?)(?:화))""") - val title = rawTitle.trim().replace(chapterRegex, "") - // val regexSpecialChapter = Regex("(부록|단편|외전|.+편)") - // val lastTitleWord = excludeChapterTitle.split(" ").last() - // val title = excludeChapterTitle.replace(lastTitleWord, lastTitleWord.replace(regexSpecialChapter, "")) - - val manga = SManga.create() - manga.url = getUrlPath(linkElement.attr("href")) - manga.title = title - manga.thumbnail_url = element.select(".img-item > img").attr("src") - manga.initialized = false - return manga - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/comic" + (if (page > 1) "/p$page" else ""))!!.newBuilder() - - val genres = mutableListOf<String>() - - filters.forEach { filter -> - when (filter) { - is SearchPublishTypeList -> { - if (filter.state > 0) { - url.addQueryParameter("publish", filter.values[filter.state]) - } - } - - is SearchJaumTypeList -> { - if (filter.state > 0) { - url.addQueryParameter("jaum", filter.values[filter.state]) - } - } - - is SearchGenreTypeList -> { - filter.state.forEach { - if (it.state) { - genres.add(it.id) - } - } - } - - is SearchSortTypeList -> { - url.addQueryParameter("sst", listOf("wr_datetime", "wr_hit", "wr_good", "as_update")[filter.state]) - } - - is SearchOrderTypeList -> { - url.addQueryParameter("sod", listOf("desc", "asc")[filter.state]) - } - } - } - - if (query.isNotBlank()) { - url.addQueryParameter("stx", query) - - // Remove some filter QueryParams that not working with query - url.setQueryParameter("publish", null) - url.setQueryParameter("jaum", null) - - return GET(url.toString()) - } - - url.addQueryParameter("tag", genres.joinToString(",")) - return GET(url.toString()) - } - - private class SearchCheckBox(name: String, val id: String = name) : Filter.CheckBox(name) - - // [...document.querySelectorAll("form.form td")[3].querySelectorAll("span.btn")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') - private class SearchPublishTypeList : Filter.Select<String>( - "Publish", - arrayOf( - "전체", - "주간", - "격주", - "월간", - "단편", - "단행본", - "완결" - ) - ) - - // [...document.querySelectorAll("form.form td")[4].querySelectorAll("span.btn")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') - private class SearchJaumTypeList : Filter.Select<String>( - "Jaum", - arrayOf( - "전체", - "ㄱ", - "ㄴ", - "ㄷ", - "ㄹ", - "ㅁ", - "ㅂ", - "ㅅ", - "ㅇ", - "ㅈ", - "ㅊ", - "ㅋ", - "ㅌ", - "ㅍ", - "ㅎ", - "0-9", - "a-z" - ) - ) - - // [...document.querySelectorAll("form.form td")[6].querySelectorAll("span.btn")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') - private class SearchGenreTypeList : Filter.Group<SearchCheckBox>( - "Genres", - arrayOf( - "전체", - "17", - "BL", - "SF", - "TS", - "개그", - "게임", - "도박", - "드라마", - "라노벨", - "러브코미디", - "먹방", - "백합", - "붕탁", - "순정", - "스릴러", - "스포츠", - "시대", - "애니화", - "액션", - "음악", - "이세계", - "일상", - "전생", - "추리", - "판타지", - "학원", - "호러" - ).map { SearchCheckBox(it) } - ) - - private class SearchSortTypeList : Filter.Select<String>( - "Sort", - arrayOf( - "기본(날짜순)", - "인기순", - "추천순", - "업데이트순" - ) - ) - - private class SearchOrderTypeList : Filter.Select<String>( - "Order", - arrayOf( - "Descending", - "Ascending" - ) - ) - - override fun getFilterList() = FilterList( - Filter.Header("Some filters can't use with query"), - SearchPublishTypeList(), - SearchJaumTypeList(), - SearchSortTypeList(), - SearchOrderTypeList(), - SearchGenreTypeList() - ) -} diff --git a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt deleted file mode 100644 index 4dcec1fd9..000000000 --- a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt +++ /dev/null @@ -1,500 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.newtoki - -import android.annotation.SuppressLint -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.widget.Toast -import eu.kanade.tachiyomi.extensions.BuildConfig -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.net.URI -import java.net.URISyntaxException -import java.text.SimpleDateFormat -import java.util.Calendar - -/** - * NewToki Source - **/ -open class NewToki(override val name: String, private val defaultBaseUrl: String, private val boardName: String) : ConfigurableSource, ParsedHttpSource() { - override val baseUrl by lazy { getPrefBaseUrl() } - override val lang: String = "ko" - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient - protected val rateLimitedClient: OkHttpClient by lazy { - network.cloudflareClient.newBuilder() - .addNetworkInterceptor(RateLimitInterceptor(1, getRateLimitPeriod())) - .build() - } - - override fun popularMangaSelector() = "div#webtoon-list > ul > li" - - override fun popularMangaFromElement(element: Element): SManga { - val linkElement = element.getElementsByTag("a").first() - - val manga = SManga.create() - manga.setUrlWithoutDomain(linkElement.attr("href").substringBefore("?")) - manga.title = element.select("span.title").first().ownText() - manga.thumbnail_url = linkElement.getElementsByTag("img").attr("src") - return manga - } - - override fun popularMangaNextPageSelector() = "ul.pagination > li:last-child:not(.disabled)" - - // Do not add page parameter if page is 1 to prevent tracking. - override fun popularMangaRequest(page: Int) = GET("$baseUrl/$boardName" + if (page > 1) "/p$page" else "") - - override fun searchMangaSelector() = popularMangaSelector() - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/$boardName" + (if (page > 1) "/p$page" else "") + "?stx=$query") - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - val urlPath = "/$boardName/$realQuery" - rateLimitedClient.newCall(GET("$baseUrl$urlPath")) - .asObservableSuccess() - .map { response -> - // the id is matches any of 'post' from their CMS board. - // Includes Manga Details Page, Chapters, Comments, and etcs... - actualMangaParseById(urlPath, response) - } - } else super.fetchSearchManga(page, query, filters) - } - - private fun actualMangaParseById(urlPath: String, response: Response): MangasPage { - val document = response.asJsoup() - - // Only exists on detail page. - val firstChapterButton = document.select("tr > th > button.btn-blue").first() - // only exists on chapter with proper manga detail page. - val fullListButton = document.select(".comic-navbar .toon-nav a").last() - - val list: List<SManga> = when { - firstChapterButton?.text()?.contains("첫회보기") == true -> { // Check this page is detail page - val details = mangaDetailsParse(document) - details.url = urlPath - listOf(details) - } - fullListButton?.text()?.contains("전체목록") == true -> { // Check this page is chapter page - val url = fullListButton.attr("abs:href") - val details = mangaDetailsParse(rateLimitedClient.newCall(GET(url)).execute()) - details.url = getUrlPath(url) - listOf(details) - } - else -> emptyList() - } - - return MangasPage(list, false) - } - - override fun mangaDetailsParse(document: Document): SManga { - val info = document.select("div.view-title > .view-content").first() - val title = document.select("div.view-content > span > b").text() - val thumbnail = document.select("div.row div.view-img > img").attr("src") - val descriptionElement = info.select("div.row div.view-content:not([style])") - val description = descriptionElement.map { - it.text().trim() - } - - val manga = SManga.create() - manga.title = title - manga.description = description.joinToString("\n") - manga.thumbnail_url = thumbnail - descriptionElement.forEach { - val text = it.text() - when { - "작가" in text -> manga.author = it.getElementsByTag("a").text() - "분류" in text -> { - val genres = mutableListOf<String>() - it.getElementsByTag("a").forEach { item -> - genres.add(item.text()) - } - manga.genre = genres.joinToString(", ") - } - "발행구분" in text -> manga.status = parseStatus(it.getElementsByTag("a").text()) - } - } - return manga - } - - private fun parseStatus(status: String) = when (status.trim()) { - "주간", "격주", "월간", "격월/비정기", "단행본" -> SManga.ONGOING - "단편", "완결" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.serial-list > ul.list-body > li.list-item" - - override fun chapterFromElement(element: Element): SChapter { - val linkElement = element.select(".wr-subject > a.item-subject").last() - val rawName = linkElement.ownText().trim() - - val chapter = SChapter.create() - chapter.url = getUrlWithoutDomainWithFallback(linkElement.attr("href")) - chapter.chapter_number = parseChapterNumber(rawName) - chapter.name = rawName - chapter.date_upload = parseChapterDate(element.select(".wr-date").last().text().trim()) - return chapter - } - - private fun parseChapterNumber(name: String): Float { - try { - if (name.contains("[단편]")) return 1f - // `특별` means `Special`, so It can be buggy. so pad `편`(Chapter) to prevent false return - if (name.contains("번외") || name.contains("특별편")) return -2f - val regex = Regex("([0-9]+)(?:[-.]([0-9]+))?(?:화)") - val (ch_primal, ch_second) = regex.find(name)!!.destructured - return (ch_primal + if (ch_second.isBlank()) "" else ".$ch_second").toFloatOrNull() - ?: -1f - } catch (e: Exception) { - e.printStackTrace() - return -1f - } - } - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return rateLimitedClient.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return rateLimitedClient.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response) - } - } - - @SuppressLint("SimpleDateFormat") - private fun parseChapterDate(date: String): Long { - return try { - if (date.contains(":")) { - val calendar = Calendar.getInstance() - val splitDate = date.split(":") - - val hours = splitDate.first().toInt() - val minutes = splitDate.last().toInt() - - val calendarHours = calendar.get(Calendar.HOUR) - val calendarMinutes = calendar.get(Calendar.MINUTE) - - if (calendarHours >= hours && calendarMinutes > minutes) { - calendar.add(Calendar.DATE, -1) - } - - calendar.timeInMillis - } else { - SimpleDateFormat("yyyy.MM.dd").parse(date)?.time ?: 0 - } - } catch (e: Exception) { - e.printStackTrace() - 0 - } - } - - private val htmlDataRegex = Regex("""html_data\+='([^']+)'""") - - override fun pageListParse(document: Document): List<Page> { - val script = document.select("script:containsData(html_data)").firstOrNull()?.data() - ?: throw Exception("data script not found") - val loadScript = document.select("script:containsData(data_attribute)").firstOrNull()?.data() - ?: throw Exception("load script not found") - val dataAttr = "abs:data-" + loadScript.substringAfter("data_attribute: '").substringBefore("',") - - return htmlDataRegex.findAll(script).map { it.groupValues[1] } - .asIterable() - .flatMap { it.split(".") } - .joinToString("") { it.toIntOrNull(16)?.toChar()?.toString() ?: "" } - .let { Jsoup.parse(it) } - .select("img[src=/img/loading-image.gif], .view-img > img[itemprop]") - .mapIndexed { i, img -> Page(i, "", if (img.hasAttr(dataAttr)) img.attr(dataAttr) else img.attr("abs:content")) } - } - - override fun latestUpdatesSelector() = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - override fun latestUpdatesRequest(page: Int) = popularMangaRequest(page) - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // We are able to get the image URL directly from the page list - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!") - - override fun getFilterList() = FilterList() - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply { - key = BASE_URL_PREF_TITLE - title = BASE_URL_PREF_TITLE - summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(defaultBaseUrl) - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "Default: $defaultBaseUrl" - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val latestExperimentPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = EXPERIMENTAL_LATEST_PREF_TITLE - title = EXPERIMENTAL_LATEST_PREF_TITLE - summary = EXPERIMENTAL_LATEST_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_PREF, newValue as Boolean).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val latestWithDetailPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_TITLE - title = EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_TITLE - summary = EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_WITH_DETAIL_PREF, newValue as Boolean).commit() - // Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val rateLimitPeriodPref = androidx.preference.EditTextPreference(screen.context).apply { - key = RATE_LIMIT_PERIOD_PREF_TITLE - title = RATE_LIMIT_PERIOD_PREF_TITLE - summary = RATE_LIMIT_PERIOD_PREF_SUMMARY - this.setDefaultValue(defaultRateLimitPeriod.toString()) - dialogTitle = RATE_LIMIT_PERIOD_PREF_TITLE - dialogMessage = "Min 1 to Max 9, Invalid value will treat as $defaultRateLimitPeriod. Only Integer.\nDefault: $defaultRateLimitPeriod" - - setOnPreferenceChangeListener { _, newValue -> - try { - // Make sure to validate the value. - val p = (newValue as String).toLongOrNull(10) - var value = p ?: defaultRateLimitPeriod - if (p == null || value !in 1..9) { - Toast.makeText(screen.context, RATE_LIMIT_PERIOD_PREF_WARNING_INVALID_VALUE, Toast.LENGTH_LONG).show() - value = defaultRateLimitPeriod - } - val res = preferences.edit().putLong(RATE_LIMIT_PERIOD_PREF, value).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - if (name == "ManaToki") { - screen.addPreference(latestExperimentPref) - screen.addPreference(latestWithDetailPref) - } - screen.addPreference(rateLimitPeriodPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val baseUrlPref = EditTextPreference(screen.context).apply { - key = BASE_URL_PREF_TITLE - title = BASE_URL_PREF_TITLE - summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(defaultBaseUrl) - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "Default: $defaultBaseUrl" - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val latestExperimentPref = CheckBoxPreference(screen.context).apply { - key = EXPERIMENTAL_LATEST_PREF_TITLE - title = EXPERIMENTAL_LATEST_PREF_TITLE - summary = EXPERIMENTAL_LATEST_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_PREF, newValue as Boolean).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val latestWithDetailPref = CheckBoxPreference(screen.context).apply { - key = EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_TITLE - title = EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_TITLE - summary = EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putBoolean(EXPERIMENTAL_LATEST_WITH_DETAIL_PREF, newValue as Boolean).commit() - // Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val rateLimitPeriodPref = EditTextPreference(screen.context).apply { - key = RATE_LIMIT_PERIOD_PREF_TITLE - title = RATE_LIMIT_PERIOD_PREF_TITLE - summary = RATE_LIMIT_PERIOD_PREF_SUMMARY - this.setDefaultValue(defaultRateLimitPeriod.toString()) - dialogTitle = RATE_LIMIT_PERIOD_PREF_TITLE - dialogMessage = "Min 1 to Max 9, Invalid value will treat as $defaultRateLimitPeriod. Only Integer.\nDefault: $defaultRateLimitPeriod" - - setOnPreferenceChangeListener { _, newValue -> - try { - // Make sure to validate the value. - val p = (newValue as String).toLongOrNull(10) - var value = p ?: defaultRateLimitPeriod - if (p == null || value !in 1..9) { - Toast.makeText(screen.context, RATE_LIMIT_PERIOD_PREF_WARNING_INVALID_VALUE, Toast.LENGTH_LONG).show() - value = defaultRateLimitPeriod - } - val res = preferences.edit().putLong(RATE_LIMIT_PERIOD_PREF, value).commit() - Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - if (name == "ManaToki") { - screen.addPreference(latestExperimentPref) - screen.addPreference(latestWithDetailPref) - } - screen.addPreference(rateLimitPeriodPref) - } - - protected fun getUrlPath(orig: String): String { - return try { - URI(orig).path - } catch (e: URISyntaxException) { - orig - } - } - - // This is just replicate of original method but with fallback. - protected fun getUrlWithoutDomainWithFallback(orig: String): String { - return try { - val uri = URI(orig) - var out = uri.path - if (uri.query != null) { - out += "?" + uri.query - } - if (uri.fragment != null) { - out += "#" + uri.fragment - } - out - } catch (e: URISyntaxException) { - // fallback method. may not work. - orig.substringAfter(baseUrl) - } - } - - private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! - protected fun getExperimentLatest(): Boolean = preferences.getBoolean(EXPERIMENTAL_LATEST_PREF, false) - protected fun getLatestWithDetail(): Boolean = preferences.getBoolean(EXPERIMENTAL_LATEST_WITH_DETAIL_PREF, false) - private fun getRateLimitPeriod(): Long = try { // Check again as preference is bit weirdly buggy. - val v = preferences.getLong(RATE_LIMIT_PERIOD_PREF, defaultRateLimitPeriod) - if (v in 1..9) v else defaultRateLimitPeriod - } catch (e: Exception) { - defaultRateLimitPeriod - } - - companion object { - private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting." - - private const val BASE_URL_PREF_TITLE = "Override BaseUrl" - private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_NAME}" - private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Update extension will erase this setting." - - // Setting: Experimental Latest Fetcher - private const val EXPERIMENTAL_LATEST_PREF_TITLE = "Enable Latest (Experimental)" - private const val EXPERIMENTAL_LATEST_PREF = "fetchLatestExperiment" - private const val EXPERIMENTAL_LATEST_PREF_SUMMARY = "Fetch Latest Manga using Latest Chapters. May has duplicates and May DB corruption on certain Tachiyomi builds" - - // Setting: Experimental Latest Fetcher With Full Details (Optional) - private const val EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_TITLE = "Fetch Latest with detail (Optional)" - private const val EXPERIMENTAL_LATEST_WITH_DETAIL_PREF = "fetchLatestWithDetail" - private const val EXPERIMENTAL_LATEST_WITH_DETAIL_PREF_SUMMARY = - "Parse latest manga details with detail pages. This will reduce DB corruption on certain Tachiyomi builds.\n" + - "But makes chance of IP Ban, Also makes bunch of requests, For prevent IP ban, rate limit is set. so may slow,\n" + - "Still, It's experiment. Required to enable `Enable Latest (Experimental).`" - - // Settings: Rate Limit Period - private const val defaultRateLimitPeriod: Long = 2L - private const val RATE_LIMIT_PERIOD_PREF_TITLE = "Rate Limit Request Period Seconds" - private const val RATE_LIMIT_PERIOD_PREF = "rateLimitPeriod" - private const val RATE_LIMIT_PERIOD_PREF_SUMMARY = - "As Source is using Temporary IP ban system to who makes bunch of request, Some of requests are rate limited\n" + - "If you want to reduce limit, Use this option.\n" + - "Invalid value will treat as default $defaultRateLimitPeriod seconds.\n" + - "(Valid: Min 1 to Max 9)" - private const val RATE_LIMIT_PERIOD_PREF_WARNING_INVALID_VALUE = "Invalid value detected. Treating as $defaultRateLimitPeriod..." - - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt deleted file mode 100644 index dd11b9522..000000000 --- a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt +++ /dev/null @@ -1,179 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.newtoki - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import okhttp3.HttpUrl -import okhttp3.Request -import java.security.MessageDigest -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -/** - * Source changes domain names every few days (e.g. newtoki31.net to newtoki32.net) - * The domain name was newtoki32 on 2019-11-14, this attempts to match the rate at which the domain changes - * - * Since 2020-09-20, They changed manga side to Manatoki. - * It was merged after shutdown of ManaMoa. - * This is by the head of Manamoa, as they decided to move to Newtoki. - */ -private val domainNumber = 32 + ((Date().time - SimpleDateFormat("yyyy-MM-dd", Locale.US).parse("2019-11-14")!!.time) / 595000000) - -class NewTokiFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - ManaToki(domainNumber), - NewTokiWebtoon() - ) -} - -class NewTokiWebtoon : NewToki("NewToki", "https://newtoki$domainNumber.com", "webtoon") { - // / ! DO NOT CHANGE THIS ! Prevent to treating as a new site - override val id by lazy { generateSourceId("NewToki (Webtoon)", lang, versionId) } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/webtoon" + (if (page > 1) "/p$page" else ""))!!.newBuilder() - filters.forEach { filter -> - when (filter) { - is SearchTargetTypeList -> { - if (filter.state > 0) { - url.addQueryParameter("toon", filter.values[filter.state]) - } - } - - is SearchSortTypeList -> { - url.addQueryParameter("sst", listOf("as_update", "wr_hit", "wr_good")[filter.state]) - } - - is SearchOrderTypeList -> { - url.addQueryParameter("sod", listOf("desc", "asc")[filter.state]) - } - } - } - - // Incompatible with Other Search Parameter - if (!query.isBlank()) { - url.addQueryParameter("stx", query) - } else { - filters.forEach { filter -> - when (filter) { - is SearchYoilTypeList -> { - if (filter.state > 0) { - url.addQueryParameter("yoil", filter.values[filter.state]) - } - } - - is SearchJaumTypeList -> { - if (filter.state > 0) { - url.addQueryParameter("jaum", filter.values[filter.state]) - } - } - - is SearchGenreTypeList -> { - if (filter.state > 0) { - url.addQueryParameter("tag", filter.values[filter.state]) - } - } - } - } - } - - return GET(url.toString()) - } - - private class SearchTargetTypeList : Filter.Select<String>("Type", arrayOf("전체", "일반웹툰", "성인웹툰", "BL/GL", "완결웹툰")) - - // [...document.querySelectorAll("form.form td")[1].querySelectorAll("a")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') - private class SearchYoilTypeList : Filter.Select<String>( - "Day of the Week", - arrayOf( - "전체", - "월", - "화", - "수", - "목", - "금", - "토", - "일", - "열흘" - ) - ) - - // [...document.querySelectorAll("form.form td")[2].querySelectorAll("a")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') - private class SearchJaumTypeList : Filter.Select<String>( - "Jaum", - arrayOf( - "전체", - "ㄱ", - "ㄴ", - "ㄷ", - "ㄹ", - "ㅁ", - "ㅂ", - "ㅅ", - "ㅇ", - "ㅈ", - "ㅊ", - "ㅋ", - "ㅌ", - "ㅍ", - "ㅎ", - "a-z", - "0-9" - ) - ) - // [...document.querySelectorAll("form.form td")[3].querySelectorAll("a")].map((el, i) => `"${el.innerText.trim()}"`).join(',\n') - private class SearchGenreTypeList : Filter.Select<String>( - "Genre", - arrayOf( - "전체", - "판타지", - "액션", - "개그", - "미스터리", - "로맨스", - "드라마", - "무협", - "스포츠", - "일상", - "학원", - "성인" - ) - ) - - private class SearchSortTypeList : Filter.Select<String>( - "Sort", - arrayOf( - "기본(업데이트순)", - "인기순", - "추천순", - ) - ) - - private class SearchOrderTypeList : Filter.Select<String>( - "Order", - arrayOf( - "Descending", - "Ascending" - ) - ) - - override fun getFilterList() = FilterList( - SearchTargetTypeList(), - SearchSortTypeList(), - SearchOrderTypeList(), - Filter.Separator(), - Filter.Header("Under 3 Filters can't use with query"), - SearchYoilTypeList(), - SearchJaumTypeList(), - SearchGenreTypeList() - ) -} - -fun generateSourceId(name: String, lang: String, versionId: Int): Long { - val key = "${name.toLowerCase()}/$lang/$versionId" - val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) - return (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE -} diff --git a/src/ko/toonkor/AndroidManifest.xml b/src/ko/toonkor/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ko/toonkor/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ko/toonkor/build.gradle b/src/ko/toonkor/build.gradle deleted file mode 100644 index 8579580ed..000000000 --- a/src/ko/toonkor/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Toonkor' - pkgNameSuffix = 'ko.toonkor' - extClass = '.Toonkor' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ko/toonkor/res/mipmap-hdpi/ic_launcher.png b/src/ko/toonkor/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4e2965d1e..000000000 Binary files a/src/ko/toonkor/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/toonkor/res/mipmap-mdpi/ic_launcher.png b/src/ko/toonkor/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 87dd4aae9..000000000 Binary files a/src/ko/toonkor/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/toonkor/res/mipmap-xhdpi/ic_launcher.png b/src/ko/toonkor/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8c4f88868..000000000 Binary files a/src/ko/toonkor/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/toonkor/res/mipmap-xxhdpi/ic_launcher.png b/src/ko/toonkor/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index af9983bf1..000000000 Binary files a/src/ko/toonkor/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/toonkor/res/mipmap-xxxhdpi/ic_launcher.png b/src/ko/toonkor/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5d8b0cb5c..000000000 Binary files a/src/ko/toonkor/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ko/toonkor/res/web_hi_res_512.png b/src/ko/toonkor/res/web_hi_res_512.png deleted file mode 100644 index 2a880cb06..000000000 Binary files a/src/ko/toonkor/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ko/toonkor/src/eu/kanade/tachiyomi/extension/ko/toonkor/Toonkor.kt b/src/ko/toonkor/src/eu/kanade/tachiyomi/extension/ko/toonkor/Toonkor.kt deleted file mode 100644 index bfc020252..000000000 --- a/src/ko/toonkor/src/eu/kanade/tachiyomi/extension/ko/toonkor/Toonkor.kt +++ /dev/null @@ -1,248 +0,0 @@ -package eu.kanade.tachiyomi.extension.ko.toonkor - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.util.Base64 -import eu.kanade.tachiyomi.extension.BuildConfig -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.lang.Exception -import java.nio.charset.Charset -import java.text.SimpleDateFormat -import java.util.Locale - -class Toonkor : ConfigurableSource, ParsedHttpSource() { - - override val name = "Toonkor" - - private val defaultBaseUrl = "https://tkor.cx" - - private val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_NAME}" - - override val baseUrl by lazy { getPrefBaseUrl() } - - override val lang = "ko" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - private val webtoonsRequestPath = "/%EC%9B%B9%ED%88%B0" - - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl + webtoonsRequestPath, headers) - } - - override fun popularMangaSelector() = "div.section-item-inner" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.section-item-title a").let { - title = it.select("h3").text() - url = it.attr("href") - } - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - - private val latestRequestModifier = "?fil=%EC%B5%9C%EC%8B%A0" - - override fun latestUpdatesRequest(page: Int): Request { - return GET(baseUrl + webtoonsRequestPath + latestRequestModifier, headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val filterList = if (filters.isEmpty()) getFilterList() else filters - - // Webtoons, Manga, or Hentai - val type = filterList.findUriPartFilter<TypeFilter>() - // Popular, Latest, or Completed - val sort = filterList.findUriPartFilter<SortFilter>() - - // Hentai doesn't have a "completed" sort, ignore it if it's selected (equivalent to returning popular) - val requestPath = when { - query.isNotBlank() -> "/bbs/search.php?sfl=wr_subject%7C%7Cwr_content&stx=$query" - type.isSelection("Hentai") && sort.isSelection("Completed") -> type.toUriPart() - else -> type.toUriPart() + sort.toUriPart() - } - - return GET(baseUrl + requestPath, headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - with(document.select("table.bt_view1")) { - title = select("td.bt_title").text() - author = select("td.bt_label span.bt_data").text() - description = select("td.bt_over").text() - thumbnail_url = select("td.bt_thumb img").firstOrNull()?.attr("abs:src") - } - } - } - - // Chapters - - override fun chapterListSelector() = "table.web_list tr:has(td.content__title)" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("td.content__title").let { - url = it.attr("data-role") - name = it.text() - } - date_upload = element.select("td.episode__index").text().toDate() - } - } - - private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) } - - private fun String.toDate(): Long { - return dateFormat.parse(this)?.time ?: 0 - } - - // Pages - - private val pageListRegex = Regex("""src="([^"]*)"""") - - override fun pageListParse(document: Document): List<Page> { - val encoded = document.select("script:containsData(toon_img)").firstOrNull()?.data() - ?.substringAfter("'")?.substringBefore("'") ?: throw Exception("toon_img script not found") - - val decoded = Base64.decode(encoded, Base64.DEFAULT).toString(Charset.defaultCharset()) - - return pageListRegex.findAll(decoded).toList().mapIndexed { i, matchResult -> - Page(i, "", matchResult.destructured.component1().let { if (it.startsWith("http")) it else baseUrl + it }) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList(): FilterList { - return FilterList( - Filter.Header("Note: can't combine with text search!"), - Filter.Separator(), - TypeFilter(getTypeList()), - SortFilter(getSortList()) - ) - } - - private class TypeFilter(vals: Array<Pair<String, String>>) : UriPartFilter("Type", vals) - private class SortFilter(vals: Array<Pair<String, String>>) : UriPartFilter("Sort", vals) - - private fun getTypeList() = arrayOf( - Pair("Webtoons", webtoonsRequestPath), - Pair("Manga", "/%EB%8B%A8%ED%96%89%EB%B3%B8"), - Pair("Hentai", "/%EB%A7%9D%EA%B0%80") - ) - - private fun getSortList() = arrayOf( - Pair("Popular", ""), - Pair("Latest", latestRequestModifier), - Pair("Completed", "/%EC%99%84%EA%B2%B0") - ) - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun isSelection(name: String): Boolean = name == vals[state].first - fun toUriPart() = vals[state].second - } - - private inline fun <reified T> FilterList.findUriPartFilter(): UriPartFilter = this.find { it is T } as UriPartFilter - - // Preferences - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply { - key = BASE_URL_PREF_TITLE - title = BASE_URL_PREF_TITLE - summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(defaultBaseUrl) - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "Default: $defaultBaseUrl" - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val baseUrlPref = EditTextPreference(screen.context).apply { - key = BASE_URL_PREF_TITLE - title = BASE_URL_PREF_TITLE - summary = BASE_URL_PREF_SUMMARY - this.setDefaultValue(defaultBaseUrl) - dialogTitle = BASE_URL_PREF_TITLE - dialogMessage = "Default: $defaultBaseUrl" - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(BASE_URL_PREF, newValue as String).commit() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(baseUrlPref) - } - - private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!! - - companion object { - private const val BASE_URL_PREF_TITLE = "Override BaseUrl" - private const val BASE_URL_PREF_SUMMARY = "Override default domain with a different one" - } -} diff --git a/src/pt/bruttal/AndroidManifest.xml b/src/pt/bruttal/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/bruttal/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/bruttal/build.gradle b/src/pt/bruttal/build.gradle deleted file mode 100644 index ab3eacca6..000000000 --- a/src/pt/bruttal/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Bruttal' - pkgNameSuffix = 'pt.bruttal' - extClass = '.Bruttal' - extVersionCode = 2 - libVersion = '1.2' -} - - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/bruttal/res/mipmap-hdpi/ic_launcher.png b/src/pt/bruttal/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7acf1167f..000000000 Binary files a/src/pt/bruttal/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bruttal/res/mipmap-mdpi/ic_launcher.png b/src/pt/bruttal/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c31c1d044..000000000 Binary files a/src/pt/bruttal/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bruttal/res/mipmap-xhdpi/ic_launcher.png b/src/pt/bruttal/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ca5a5cabe..000000000 Binary files a/src/pt/bruttal/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bruttal/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/bruttal/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 662be6849..000000000 Binary files a/src/pt/bruttal/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bruttal/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/bruttal/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a6f89087e..000000000 Binary files a/src/pt/bruttal/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/bruttal/res/web_hi_res_512.png b/src/pt/bruttal/res/web_hi_res_512.png deleted file mode 100644 index 9f4f3e7cc..000000000 Binary files a/src/pt/bruttal/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/bruttal/src/eu/kanade/tachiyomi/extension/pt/bruttal/Bruttal.kt b/src/pt/bruttal/src/eu/kanade/tachiyomi/extension/pt/bruttal/Bruttal.kt deleted file mode 100644 index 00a1c7ccd..000000000 --- a/src/pt/bruttal/src/eu/kanade/tachiyomi/extension/pt/bruttal/Bruttal.kt +++ /dev/null @@ -1,195 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.bruttal - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import java.util.concurrent.TimeUnit - -class Bruttal : HttpSource() { - - override val name = "Bruttal" - - override val baseUrl = "https://originals.omelete.com.br" - - override val lang = "pt-BR" - - override val supportsLatest = false - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/bruttal/") - .add("User-Agent", USER_AGENT) - - override fun popularMangaRequest(page: Int): Request { - val newHeaders = headersBuilder() - .add("Accept", "application/json, text/plain, */*") - .build() - - return GET("$baseUrl/bruttal/data/home.json", newHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val json = response.asJson().obj - - val titles = json["list"].array.map { jsonEl -> - popularMangaFromObject(jsonEl.obj) - } - - return MangasPage(titles, false) - } - - private fun popularMangaFromObject(obj: JsonObject): SManga = SManga.create().apply { - title = obj["title"].string - thumbnail_url = "$baseUrl/bruttal/" + obj["image_mobile"].string.removePrefix("./") - url = "/bruttal" + obj["url"].string - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return super.fetchSearchManga(page, query, filters) - .map { mp -> - val filteredTitles = mp.mangas.filter { it.title.contains(query, true) } - MangasPage(filteredTitles, mp.hasNextPage) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(page) - - override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(mangaDetailsApiRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun mangaDetailsApiRequest(manga: SManga): Request { - val newHeaders = headersBuilder() - .add("Accept", "application/json, text/plain, */*") - .set("Referer", baseUrl + manga.url) - .build() - - return GET("$baseUrl/bruttal/data/comicbooks.json", newHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga { - val json = response.asJson().array - - val titleUrl = response.request().header("Referer")!!.substringAfter("/bruttal") - val titleObj = json.first { it.obj["url"].string == titleUrl }.obj - val soonText = titleObj["soon_text"].string - - return SManga.create().apply { - title = titleObj["title"].string - thumbnail_url = "$baseUrl/bruttal/" + titleObj["image_mobile"].string.removePrefix("./") - description = titleObj["synopsis"].string + - (if (soonText.isEmpty()) "" else "\n\n$soonText") - artist = titleObj["illustrator"].string - author = titleObj["author"].string - genre = titleObj["keywords"].string.replace("; ", ", ") - status = SManga.ONGOING - } - } - - // Chapters are available in the same url of the manga details. - override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val json = response.asJson().array - - val titleUrl = response.request().header("Referer")!!.substringAfter("/bruttal") - val title = json.first { it.obj["url"].string == titleUrl }.obj - - return title["seasons"].array - .flatMap { it.obj["chapters"].array } - .map { jsonEl -> chapterFromObject(jsonEl.obj) } - .reversed() - } - - private fun chapterFromObject(obj: JsonObject): SChapter = SChapter.create().apply { - name = obj["title"].string - chapter_number = obj["share_title"].string.removePrefix("Capítulo ").toFloatOrNull() ?: -1f - url = "/bruttal" + obj["url"].string - } - - override fun pageListRequest(chapter: SChapter): Request { - val newHeaders = headersBuilder() - .add("Accept", "application/json, text/plain, */*") - .set("Referer", baseUrl + chapter.url) - .build() - - return GET("$baseUrl/bruttal/data/comicbooks.json", newHeaders) - } - - override fun pageListParse(response: Response): List<Page> { - val json = response.asJson().array - - val chapterUrl = response.request().header("Referer")!! - val titleSlug = chapterUrl.substringAfter("bruttal/").substringBefore("/") - val season = chapterUrl.substringAfter("temporada-").substringBefore("/").toInt() - val chapter = chapterUrl.substringAfter("capitulo-") - - val titleObj = json.first { it.obj["url"].string == "/$titleSlug" }.obj - val seasonObj = titleObj["seasons"].array[season - 1].obj - val chapterObj = seasonObj["chapters"].array.first { - it.obj["alias"].string.substringAfter("-") == chapter - } - - return chapterObj["images"].array - .mapIndexed { i, jsonEl -> - val imageUrl = "$baseUrl/bruttal/" + jsonEl.obj["image"].string.removePrefix("./") - Page(i, chapterUrl, imageUrl) - } - } - - override fun fetchImageUrl(page: Page): Observable<String> { - return Observable.just(page.imageUrl!!) - } - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .add("Accept", "image/avif,image/webp,image/apng,image/*,*/*;q=0.8") - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - private fun Response.asJson(): JsonElement = JSON_PARSER.parse(body()!!.string()) - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private val JSON_PARSER by lazy { JsonParser() } - } -} diff --git a/src/pt/centraldemangas/AndroidManifest.xml b/src/pt/centraldemangas/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/centraldemangas/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/centraldemangas/build.gradle b/src/pt/centraldemangas/build.gradle deleted file mode 100644 index 5748efea0..000000000 --- a/src/pt/centraldemangas/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Central de Mangás' - pkgNameSuffix = 'pt.centraldemangas' - extClass = '.CentralDeMangas' - extVersionCode = 4 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/centraldemangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0c796f9f1..000000000 Binary files a/src/pt/centraldemangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/centraldemangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e523f21e8..000000000 Binary files a/src/pt/centraldemangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/centraldemangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1cf9880d9..000000000 Binary files a/src/pt/centraldemangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/centraldemangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 04f295d46..000000000 Binary files a/src/pt/centraldemangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/centraldemangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/centraldemangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3ae754011..000000000 Binary files a/src/pt/centraldemangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/centraldemangas/res/web_hi_res_512.png b/src/pt/centraldemangas/res/web_hi_res_512.png deleted file mode 100644 index 1c5928edb..000000000 Binary files a/src/pt/centraldemangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/centraldemangas/src/eu/kanade/tachiyomi/extension/pt/centraldemangas/CentralDeMangas.kt b/src/pt/centraldemangas/src/eu/kanade/tachiyomi/extension/pt/centraldemangas/CentralDeMangas.kt deleted file mode 100644 index 2221b90a9..000000000 --- a/src/pt/centraldemangas/src/eu/kanade/tachiyomi/extension/pt/centraldemangas/CentralDeMangas.kt +++ /dev/null @@ -1,209 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.centraldemangas - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonArray -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class CentralDeMangas : ParsedHttpSource() { - - // Hardcode the id because the language wasn't specific. - override val id: Long = 711589261250964163 - - override val name = "Central de Mangás" - - override val baseUrl = "http://centraldemangas.online" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .connectTimeout(3, TimeUnit.MINUTES) - .readTimeout(3, TimeUnit.MINUTES) - .writeTimeout(3, TimeUnit.MINUTES) - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaSelector(): String = "div.ui.eight.doubling.stackable.cards div.card" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("div.content a").last().text() - url = element.select("div.content a").last().attr("href") - thumbnail_url = element.select("div.ui.image a img").first()?.attr("src") - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) - - override fun latestUpdatesSelector() = "div.ui.black.segment div.ui.divided.celled.list div.item" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("div.content div.header a.popar").last().text() - url = element.select("div.content div.header a.popar").last().attr("href") - thumbnail_url = element.select("div.ui.tiny.bordered.image a img").first() - ?.attr("src") - ?.replace("60x80", "150x200") - } - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response, query) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = headers.newBuilder() - .set("X-Requested-With", "XMLHttpRequest") - .build() - - return GET("$baseUrl/api/titulos", newHeaders) - } - - private fun searchMangaParse(response: Response, query: String): MangasPage { - val result = response.asJsonArray() - - val resultFiltered = result - .filter { it["title"].string.contains(query, true) } - .map { - SManga.create().apply { - title = it["title"].string - url = it["url"].string - thumbnail_url = getCoverUrl(it["url"].string.substringAfterLast("/")) - } - } - - return MangasPage(resultFiltered, false) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val elementList = document.select("div.ui.black.segment div.ui.relaxed.list").first() - - author = elementList.select("div.item:eq(3) div.content div.description").text() - artist = elementList.select("div.item:eq(2) div.content div.description").text() - genre = elementList.select("div.item:eq(4) div.content div.description a") - .joinToString { it.text() } - - status = elementList.select("div.item:eq(6) div.content div.description") - .text().orEmpty().let { parseStatus(it) } - - description = elementList.select("div.item:eq(0) div.content div.description").text() - thumbnail_url = elementList.select("div.item:eq(0) div.content div.description img") - .attr("src") - } - - private fun parseStatus(status: String) = when { - status.contains("Em publicação") -> SManga.ONGOING - status.contains("Completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List<SChapter> { - // Filter only manga chapters. - return super.chapterListParse(response) - .filter { !it.url.contains("/novel/") } - } - - override fun chapterListSelector() = "table.ui.small.compact.very.basic.table tbody tr:not(.active)" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val firstColumn = element.select("td:eq(0)") - val secondColumn = element.select("td:eq(1)") - - url = firstColumn.select("a").first().attr("href") - name = firstColumn.select("a").first().text() - date_upload = secondColumn.select("small").first()?.text() - ?.let { parseChapterDate(it) } ?: 0 - } - - private fun parseChapterDate(date: String): Long { - return try { - SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - override fun pageListParse(document: Document): List<Page> { - val script = document.select("script").last().data() - val urlSuffix = script - .substringAfter(SCRIPT_URL_BEGIN) - .substringBefore(SCRIPT_URL_END) - val pages = script - .substringAfter(SCRIPT_PAGES_BEGIN) - .substringBefore(SCRIPT_PAGES_END) - .replace("'", "") - .split(",") - - val chapterUrl = document.select("meta[property='og:url']").attr("content") - - return pages - .mapIndexed { i, page -> Page(i, chapterUrl, "$urlSuffix$page.jpg") } - } - - override fun imageRequest(page: Page): Request { - val imageHeaders = Headers.Builder() - .add("Referer", page.url) - .build() - - return GET(page.imageUrl!!, imageHeaders) - } - - override fun imageUrlParse(document: Document) = "" - - override fun searchMangaSelector() = throw Exception("This method should not be called!") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("This method should not be called!") - - private fun getCoverUrl(slug: String): String = "$COVER_CDN/150x200/$slug.jpg" - - private fun Response.asJsonArray(): JsonArray = JSON_PARSER.parse(body()!!.string()).array - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - private const val COVER_CDN = "http://capas.centraldemangas.com.br" - - private const val SCRIPT_URL_BEGIN = "var urlSulfix = '" - private const val SCRIPT_URL_END = "';" - private const val SCRIPT_PAGES_BEGIN = "var pages = [" - private const val SCRIPT_PAGES_END = ",];" - - val JSON_PARSER by lazy { JsonParser() } - } -} diff --git a/src/pt/goldenmangas/AndroidManifest.xml b/src/pt/goldenmangas/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/goldenmangas/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/goldenmangas/build.gradle b/src/pt/goldenmangas/build.gradle deleted file mode 100644 index 13723a103..000000000 --- a/src/pt/goldenmangas/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Golden Mangás' - pkgNameSuffix = 'pt.goldenmangas' - extClass = '.GoldenMangas' - extVersionCode = 9 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/goldenmangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c3cb7433c..000000000 Binary files a/src/pt/goldenmangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 90472d8cc..000000000 Binary files a/src/pt/goldenmangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6fd5656e3..000000000 Binary files a/src/pt/goldenmangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index be7df0371..000000000 Binary files a/src/pt/goldenmangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/goldenmangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 54b898e07..000000000 Binary files a/src/pt/goldenmangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/goldenmangas/res/web_hi_res_512.png b/src/pt/goldenmangas/res/web_hi_res_512.png deleted file mode 100644 index e909cd0a2..000000000 Binary files a/src/pt/goldenmangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/goldenmangas/src/eu/kanade/tachiyomi/extension/pt/goldenmangas/GoldenMangas.kt b/src/pt/goldenmangas/src/eu/kanade/tachiyomi/extension/pt/goldenmangas/GoldenMangas.kt deleted file mode 100644 index 1b637e391..000000000 --- a/src/pt/goldenmangas/src/eu/kanade/tachiyomi/extension/pt/goldenmangas/GoldenMangas.kt +++ /dev/null @@ -1,188 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.goldenmangas - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class GoldenMangas : ParsedHttpSource() { - - // Hardcode the id because the language wasn't specific. - override val id: Long = 6858719406079923084 - - override val name = "Golden Mangás" - - override val baseUrl = "https://goldenmangas.top" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept", ACCEPT) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaSelector(): String = "div#maisLidos div.itemmanga" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h3").text().withoutLanguage() - thumbnail_url = element.select("img").attr("abs:src") - url = element.attr("href") - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request { - val path = if (page > 1) "/index.php?pagina=$page" else "" - return GET("$baseUrl$path", headers) - } - - override fun latestUpdatesSelector() = "div.col-sm-12.atualizacao > div.row" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - val infoElement = element.select("div.col-sm-10.col-xs-8 h3").first() - val thumbElement = element.select("a:first-child div img").first() - - title = infoElement.text().withoutLanguage() - thumbnail_url = thumbElement.attr("abs:src") - .replace("w=80&h=120", "w=380&h=600") - url = element.select("a:first-child").attr("href") - } - - override fun latestUpdatesNextPageSelector() = "ul.pagination li:last-child a" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = headers.newBuilder() - .set("Referer", "$baseUrl/mangas") - .build() - - val url = HttpUrl.parse("$baseUrl/mangas")!!.newBuilder() - .addQueryParameter("busca", query) - .toString() - - return GET(url, newHeaders) - } - - override fun searchMangaSelector() = "div.mangas.col-lg-2 a" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h3").text().withoutLanguage() - thumbnail_url = element.select("img").attr("abs:src") - url = element.attr("href") - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select("div.row > div.col-sm-8 > div.row").first() - val firstColumn = infoElement.select("div.col-sm-4.text-right > img").first() - val secondColumn = infoElement.select("div.col-sm-8").first() - - title = secondColumn.select("h2:eq(0)").text().withoutLanguage() - author = secondColumn.select("h5:eq(3)")!!.text().withoutLabel() - artist = secondColumn.select("h5:eq(4)")!!.text().withoutLabel() - genre = secondColumn.select("h5:eq(2) a") - .filter { it.text().isNotEmpty() } - .joinToString { it.text() } - status = secondColumn.select("h5:eq(5) a").text().toStatus() - description = document.select("#manga_capitulo_descricao").text() - thumbnail_url = firstColumn.attr("abs:src") - } - - override fun chapterListSelector() = "ul#capitulos li.row" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val firstColumn = element.select("a > div.col-sm-5") - val secondColumn = element.select("div.col-sm-5.text-right a[href^='http']") - - name = firstColumn.select("div.col-sm-5").first().text() - .substringBefore("(").trim() - scanlator = secondColumn?.joinToString { it.text() } - date_upload = firstColumn.select("div.col-sm-5 span[style]").text().toDate() - url = element.select("a").attr("href") - } - - override fun pageListRequest(chapter: SChapter): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl + chapter.url.substringBeforeLast("/")) - .build() - - return GET(baseUrl + chapter.url, newHeaders) - } - - override fun pageListParse(document: Document): List<Page> { - val chapterImages = document.select("div.col-sm-12[id^='capitulos_images']").first() - - return chapterImages.select("img[pag]") - .mapIndexed { i, element -> - Page(i, document.location(), element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun String.toDate(): Long { - return try { - DATE_FORMATTER.parse(this.trim())?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.toStatus() = when { - contains("Ativo") -> SManga.ONGOING - contains("Completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun String.withoutLabel(): String = substringAfter(":").trim() - - private fun String.withoutLanguage(): String = replace(FLAG_REGEX, "").trim() - - companion object { - private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + - "image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" - private const val ACCEPT_IMAGE = "image/webp,image/apng,image/*,*/*;q=0.8" - private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private val FLAG_REGEX = "\\((Pt[-/]br|Scan)\\)".toRegex(RegexOption.IGNORE_CASE) - - private val DATE_FORMATTER by lazy { SimpleDateFormat("(dd/MM/yyyy)", Locale.ENGLISH) } - } -} diff --git a/src/pt/hipercool/AndroidManifest.xml b/src/pt/hipercool/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/hipercool/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/hipercool/build.gradle b/src/pt/hipercool/build.gradle deleted file mode 100644 index 8acd7b957..000000000 --- a/src/pt/hipercool/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HipercooL' - pkgNameSuffix = 'pt.hipercool' - extClass = '.Hipercool' - extVersionCode = 6 - libVersion = '1.2' - containsNsfw = true -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/hipercool/res/mipmap-hdpi/ic_launcher.png b/src/pt/hipercool/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 111d92972..000000000 Binary files a/src/pt/hipercool/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hipercool/res/mipmap-mdpi/ic_launcher.png b/src/pt/hipercool/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fb19c5ea3..000000000 Binary files a/src/pt/hipercool/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hipercool/res/mipmap-xhdpi/ic_launcher.png b/src/pt/hipercool/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 93dc4f292..000000000 Binary files a/src/pt/hipercool/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hipercool/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/hipercool/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a9e484821..000000000 Binary files a/src/pt/hipercool/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hipercool/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/hipercool/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 901a2ffad..000000000 Binary files a/src/pt/hipercool/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hipercool/res/web_hi_res_512.png b/src/pt/hipercool/res/web_hi_res_512.png deleted file mode 100644 index 0843ac4cb..000000000 Binary files a/src/pt/hipercool/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/hipercool/src/eu/kanade/tachiyomi/extension/pt/hipercool/Hipercool.kt b/src/pt/hipercool/src/eu/kanade/tachiyomi/extension/pt/hipercool/Hipercool.kt deleted file mode 100644 index 442b3502d..000000000 --- a/src/pt/hipercool/src/eu/kanade/tachiyomi/extension/pt/hipercool/Hipercool.kt +++ /dev/null @@ -1,261 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.hipercool - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.jsonObject -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -@Nsfw -class Hipercool : HttpSource() { - - // Hardcode the id because the language wasn't specific. - override val id: Long = 5898568703656160 - - override val name = "HipercooL" - - override val baseUrl = "https://hiper.cool" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Referer", baseUrl) - .add("X-Requested-With", "XMLHttpRequest") - - private fun genericMangaListParse(response: Response): MangasPage { - val result = response.asJsonArray() - - if (result.size() == 0) - return MangasPage(emptyList(), false) - - val mangaList = result - .map { genericMangaFromObject(it.obj) } - .distinctBy { it.title } - - val hasNextPage = result.size() == DEFAULT_COUNT - - return MangasPage(mangaList, hasNextPage) - } - - private fun genericMangaFromObject(obj: JsonObject): SManga { - val book = obj["_book"].obj - val bookSlug = book["slug"].string - val bookRevision = book["revision"]?.int ?: 1 - - return SManga.create().apply { - title = book["title"].string - thumbnail_url = bookSlug.toThumbnailUrl(bookRevision) - url = "/books/$bookSlug" - } - } - - // The source does not have popular mangas, so use latest instead. - override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) - - override fun popularMangaParse(response: Response): MangasPage = genericMangaListParse(response) - - override fun latestUpdatesRequest(page: Int): Request { - val start = (page - 1) * DEFAULT_COUNT - return GET("$baseUrl/api/books/chapters?start=$start&count=$DEFAULT_COUNT", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = genericMangaListParse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val mediaType = MediaType.parse("application/json; charset=utf-8") - - // Create json body. - val json = jsonObject( - "start" to (page - 1) * DEFAULT_COUNT, - "count" to DEFAULT_COUNT, - "text" to query, - "type" to "text" - ) - - val body = RequestBody.create(mediaType, json.toString()) - - return POST("$baseUrl/api/books/chapters/search", headers, body) - } - - override fun searchMangaParse(response: Response): MangasPage = genericMangaListParse(response) - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(mangaDetailsApiRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun mangaDetailsApiRequest(manga: SManga): Request { - val slug = manga.url.substringAfterLast("/") - - return GET("$baseUrl/api/books/$slug", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val result = response.asJsonObject() - - val artists = result["tags"].array - .filter { it["label"].string == "Artista" } - .flatMap { it["values"].array } - .joinToString("; ") { it["label"].string } - - val authors = result["tags"].array - .filter { it["label"].string == "Autor" } - .flatMap { it["values"].array } - .joinToString("; ") { it["label"].string } - - val tags = result["tags"].array - .filter { it["label"].string == "Tags" } - .flatMap { it["values"].array } - .joinToString(", ") { it["label"].string } - - return SManga.create().apply { - title = result["title"].string - thumbnail_url = result["slug"].string.toThumbnailUrl(result["revision"].int) - description = result["synopsis"]?.string ?: "" - artist = artists - author = authors - genre = tags - } - } - - // Chapters are available in the same url of the manga details. - override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val result = response.asJsonObject() - - if (!result["chapters"]!!.isJsonArray) - return emptyList() - - return result["chapters"].array - .map { chapterListItemParse(result, it.obj) } - .reversed() - } - - private fun chapterListItemParse(book: JsonObject, obj: JsonObject): SChapter = SChapter.create().apply { - name = obj["title"].string - chapter_number = obj["title"].string.toFloatOrNull() ?: -1f - // The property is written wrong. - date_upload = DATE_FORMATTER.tryParseTime(obj["publishied_at"].string) - - val fullUrl = HttpUrl.parse("$baseUrl/books")!!.newBuilder() - .addPathSegment(book["slug"].string) - .addPathSegment(obj["slug"].string) - .addQueryParameter("images", obj["images"].int.toString()) - .addQueryParameter("revision", book["revision"].int.toString()) - .toString() - - setUrlWithoutDomain(fullUrl) - } - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - val chapterUrl = HttpUrl.parse(baseUrl + chapter.url)!! - - val bookSlug = chapterUrl.pathSegments()[1] - val chapterSlug = chapterUrl.pathSegments()[2] - val images = chapterUrl.queryParameter("images")!!.toInt() - val revision = chapterUrl.queryParameter("revision")!!.toInt() - - val pages = arrayListOf<Page>() - - // Create the pages. - for (i in 1..images) { - val imageUrl = HttpUrl.parse("$STATIC_URL/books")!!.newBuilder() - .addPathSegment(bookSlug) - .addPathSegment(chapterSlug) - .addPathSegment("$bookSlug-chapter-$chapterSlug-page-$i.jpg") - .addQueryParameter("revision", revision.toString()) - .toString() - - pages += Page(i - 1, chapter.url, imageUrl) - } - - return Observable.just(pages) - } - - override fun pageListParse(response: Response): List<Page> = throw Exception("This method should not be called!") - - override fun fetchImageUrl(page: Page): Observable<String> { - return Observable.just(page.imageUrl!!) - } - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl + page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun SimpleDateFormat.tryParseTime(date: String): Long { - return try { - parse(date.substringBefore("T"))?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.toThumbnailUrl(revision: Int): String = - HttpUrl.parse("$STATIC_URL/books")!!.newBuilder() - .addPathSegment(this) - .addPathSegment("$this-cover.jpg") - .addQueryParameter("revision", revision.toString()) - .toString() - - private fun Response.asJsonObject(): JsonObject = JSON_PARSER.parse(body()!!.string()).obj - - private fun Response.asJsonArray(): JsonArray = JSON_PARSER.parse(body()!!.string()).array - - companion object { - private const val STATIC_URL = "https://static.hiper.cool" - - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private const val DEFAULT_COUNT = 40 - - private val JSON_PARSER by lazy { JsonParser() } - - private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } - } -} diff --git a/src/pt/hqdragon/AndroidManifest.xml b/src/pt/hqdragon/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/hqdragon/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/hqdragon/build.gradle b/src/pt/hqdragon/build.gradle deleted file mode 100644 index 8eb08a8e7..000000000 --- a/src/pt/hqdragon/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HQ Dragon' - pkgNameSuffix = 'pt.hqdragon' - extClass = '.HQDragon' - extVersionCode = 4 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/hqdragon/res/mipmap-hdpi/ic_launcher.png b/src/pt/hqdragon/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7c21d8b91..000000000 Binary files a/src/pt/hqdragon/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqdragon/res/mipmap-mdpi/ic_launcher.png b/src/pt/hqdragon/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 04df4fb73..000000000 Binary files a/src/pt/hqdragon/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqdragon/res/mipmap-xhdpi/ic_launcher.png b/src/pt/hqdragon/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2d51d19fa..000000000 Binary files a/src/pt/hqdragon/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqdragon/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/hqdragon/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index de7b1a1cf..000000000 Binary files a/src/pt/hqdragon/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqdragon/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/hqdragon/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5ea6265cd..000000000 Binary files a/src/pt/hqdragon/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqdragon/res/web_hi_res_512.png b/src/pt/hqdragon/res/web_hi_res_512.png deleted file mode 100644 index 3992c579d..000000000 Binary files a/src/pt/hqdragon/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/hqdragon/src/eu/kanade/tachiyomi/extension/pt/hqdragon/HQDragon.kt b/src/pt/hqdragon/src/eu/kanade/tachiyomi/extension/pt/hqdragon/HQDragon.kt deleted file mode 100644 index 084cbf6e6..000000000 --- a/src/pt/hqdragon/src/eu/kanade/tachiyomi/extension/pt/hqdragon/HQDragon.kt +++ /dev/null @@ -1,206 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.hqdragon - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.util.concurrent.TimeUnit - -class HQDragon : ParsedHttpSource() { - - override val name = "HQ Dragon" - - override val baseUrl = "https://hqdragon.com" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept", ACCEPT) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("Referer", "$baseUrl/") - - // Popular - - // Top 10 - override fun popularMangaRequest(page: Int): Request { - return GET(baseUrl, headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val results = super.popularMangaParse(response) - - if (results.mangas.isEmpty()) { - throw Exception(BLOCK_MESSAGE) - } - - return results - } - - override fun popularMangaSelector() = "h4:contains(Top 10) + ol.mb-0 li a" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.text() - setUrlWithoutDomain(element.attr("href")) - } - - override fun popularMangaNextPageSelector(): String? = null - - // Latest - - override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { - return super.fetchLatestUpdates(page) - .map { results -> results.copy(hasNextPage = page < 5) } - } - - override fun latestUpdatesRequest(page: Int): Request { - val formBody = FormBody.Builder() - .add("pagina", page.toString()) - .build() - - val headers = headersBuilder() - .add("Content-Length", formBody.contentLength().toString()) - .add("Content-Type", formBody.contentType().toString()) - .add("Origin", baseUrl) - .add("X-Requested-With", "XMLHttpRequest") - .set("Accept", "*/*") - .build() - - return POST("$baseUrl/assets/php/index_paginar.php", headers, formBody) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val results = super.latestUpdatesParse(response) - - if (results.mangas.isEmpty()) { - throw Exception(BLOCK_MESSAGE) - } - - return results - } - - override fun latestUpdatesSelector() = "a:has(img)" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - val image = element.select("img").first() - - title = image.attr("alt") - thumbnail_url = image.attr("abs:src") - setUrlWithoutDomain(element.attr("href")) - } - - override fun latestUpdatesNextPageSelector(): String? = null - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/pesquisa")!!.newBuilder() - .addQueryParameter("titulo", query) - - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - val results = super.searchMangaParse(response) - - if (results.mangas.isEmpty()) { - throw Exception(BLOCK_MESSAGE) - } - - return results - } - - override fun searchMangaSelector() = "div.col-sm-6.col-md-3:has(img.img-thumbnail)" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - val link = element.select("a + a").first() - - title = link.text() - thumbnail_url = element.select("img").first().attr("abs:src") - setUrlWithoutDomain(link.attr("href")) - } - - override fun searchMangaNextPageSelector(): String? = null - - // Manga summary page - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select("div.blog-post div.row").firstOrNull() - ?: throw Exception(BLOCK_MESSAGE) - - title = infoElement.select("h3").first().text() - author = infoElement.select("p:contains(Editora:)").first().textWithoutLabel() - status = infoElement.select("p:contains(Status:) span").first().text().toStatus() - description = infoElement.select("p:contains(Sinopse:)").first().ownText() - thumbnail_url = infoElement.select("div.col-md-4 .img-fluid").first().attr("src") - } - - // Chapters - - override fun chapterListSelector() = "table.table tr a" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.text().replace("Ler ", "") - setUrlWithoutDomain(element.attr("href")) - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("img.img-responsive.img-manga") - .filter { it.attr("src").contains("/leitor/") } - .mapIndexed { i, element -> - Page(i, document.location(), element.absUrl("src")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun Element.textWithoutLabel(): String = text()!!.substringAfter(":").trim() - - private fun String.toStatus(): Int = when { - contains("Ativo") -> SManga.ONGOING - contains("Completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - companion object { - private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + - "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private const val BLOCK_MESSAGE = "O site está bloqueando o Tachiyomi. " + - "Migre para outra fonte caso o problema persistir." - } -} diff --git a/src/pt/hqnow/AndroidManifest.xml b/src/pt/hqnow/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/hqnow/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/hqnow/build.gradle b/src/pt/hqnow/build.gradle deleted file mode 100644 index 07aaf7cad..000000000 --- a/src/pt/hqnow/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HQ Now!' - pkgNameSuffix = 'pt.hqnow' - extClass = '.HQNow' - extVersionCode = 3 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/hqnow/res/mipmap-hdpi/ic_launcher.png b/src/pt/hqnow/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 62954776a..000000000 Binary files a/src/pt/hqnow/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqnow/res/mipmap-mdpi/ic_launcher.png b/src/pt/hqnow/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 84256e8ca..000000000 Binary files a/src/pt/hqnow/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqnow/res/mipmap-xhdpi/ic_launcher.png b/src/pt/hqnow/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 00d734b44..000000000 Binary files a/src/pt/hqnow/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqnow/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/hqnow/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3c287a74f..000000000 Binary files a/src/pt/hqnow/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqnow/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/hqnow/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6cee95f27..000000000 Binary files a/src/pt/hqnow/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/hqnow/res/web_hi_res_512.png b/src/pt/hqnow/res/web_hi_res_512.png deleted file mode 100644 index 13d021bfa..000000000 Binary files a/src/pt/hqnow/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNow.kt b/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNow.kt deleted file mode 100644 index 8745e5d4a..000000000 --- a/src/pt/hqnow/src/eu/kanade/tachiyomi/extension/pt/hqnow/HQNow.kt +++ /dev/null @@ -1,199 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.hqnow - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import java.util.concurrent.TimeUnit - -class HQNow : HttpSource() { - - override val name = "HQ Now!" - - // Website is http://www.hq-now.com - override val baseUrl = "http://admin.hq-now.com/graphql" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - private val gson = Gson() - - private val jsonHeaders = headersBuilder().add("content-type", "application/json").build() - - private fun mangaFromResponse(response: Response, selector: String, coversAvailable: Boolean = true): List<SManga> { - return gson.fromJson<JsonObject>(response.body()!!.string())["data"][selector].asJsonArray - .map { - SManga.create().apply { - url = it["id"].asString - title = it["name"].asString - if (coversAvailable) thumbnail_url = it["hqCover"].asString - } - } - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return POST(baseUrl, jsonHeaders, RequestBody.create(null, "{\"operationName\":\"getHqsByFilters\",\"variables\":{\"orderByViews\":true,\"loadCovers\":true,\"limit\":30},\"query\":\"query getHqsByFilters(\$orderByViews: Boolean, \$limit: Int, \$publisherId: Int, \$loadCovers: Boolean) {\\n getHqsByFilters(orderByViews: \$orderByViews, limit: \$limit, publisherId: \$publisherId, loadCovers: \$loadCovers) {\\n id\\n name\\n editoraId\\n status\\n publisherName\\n hqCover\\n synopsis\\n updatedAt\\n }\\n}\\n\"}")) - } - - override fun popularMangaParse(response: Response): MangasPage { - return MangasPage(mangaFromResponse(response, "getHqsByFilters"), false) - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return POST(baseUrl, jsonHeaders, RequestBody.create(null, "{\"operationName\":\"getRecentlyUpdatedHqs\",\"variables\":{},\"query\":\"query getRecentlyUpdatedHqs {\\n getRecentlyUpdatedHqs {\\n name\\n hqCover\\n synopsis\\n id\\n updatedAt\\n updatedChapters\\n }\\n}\\n\"}")) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - return MangasPage(mangaFromResponse(response, "getRecentlyUpdatedHqs"), false) - } - - // Search - - private var queryIsTitle = true - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - queryIsTitle = true - POST(baseUrl, jsonHeaders, RequestBody.create(null, "{\"operationName\":\"getHqsByName\",\"variables\":{\"name\":\"$query\"},\"query\":\"query getHqsByName(\$name: String!) {\\n getHqsByName(name: \$name) {\\n id\\n name\\n editoraId\\n status\\n publisherName\\n impressionsCount\\n }\\n}\\n\"}")) - } else { - queryIsTitle = false - var searchLetter = "" - - filters.forEach { filter -> - when (filter) { - is LetterFilter -> { - searchLetter = filter.toUriPart() - } - } - } - POST(baseUrl, jsonHeaders, RequestBody.create(null, "{\"operationName\":\"getHqsByNameStartingLetter\",\"variables\":{\"letter\":\"$searchLetter-$searchLetter\"},\"query\":\"query getHqsByNameStartingLetter(\$letter: String!) {\\n getHqsByNameStartingLetter(letter: \$letter) {\\n id\\n name\\n editoraId\\n status\\n publisherName\\n impressionsCount\\n }\\n}\\n\"}")) - } - } - - override fun searchMangaParse(response: Response): MangasPage { - return MangasPage(mangaFromResponse(response, if (queryIsTitle) "getHqsByName" else "getHqsByNameStartingLetter", false), false) - } - - // Details - - override fun mangaDetailsRequest(manga: SManga): Request { - return POST(baseUrl, jsonHeaders, RequestBody.create(null, "{\"operationName\":\"getHqsById\",\"variables\":{\"id\":${manga.url}},\"query\":\"query getHqsById(\$id: Int!) {\\n getHqsById(id: \$id) {\\n id\\n name\\n synopsis\\n editoraId\\n status\\n publisherName\\n hqCover\\n impressionsCount\\n capitulos {\\n name\\n id\\n number\\n }\\n }\\n}\\n\"}")) - } - - override fun mangaDetailsParse(response: Response): SManga { - return gson.fromJson<JsonObject>(response.body()!!.string())["data"]["getHqsById"][0] - .let { - SManga.create().apply { - title = it["name"].asString - thumbnail_url = it["hqCover"].asString - description = it["synopsis"].asString - author = it["publisherName"].asString - status = when (it["status"].asString) { - "Concluído" -> SManga.COMPLETED - "Em Andamento" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } - } - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request { - return mangaDetailsRequest(manga) - } - - override fun chapterListParse(response: Response): List<SChapter> { - return gson.fromJson<JsonObject>(response.body()!!.string())["data"]["getHqsById"][0]["capitulos"].asJsonArray - .map { - SChapter.create().apply { - url = it["id"].asString - name = it["name"].asString.let { jsonName -> - if (jsonName.isNotEmpty()) jsonName.trim() else "Capitulo: " + it["number"].asString - } - } - }.reversed() - } - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return POST(baseUrl, jsonHeaders, RequestBody.create(null, "{\"operationName\":\"getChapterById\",\"variables\":{\"chapterId\":${chapter.url}},\"query\":\"query getChapterById(\$chapterId: Int!) {\\n getChapterById(chapterId: \$chapterId) {\\n name\\n number\\n oneshot\\n pictures {\\n pictureUrl\\n }\\n hq {\\n id\\n name\\n capitulos {\\n id\\n number\\n }\\n }\\n }\\n}\\n\"}")) - } - - override fun pageListParse(response: Response): List<Page> { - return gson.fromJson<JsonObject>(response.body()!!.string())["data"]["getChapterById"]["pictures"].asJsonArray - .mapIndexed { i, json -> Page(i, "", json["pictureUrl"].asString) } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("NOTA: Ignorado se estiver usando"), - Filter.Header("a pesquisa de texto!"), - Filter.Separator(), - LetterFilter() - ) - - private class LetterFilter : UriPartFilter( - "Letra", - arrayOf( - Pair("---", "<Selecione>"), - Pair("a", "A"), - Pair("b", "B"), - Pair("c", "C"), - Pair("d", "D"), - Pair("e", "E"), - Pair("f", "F"), - Pair("g", "G"), - Pair("h", "H"), - Pair("i", "I"), - Pair("j", "J"), - Pair("k", "K"), - Pair("l", "L"), - Pair("m", "M"), - Pair("n", "N"), - Pair("o", "O"), - Pair("p", "P"), - Pair("q", "Q"), - Pair("r", "R"), - Pair("s", "S"), - Pair("t", "T"), - Pair("u", "U"), - Pair("v", "V"), - Pair("w", "W"), - Pair("x", "X"), - Pair("y", "Y"), - Pair("z", "Z") - ) - ) - - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) { - fun toUriPart() = vals[state].first - } -} diff --git a/src/pt/mangahost/AndroidManifest.xml b/src/pt/mangahost/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/mangahost/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/mangahost/build.gradle b/src/pt/mangahost/build.gradle deleted file mode 100644 index 4bc3c2b4f..000000000 --- a/src/pt/mangahost/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangá Host' - pkgNameSuffix = 'pt.mangahost' - extClass = '.MangaHost' - extVersionCode = 23 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/mangahost/res/mipmap-hdpi/ic_launcher.png b/src/pt/mangahost/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index eaf86b0f2..000000000 Binary files a/src/pt/mangahost/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangahost/res/mipmap-mdpi/ic_launcher.png b/src/pt/mangahost/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index dfcf87577..000000000 Binary files a/src/pt/mangahost/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangahost/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mangahost/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b2b7a89ea..000000000 Binary files a/src/pt/mangahost/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangahost/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mangahost/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a220903e1..000000000 Binary files a/src/pt/mangahost/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangahost/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mangahost/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 70558e895..000000000 Binary files a/src/pt/mangahost/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangahost/res/web_hi_res_512.png b/src/pt/mangahost/res/web_hi_res_512.png deleted file mode 100644 index e7a9e90e3..000000000 Binary files a/src/pt/mangahost/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/mangahost/src/eu/kanade/tachiyomi/extension/pt/mangahost/MangaHost.kt b/src/pt/mangahost/src/eu/kanade/tachiyomi/extension/pt/mangahost/MangaHost.kt deleted file mode 100644 index 0181c0b74..000000000 --- a/src/pt/mangahost/src/eu/kanade/tachiyomi/extension/pt/mangahost/MangaHost.kt +++ /dev/null @@ -1,257 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mangahost - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Call -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MangaHost : ParsedHttpSource() { - - // Hardcode the id because the name was wrong and the language wasn't specific. - override val id: Long = 3926812845500643354 - - override val name = "Mangá Host" - - override val baseUrl = "https://mangahosted.com" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 3, TimeUnit.SECONDS)) - .addInterceptor(::blockMessageIntercept) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept", ACCEPT) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("Referer", baseUrl) - .add("User-Agent", USER_AGENT) - - private fun genericMangaFromElement(element: Element): SManga = - SManga.create().apply { - val thumbnailEl = element.select("img") - val thumbnailAttr = if (thumbnailEl.hasAttr("data-path")) "data-path" else "src" - - title = element.attr("title").withoutLanguage() - thumbnail_url = thumbnailEl.attr(thumbnailAttr).toLargeUrl() - setUrlWithoutDomain(element.attr("href")) - } - - override fun popularMangaRequest(page: Int): Request { - val listPath = if (page == 1) "" else "/mais-visualizados/page/${page - 1}" - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/mangas$listPath") - .build() - - val pageStr = if (page != 1) "/page/$page" else "" - return GET("$baseUrl/mangas/mais-visualizados$pageStr", newHeaders) - } - - override fun popularMangaSelector(): String = "div#dados div.manga-block div.manga-block-left a" - - override fun popularMangaFromElement(element: Element): SManga = genericMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "div.wp-pagenavi:has(a.nextpostslink)" - - override fun latestUpdatesRequest(page: Int): Request { - val listPath = if (page == 1) "" else "/lancamentos/page/${page - 1}" - val newHeaders = headersBuilder() - .set("Referer", baseUrl + listPath) - .build() - - val pageStr = if (page != 1) "/page/$page" else "" - return GET("$baseUrl/lancamentos$pageStr", newHeaders) - } - - override fun latestUpdatesSelector() = "div#dados div.line-lancamentos div.column-img a" - - override fun latestUpdatesFromElement(element: Element): SManga = genericMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/find")!!.newBuilder() - .addQueryParameter("this", query) - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "table.table-search > tbody > tr > td:eq(0) > a" - - override fun searchMangaFromElement(element: Element): SManga = genericMangaFromElement(element) - - override fun searchMangaNextPageSelector(): String? = null - - /** - * The site wrongly return 404 for some titles, even if they are present. - * In those cases, the extension will parse the response normally. - */ - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(mangaDetailsRequest(manga)) - .asObservableIgnoreCode(404) - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select("div.box-content div.w-row div.w-col:eq(1) article") - - author = infoElement.select("div.text li div:contains(Autor:)").textWithoutLabel() - artist = infoElement.select("div.text li div:contains(Arte:)").textWithoutLabel() - genre = infoElement.select("h3.subtitle + div.tags a").joinToString { it.text() } - description = infoElement.select("div.text div.paragraph").first()?.text() - ?.substringBefore("Relacionados:") - status = infoElement.select("div.text li div:contains(Status:)").text().toStatus() - thumbnail_url = document.select("div.box-content div.w-row div.w-col:eq(0) div.widget img") - .attr("src") - } - - /** - * The site wrongly return 404 for some titles, even if they are present. - * In those cases, the extension will parse the response normally. - */ - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return if (manga.status != SManga.LICENSED) { - client.newCall(chapterListRequest(manga)) - .asObservableIgnoreCode(404) - .map(::chapterListParse) - } else { - Observable.error(Exception("Licensed - No chapters to show")) - } - } - - override fun chapterListSelector(): String = - "article.article > section.clearfix div.chapters div.cap div.card.pop" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.select("div.pop-title").text().withoutLanguage() - scanlator = element.select("div.pop-content small strong").text() - date_upload = element.select("small.clearfix").text() - .substringAfter("Adicionado em ") - .toDate() - chapter_number = element.select("div.pop-title span.btn-caps").text() - .toFloatOrNull() ?: 1f - setUrlWithoutDomain(element.select("div.tags a").attr("href")) - - if (scanlator!!.split("/").count() >= 5) { - val scanlators = scanlator!!.split("/") - scanlator = scanlators[0] + " e mais " + (scanlators.count() - 1) - } - } - - /** - * The site wrongly return 404 for some chapters, even if they are present. - * In those cases, the extension will parse the response normally. - */ - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - return client.newCall(pageListRequest(chapter)) - .asObservableIgnoreCode(404) - .map(::pageListParse) - } - - override fun pageListRequest(chapter: SChapter): Request { - // Just to prevent the detection of the crawler. - val newHeader = headersBuilder() - .set("Referer", "$baseUrl${chapter.url}".substringBeforeLast("/")) - .build() - - return GET(baseUrl + chapter.url, newHeader) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div#slider a img") - .mapIndexed { i, el -> Page(i, document.location(), el.attr("src")) } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", "$baseUrl/") - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun blockMessageIntercept(chain: Interceptor.Chain): Response { - val response = chain.proceed(chain.request()) - - if (!response.isSuccessful && response.code() == 403) { - response.close() - throw Exception(BLOCK_MESSAGE) - } - - return response - } - - private fun Call.asObservableIgnoreCode(code: Int): Observable<Response> { - return asObservable().doOnNext { response -> - if (!response.isSuccessful && response.code() != code) { - response.close() - throw Exception("HTTP error ${response.code()}") - } - } - } - - private fun String.toDate(): Long { - return try { - DATE_FORMAT.parse(this)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.toStatus() = when { - contains("Ativo") -> SManga.ONGOING - contains("Completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun String.withoutLanguage(): String = replace(LANG_REGEX, "") - - private fun String.toLargeUrl(): String = replace(IMAGE_REGEX, "_full.") - - private fun Elements.textWithoutLabel(): String = text()!!.substringAfter(":").trim() - - companion object { - private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + - "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" - - private val LANG_REGEX = "( )?\\((PT-)?BR\\)".toRegex() - private val IMAGE_REGEX = "_(small|medium|xmedium)\\.".toRegex() - private val CDN_REGEX = "/mangas_files/.*\\.jpg".toRegex() - - private const val BLOCK_MESSAGE = "O site está bloqueando o Tachiyomi. Migre para outra fonte caso o problema persistir." - - private val DATE_FORMAT by lazy { - SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH) - } - } -} diff --git a/src/pt/mangayabu/AndroidManifest.xml b/src/pt/mangayabu/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/mangayabu/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/mangayabu/build.gradle b/src/pt/mangayabu/build.gradle deleted file mode 100644 index d584a53c4..000000000 --- a/src/pt/mangayabu/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaYabu!' - pkgNameSuffix = 'pt.mangayabu' - extClass = '.MangaYabu' - extVersionCode = 7 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/mangayabu/res/mipmap-hdpi/ic_launcher.png b/src/pt/mangayabu/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 52504b980..000000000 Binary files a/src/pt/mangayabu/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangayabu/res/mipmap-mdpi/ic_launcher.png b/src/pt/mangayabu/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17e1efd71..000000000 Binary files a/src/pt/mangayabu/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangayabu/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mangayabu/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index add57d959..000000000 Binary files a/src/pt/mangayabu/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangayabu/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mangayabu/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9dae0527a..000000000 Binary files a/src/pt/mangayabu/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangayabu/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mangayabu/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 17e24679b..000000000 Binary files a/src/pt/mangayabu/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mangayabu/res/web_hi_res_512.png b/src/pt/mangayabu/res/web_hi_res_512.png deleted file mode 100644 index a22c8f2f2..000000000 Binary files a/src/pt/mangayabu/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/mangayabu/src/eu/kanade/tachiyomi/extension/pt/mangayabu/MangaYabu.kt b/src/pt/mangayabu/src/eu/kanade/tachiyomi/extension/pt/mangayabu/MangaYabu.kt deleted file mode 100644 index 96e62f11b..000000000 --- a/src/pt/mangayabu/src/eu/kanade/tachiyomi/extension/pt/mangayabu/MangaYabu.kt +++ /dev/null @@ -1,198 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mangayabu - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class MangaYabu : ParsedHttpSource() { - - // Hardcode the id because the language wasn't specific. - override val id: Long = 7152688036023311164 - - override val name = "MangaYabu!" - - override val baseUrl = "https://mangayabu.top" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .connectTimeout(2, TimeUnit.MINUTES) - .readTimeout(2, TimeUnit.MINUTES) - .writeTimeout(2, TimeUnit.MINUTES) - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaSelector(): String = "#main div.row:contains(Populares) div.carousel div.card > a" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - val thumb = element.select("img").first()!! - - title = thumb.attr("alt").withoutFlags() - thumbnail_url = thumb.attr("src") - setUrlWithoutDomain(element.attr("href")) - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { - return super.fetchLatestUpdates(page) - .map { MangasPage(it.mangas.distinctBy { m -> m.url }, it.hasNextPage) } - } - - override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) - - override fun latestUpdatesSelector() = "#main div.row:contains(Lançamentos) div.card div.card-image > a" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - val thumb = element.select("img").first()!! - - title = thumb.attr("alt").substringBefore(" –").withoutFlags() - thumbnail_url = thumb.attr("src") - url = mapChapterToMangaUrl(element.attr("href")) - } - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val form = FormBody.Builder() - .add("action", "data_fetch") - .add("search_keyword", query) - .build() - - val newHeaders = headers.newBuilder() - .add("X-Requested-With", "XMLHttpRequest") - .add("Content-Length", form.contentLength().toString()) - .add("Content-Type", form.contentType().toString()) - .build() - - return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, form) - } - - override fun searchMangaSelector() = "ul.popup-list div.row > div.col.s4 a.search-links" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - val thumbnail = element.select("img").first()!! - - title = thumbnail.attr("alt").withoutFlags() - thumbnail_url = thumbnail.attr("src") - setUrlWithoutDomain(element.attr("href")) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.manga-column") - - return SManga.create().apply { - title = document.select("div.manga-info > h1").first()!!.text() - status = infoElement.select("div.manga-column:contains(Status:)").first()!! - .textWithoutLabel() - .toStatus() - genre = infoElement.select("div.manga-column:contains(Gêneros:)").first()!! - .textWithoutLabel() - description = document.select("div.manga-info").first()!!.text() - .substringAfter(title) - .trim() - thumbnail_url = document.select("div.manga-index div.mango-hover img")!! - .attr("src") - } - } - - override fun chapterListSelector() = "div.manga-info:contains(Capítulos) div.manga-chapters div.single-chapter" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.select("a").first()!!.text() - date_upload = element.select("small")!!.text().toDate() - setUrlWithoutDomain(element.select("a").first()!!.attr("href")) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.image-navigator img.slideit") - .mapIndexed { i, element -> - Page(i, document.location(), element.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - /** - * Some mangas doesn't use the same slug from the chapter url, and - * since the site doesn't have a proper popular list yet, we have - * to deal with some exceptions and map them to the correct - * slug manually. - * - * It's a bad solution, but it's a working one for now. - */ - private fun mapChapterToMangaUrl(chapterUrl: String): String { - val chapterSlug = chapterUrl - .substringBefore("-capitulo") - .substringAfter("ler/") - - return "/manga/" + (SLUG_EXCEPTIONS[chapterSlug] ?: chapterSlug) - } - - private fun String.toDate(): Long { - return try { - DATE_FORMATTER.parse(this)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.toStatus() = when (this) { - "Em lançamento" -> SManga.ONGOING - "Completo" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun String.withoutFlags(): String = replace(FLAG_REGEX, "").trim() - - private fun Element.textWithoutLabel(): String = text()!!.substringAfter(":").trim() - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private val FLAG_REGEX = "\\((Pt[-/]br|Scan)\\)".toRegex(RegexOption.IGNORE_CASE) - - private val DATE_FORMATTER by lazy { SimpleDateFormat("dd/MM/yy", Locale.ENGLISH) } - - private val SLUG_EXCEPTIONS = mapOf( - "the-promised-neverland-yakusoku-no-neverland" to "yakusoku-no-neverland-the-promised-neverland" - ) - } -} diff --git a/src/pt/mundohentai/AndroidManifest.xml b/src/pt/mundohentai/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/mundohentai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/mundohentai/build.gradle b/src/pt/mundohentai/build.gradle deleted file mode 100644 index bfc49afcf..000000000 --- a/src/pt/mundohentai/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mundo Hentai' - pkgNameSuffix = 'pt.mundohentai' - extClass = '.MundoHentai' - extVersionCode = 2 - libVersion = '1.2' - containsNsfw = true -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/mundohentai/res/mipmap-hdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 18d25dc5e..000000000 Binary files a/src/pt/mundohentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundohentai/res/mipmap-mdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ef17dc53d..000000000 Binary files a/src/pt/mundohentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundohentai/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d267b6da8..000000000 Binary files a/src/pt/mundohentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundohentai/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 473e8a61f..000000000 Binary files a/src/pt/mundohentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundohentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mundohentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e5ca2f39e..000000000 Binary files a/src/pt/mundohentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundohentai/res/web_hi_res_512.png b/src/pt/mundohentai/res/web_hi_res_512.png deleted file mode 100644 index fbbafb352..000000000 Binary files a/src/pt/mundohentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/mundohentai/src/eu/kanade/tachiyomi/extension/pt/mundohentai/MundoHentai.kt b/src/pt/mundohentai/src/eu/kanade/tachiyomi/extension/pt/mundohentai/MundoHentai.kt deleted file mode 100644 index b484d9866..000000000 --- a/src/pt/mundohentai/src/eu/kanade/tachiyomi/extension/pt/mundohentai/MundoHentai.kt +++ /dev/null @@ -1,193 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mundohentai - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.concurrent.TimeUnit - -@Nsfw -class MundoHentai : ParsedHttpSource() { - - override val name = "Mundo Hentai" - - override val baseUrl = "https://mundohentaioficial.com" - - override val lang = "pt-BR" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Referer", baseUrl) - - private fun genericMangaFromElement(element: Element): SManga = - SManga.create().apply { - title = element.select("div.menu a.title").text() - thumbnail_url = element.attr("style") - .substringAfter("url(\"") - .substringBefore("\")") - url = element.select("a.absolute").attr("href") - } - - // The source does not have a popular list page, so we use the Doujin list instead. - override fun popularMangaRequest(page: Int): Request { - val newHeaders = headersBuilder() - .set("Referer", if (page == 1) baseUrl else "$baseUrl/tipo/doujin/${page - 1}") - .build() - - val pageStr = if (page != 1) "/$page" else "" - return GET("$baseUrl/tipo/doujin$pageStr", newHeaders) - } - - override fun popularMangaSelector(): String = "ul.post-list li div.card:has(a.absolute[href^=/])" - - override fun popularMangaFromElement(element: Element): SManga = genericMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "div.buttons:not(:has(a.selected + a.material-icons))" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotEmpty()) { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("q", query) - .toString() - - return GET(url, headers) - } - - val tagFilter = filters[1] as TagFilter - val tagSlug = tagFilter.values[tagFilter.state].slug - - val newHeaders = headersBuilder() - .set("Referer", if (page == 1) "$baseUrl/categories" else "$baseUrl/tags/$tagSlug/${page - 1}") - .build() - - val pageStr = if (page != 1) "/$page" else "" - return GET("$baseUrl/tags/$tagSlug$pageStr", newHeaders) - } - - override fun searchMangaSelector() = popularMangaSelector() + ":not(:has(div.right-tape))" - - override fun searchMangaFromElement(element: Element): SManga = genericMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val post = document.select("div.post") - - return SManga.create().apply { - author = post.select("div.tags div.tag:contains(Artista:) a.value").text() - genre = post.select("div.tags div.tag:contains(Tags:) a.value").joinToString { it.text() } - description = post.select("div.tags div.tag:contains(Tipo:)").text() - .plus("\n" + post.select("div.tags div.tag:contains(Cor:)").text()) - status = SManga.COMPLETED - thumbnail_url = post.select("div.cover img").attr("src") - } - } - - override fun chapterListSelector(): String = "div.post header.data div.float-buttons a.read" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = "Capítulo" - scanlator = element.parent().parent() - .select("div.tags div.tag:contains(Tradutor:) a.value") - .text() - chapter_number = 1f - url = element.attr("href") - } - - override fun pageListRequest(chapter: SChapter): Request { - val newHeader = headersBuilder() - .set("Referer", "$baseUrl${chapter.url}".substringBeforeLast("/")) - .build() - - return GET(baseUrl + chapter.url, newHeader) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.gallery > img") - .mapIndexed { i, el -> - Page(i, document.location(), el.attr("src")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - override fun getFilterList(): FilterList = FilterList( - Filter.Header("Os filtros são ignorados na busca!"), - TagFilter(getTags()) - ) - - data class Tag(val name: String, val slug: String) { - override fun toString(): String = name - } - - private class TagFilter(tags: Array<Tag>) : Filter.Select<Tag>("Tag", tags) - - private fun getTags(): Array<Tag> = arrayOf( - Tag("-- Selecione --", ""), - Tag("Ahegao", "ahegao"), - Tag("Anal", "anal"), - Tag("Biquíni", "biquini"), - Tag("Chubby", "chubby"), - Tag("Colegial", "colegial"), - Tag("Creampie", "creampie"), - Tag("Dark Skin", "dark-skin"), - Tag("Dupla Penetração", "dupla-penetracao"), - Tag("Espanhola", "espanhola"), - Tag("Exibicionismo", "exibicionismo"), - Tag("Footjob", "footjob"), - Tag("Furry", "furry"), - Tag("Futanari", "futanari"), - Tag("Grupal", "grupal"), - Tag("Incesto", "incesto"), - Tag("Lingerie", "lingerie"), - Tag("MILF", "milf"), - Tag("Maiô", "maio"), - Tag("Masturbação", "masturbacao"), - Tag("Netorare", "netorare"), - Tag("Oral", "oral"), - Tag("Peitinhos", "peitinhos"), - Tag("Preservativo", "preservativo"), - Tag("Professora", "professora"), - Tag("Sex Toys", "sex-toys"), - Tag("Tentáculos", "tentaculos"), - Tag("Yaoi", "yaoi") - ) - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used") - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - } -} diff --git a/src/pt/mundomangakun/AndroidManifest.xml b/src/pt/mundomangakun/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/mundomangakun/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/mundomangakun/build.gradle b/src/pt/mundomangakun/build.gradle deleted file mode 100644 index b4215060a..000000000 --- a/src/pt/mundomangakun/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mundo Mangá-Kun' - pkgNameSuffix = 'pt.mundomangakun' - extClass = '.MundoMangaKun' - extVersionCode = 2 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/mundomangakun/res/mipmap-hdpi/ic_launcher.png b/src/pt/mundomangakun/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1b0bc1417..000000000 Binary files a/src/pt/mundomangakun/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundomangakun/res/mipmap-mdpi/ic_launcher.png b/src/pt/mundomangakun/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8a6db7520..000000000 Binary files a/src/pt/mundomangakun/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundomangakun/res/mipmap-xhdpi/ic_launcher.png b/src/pt/mundomangakun/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index aa3b704d9..000000000 Binary files a/src/pt/mundomangakun/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundomangakun/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/mundomangakun/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 81ee377b4..000000000 Binary files a/src/pt/mundomangakun/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundomangakun/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/mundomangakun/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 5931eb0db..000000000 Binary files a/src/pt/mundomangakun/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/mundomangakun/res/web_hi_res_512.png b/src/pt/mundomangakun/res/web_hi_res_512.png deleted file mode 100644 index a898dcc44..000000000 Binary files a/src/pt/mundomangakun/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/mundomangakun/src/eu/kanade/tachiyomi/extension/pt/mundomangakun/MundoMangaKun.kt b/src/pt/mundomangakun/src/eu/kanade/tachiyomi/extension/pt/mundomangakun/MundoMangaKun.kt deleted file mode 100644 index ab06f481a..000000000 --- a/src/pt/mundomangakun/src/eu/kanade/tachiyomi/extension/pt/mundomangakun/MundoMangaKun.kt +++ /dev/null @@ -1,244 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.mundomangakun - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.lang.UnsupportedOperationException -import java.util.concurrent.TimeUnit - -class MundoMangaKun : ParsedHttpSource() { - - override val name = "Mundo Mangá-Kun" - - override val baseUrl = "https://mundomangakun.com.br" - - override val lang = "pt-BR" - - override val supportsLatest = false - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request { - val refererPath = if (page <= 2) "" else "/leitor-online/${page - 1}" - val newHeaders = headersBuilder() - .set("Referer", baseUrl + refererPath) - .build() - - val pageStr = if (page != 1) "/page/$page" else "" - return GET("$baseUrl/leitor-online$pageStr", newHeaders) - } - - override fun popularMangaSelector(): String = "div.leitor_online_container article.manga_item" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h2.titulo_manga_item").text() - thumbnail_url = element.select("div.container_imagem").attr("style") - .substringAfter("url(") - .substringBefore(");") - setUrlWithoutDomain(element.select("h2.titulo_manga_item a").attr("href")) - } - - override fun popularMangaNextPageSelector() = "div.paginacao a.next.page-numbers" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val refererPage = if (page <= 2) "" else "page/${page - 1}" - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/leitor-online/$refererPage") - .build() - - val pagePath = if (page != 1) "page/$page/" else "" - val url = HttpUrl.parse("$baseUrl/leitor-online/$pagePath")!!.newBuilder() - .addQueryParameter("leitor_titulo_projeto", query) - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - url.addQueryParameter("leitor_genero_projeto", filter.selected.value) - } - is StatusFilter -> { - url.addQueryParameter("leitor_status_projeto", filter.selected.value) - } - is SortFilter -> { - val order = if (filter.state!!.ascending) "ASC" else "DESC" - url.addQueryParameter("leitor_ordem_projeto", order) - } - } - } - - return GET(url.toString(), newHeaders) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select("div.main_container_projeto div.row").first() - val colInfo = infoElement.select("div.col-sm-7").first() - val colImg = infoElement.select("div.col-sm-5").first() - val tableInfo = colInfo.select("table.tabela_info_projeto").first() - - title = colInfo.select("h1.titulo_projeto").text() - author = tableInfo.select("td:contains(Roteiro) + td").text() - artist = tableInfo.select("td:contains(Arte) + td").text() - genre = colImg.select("div.generos a.link_genero").joinToString { it.text() } - status = tableInfo.select("td:contains(Status no Scan) + td").text().toStatus() - description = colInfo.select("h2:contains(Sinopse) + div.conteudo_projeto").text() - thumbnail_url = infoElement.select("div.imagens_projeto_container img").first().attr("src") - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "div.capitulos_leitor_online a.link_capitulo" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.text() - scanlator = this@MundoMangaKun.name - - val link = element.attr("onclick") - .substringAfter("this,") - .substringBeforeLast(")") - .let { JSON_PARSER.parse(it) } - .array - .first { it.obj["tipo"].string == "LEITOR" } - - setUrlWithoutDomain(link.obj["link"].string) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("script:containsData(var paginas)").first().data() - .substringAfter("var paginas=") - .substringBefore(";var") - .let { JSON_PARSER.parse(it) } - .array - .mapIndexed { i, page -> Page(i, document.location(), page.string) } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - override fun getFilterList(): FilterList = FilterList( - GenreFilter(getGenreList()), - StatusFilter(getStatusList()), - SortFilter() - ) - - data class Tag(val text: String, val value: String) { - override fun toString(): String = text - } - - open class TagFilter(name: String, tags: List<Tag>) : Filter.Select<Tag>(name, tags.toTypedArray()) { - val selected: Tag - get() = values[state] - } - - class GenreFilter(genres: List<Tag>) : TagFilter("Gênero", genres) - class StatusFilter(status: List<Tag>) : TagFilter("Status", status) - class SortFilter : Filter.Sort("Ordem", arrayOf("Alfabeticamente"), Selection(0, true)) - - // [...document.querySelectorAll('#leitor_genero_projeto option')] - // .map(x => `Tag("${x.innerText}", "${x.value}")`) - // .join(',\n') - private fun getGenreList() = listOf( - Tag("Selecione…", ""), - Tag("Ação", "59"), - Tag("Adulto", "63"), - Tag("Artes Marciais", "77"), - Tag("Aventura", "65"), - Tag("Comédia", "30"), - Tag("Drama", "17"), - Tag("Ecchi", "74"), - Tag("Escolar", "64"), - Tag("Esportes", "87"), - Tag("Fantasia", "31"), - Tag("Harem", "82"), - Tag("hentai", "525"), - Tag("Histórico", "95"), - Tag("Josei", "553"), - Tag("Mistério", "19"), - Tag("Oneshot", "527"), - Tag("Psicológico", "20"), - Tag("Romance", "75"), - Tag("Sci-fi", "66"), - Tag("Seinen", "61"), - Tag("Serial Killer", "93"), - Tag("Shoujo", "568"), - Tag("Shoujo Ai", "92"), - Tag("Shounen", "67"), - Tag("Slice Of Life", "94"), - Tag("Sobrenatural", "76"), - Tag("Sobrevivência", "90"), - Tag("Super Poderes", "425"), - Tag("Supernatual", "60"), - Tag("Suspense", "520"), - Tag("Terror", "18"), - Tag("Tragédia", "21"), - Tag("Yuri", "526") - ) - - // [...document.querySelectorAll('#leitor_status_projeto option')] - // .map(x => `Tag("${x.innerText}", "${x.value}")`) - // .join(',\n') - private fun getStatusList() = listOf( - Tag("Selecione…", ""), - Tag("Cancelado", "6"), - Tag("Em Andamento", "8"), - Tag("Finalizado", "7"), - Tag("One Shot", "4") - ) - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - private fun String.toStatus(): Int = when (this) { - "Em Andamento" -> SManga.ONGOING - "Finalizado", "One Shot" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private val JSON_PARSER by lazy { JsonParser() } - } -} diff --git a/src/pt/saikaiscan/AndroidManifest.xml b/src/pt/saikaiscan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/saikaiscan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/saikaiscan/build.gradle b/src/pt/saikaiscan/build.gradle deleted file mode 100644 index 72b1412b1..000000000 --- a/src/pt/saikaiscan/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Saikai Scan' - pkgNameSuffix = 'pt.saikaiscan' - extClass = '.SaikaiScan' - extVersionCode = 5 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/saikaiscan/res/mipmap-hdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 46f81b08b..000000000 Binary files a/src/pt/saikaiscan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/saikaiscan/res/mipmap-mdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 30d69e7b7..000000000 Binary files a/src/pt/saikaiscan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/saikaiscan/res/mipmap-xhdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 59aa8670b..000000000 Binary files a/src/pt/saikaiscan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/saikaiscan/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index af09cefb3..000000000 Binary files a/src/pt/saikaiscan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/saikaiscan/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/saikaiscan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7bb38b06b..000000000 Binary files a/src/pt/saikaiscan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/saikaiscan/res/web_hi_res_512.png b/src/pt/saikaiscan/res/web_hi_res_512.png deleted file mode 100644 index 0a77c312c..000000000 Binary files a/src/pt/saikaiscan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt b/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt deleted file mode 100644 index 100a89fc3..000000000 --- a/src/pt/saikaiscan/src/eu/kanade/tachiyomi/extension/pt/saikaiscan/SaikaiScan.kt +++ /dev/null @@ -1,150 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.saikaiscan - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.concurrent.TimeUnit - -class SaikaiScan : ParsedHttpSource() { - - // Hardcode the id because the language wasn't specific. - override val id: Long = 2686610366990303664 - - override val name = "Saikai Scan" - - override val baseUrl = "https://saikaiscan.com.br" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaSelector(): String = "div#menu ul li.has_submenu:eq(3) li a" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.text().substringBeforeLast("(") - url = element.attr("href") - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) - - override fun latestUpdatesSelector(): String = "ul.manhuas li.manhua-item" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - val image = element.select("div.image.lazyload") - val name = element.select("h3") - - title = name.text().substringBeforeLast("(") - thumbnail_url = baseUrl + image.attr("data-src") - url = image.select("a").attr("href") - } - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/busca")!!.newBuilder() - .addQueryParameter("q", query) - - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage { - val results = super.searchMangaParse(response) - val manhuas = results.mangas.filter { it.url.contains("/manhuas/") } - - return MangasPage(manhuas, results.hasNextPage) - } - - override fun searchMangaSelector(): String = "div#news-content ul li" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - val image = element.select("div.image.lazyload") - val name = element.select("h3") - - title = name.text().substringBeforeLast("(") - thumbnail_url = baseUrl + image.attr("data-src") - url = image.select("a").attr("href") - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga { - val projectContent = document.select("div#project-content") - val name = projectContent.select("h2").first() - val cover = projectContent.select("div.cover img.lazyload") - val genres = projectContent.select("div.info:contains(Gênero:)") - val author = projectContent.select("div.info:contains(Autor:)") - val status = projectContent.select("div.info:contains(Status:)") - val summary = projectContent.select("div.summary-text") - - return SManga.create().apply { - title = name.text() - thumbnail_url = baseUrl + cover.attr("data-src") - genre = removeLabel(genres.text()) - this.author = removeLabel(author.text()) - artist = removeLabel(author.text()) - this.status = parseStatus(removeLabel(status.text())) - description = summary.text() - } - } - - private fun parseStatus(status: String) = when { - status.contains("Completo") -> SManga.COMPLETED - status.contains("Em Tradução", true) -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector(): String = "div#project-content div.project-chapters div.chapters ul li a" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - scanlator = "Saikai Scan" - chapter_number = CHAPTER_REGEX.find(element.text())?.groupValues?.get(1)?.toFloatOrNull() ?: -1f - name = element.text() - url = element.attr("href") - } - - override fun pageListParse(document: Document): List<Page> { - val imagesBlock = document.select("div.manhua-slide div.images-block img.lazyload") - - return imagesBlock - .mapIndexed { i, el -> Page(i, "", el.absUrl("src")) } - } - - override fun imageUrlParse(document: Document): String = "" - - private fun removeLabel(info: String) = info.substringAfter(":") - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - private val CHAPTER_REGEX = "Capítulo (\\d+)".toRegex() - } -} diff --git a/src/pt/socialcomics/AndroidManifest.xml b/src/pt/socialcomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/socialcomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/socialcomics/build.gradle b/src/pt/socialcomics/build.gradle deleted file mode 100644 index a4e903355..000000000 --- a/src/pt/socialcomics/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Social Comics' - pkgNameSuffix = 'pt.socialcomics' - extClass = '.SocialComics' - extVersionCode = 2 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/socialcomics/res/mipmap-hdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 841701b3e..000000000 Binary files a/src/pt/socialcomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/socialcomics/res/mipmap-mdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 543d3bf03..000000000 Binary files a/src/pt/socialcomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/socialcomics/res/mipmap-xhdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ee6bc9fcd..000000000 Binary files a/src/pt/socialcomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/socialcomics/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d6f4d5aae..000000000 Binary files a/src/pt/socialcomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/socialcomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c174c0f5f..000000000 Binary files a/src/pt/socialcomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/socialcomics/res/web_hi_res_512.png b/src/pt/socialcomics/res/web_hi_res_512.png deleted file mode 100644 index 360bba46a..000000000 Binary files a/src/pt/socialcomics/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/socialcomics/src/eu/kanade/tachiyomi/extension/pt/socialcomics/SocialComics.kt b/src/pt/socialcomics/src/eu/kanade/tachiyomi/extension/pt/socialcomics/SocialComics.kt deleted file mode 100644 index 1b7d39dc9..000000000 --- a/src/pt/socialcomics/src/eu/kanade/tachiyomi/extension/pt/socialcomics/SocialComics.kt +++ /dev/null @@ -1,440 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.socialcomics - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.text.InputType -import android.widget.Toast -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.jsonObject -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.IOException -import java.lang.UnsupportedOperationException -import java.util.concurrent.TimeUnit -import androidx.preference.EditTextPreference as AndroidXEditTextPreference -import androidx.preference.PreferenceScreen as AndroidXPreferenceScreen - -class SocialComics : HttpSource(), ConfigurableSource { - - override val name = "Social Comics" - - override val baseUrl = "https://socialcomics.com.br" - - override val lang = "pt-BR" - - override val supportsLatest = false - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .addInterceptor(::authIntercept) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/home") - .add("User-Agent", USER_AGENT) - - private fun sourceHeadersBuilder(): Headers.Builder = headersBuilder() - .add("Accept", ACCEPT_JSON) - .add("Origin", baseUrl) - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private val email: String - get() = preferences.getString(EMAIL_PREF_KEY, "")!! - - private val password: String - get() = preferences.getString(PASSWORD_PREF_KEY, "")!! - - private var apiToken: String - get() = preferences.getString(API_TOKEN_PREF_KEY, "")!! - set(value) { preferences.edit().putString(API_TOKEN_PREF_KEY, value).apply() } - - private var userHash: String = "" - - override fun popularMangaRequest(page: Int): Request { - val newHeaders = sourceHeadersBuilder() - .add("Referer", "$baseUrl/home") - .build() - - return GET("$SERVICE_URL/api/mobile/home/list/web", newHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = response.asJson().obj - - val freeTrack = result["tracks"].array - .firstOrNull { - it.obj["name"].string.contains("Grátis") - } - - if (freeTrack != null) { - val popularMangas = freeTrack.obj["comics"]["items"].array - .map { popularMangaItemParse(it.obj) } - - return MangasPage(popularMangas, hasNextPage = false) - } - - return MangasPage(emptyList(), hasNextPage = false) - } - - private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].string - thumbnail_url = obj["thumb"].string - url = "/quadrinho/${obj["hash"].string}?monetize=${obj["monetize"].int}" - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = sourceHeadersBuilder() - .add("Referer", "$baseUrl/pesquisa") - .build() - - val endpointUrl = HttpUrl.parse("$SERVICE_URL/api/mobile/search/")!!.newBuilder() - .addEncodedPathSegment(query) - .toString() - - return GET(endpointUrl, newHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = response.asJson().obj - - val searchMangas = result["comics"]["items"].array - .filter { it.obj["monetize"].int == 0 } - .map { searchMangaItemParse(it.obj) } - - return MangasPage(searchMangas, false) - } - - private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].string - thumbnail_url = obj["thumb"].string - url = "/quadrinho/${obj["hash"].string}?monetize=${obj["monetize"].int}" - } - - override fun mangaDetailsRequest(manga: SManga): Request { - val hash = manga.url - .substringAfterLast("/") - .substringBefore("?") - - val newHeaders = sourceHeadersBuilder() - .add("Referer", "$baseUrl/quadrinho/$hash") - .build() - - return GET("$SERVICE_URL/api/mobile/comic/detail/$hash", newHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { - val apiResult = response.asJson().obj - - title = apiResult["name"].string - author = apiResult["script"].string - description = apiResult["description"].string + - "\n\nEdição: #${apiResult["edition"].int}" - genre = apiResult["tags"].array - .joinToString { it.obj["name"].string } - status = SManga.COMPLETED - thumbnail_url = apiResult["thumb"].string - - if (apiResult["art"].string != "Não Informado") { - artist = apiResult["art"].string - } - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - if (manga.url.substringAfter("?monetize=") == "1") { - return Observable.error(Exception(ERROR_PAID_CONTENT)) - } - - return super.fetchChapterList(manga) - } - - override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val apiResult = response.asJson().obj - - val chapter = SChapter.create().apply { - name = "Quadrinho" - scanlator = apiResult["publisher"]["name"].string - url = "/leitor/${apiResult["hash"].string}" - } - - return listOf(chapter) - } - - override fun pageListRequest(chapter: SChapter): Request { - val newHeaders = sourceHeadersBuilder() - .set("Referer", baseUrl + chapter.url) - .build() - - val hash = chapter.url.substringAfterLast("/") - - return GET("$SERVICE_URL/api/mobile/comic/pages/$hash", newHeaders) - } - - override fun pageListParse(response: Response): List<Page> { - val apiResult = response.asJson().obj - val hash = apiResult["hash"].string - val comicUrl = "$baseUrl/leitor/$hash" - - return apiResult["pages"].array - .mapIndexed { i, el -> Page(i, comicUrl, el.obj["page"].string) } - } - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .add("Referer", page.url) - .removeAll("Origin") - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val emailPref = EditTextPreference(screen.context).apply { - key = EMAIL_PREF_KEY - title = EMAIL_PREF_TITLE - setDefaultValue("") - summary = EMAIL_PREF_SUMMARY - dialogTitle = EMAIL_PREF_TITLE - - setOnPreferenceChangeListener { _, newValue -> - val res = preferences.edit() - .putString(EMAIL_PREF_KEY, newValue as String) - .putString(API_TOKEN_PREF_KEY, "") - .commit() - - Toast.makeText(screen.context, TOAST_RESTART_TO_APPLY, Toast.LENGTH_LONG).show() - res - } - } - val passwordPref = EditTextPreference(screen.context).apply { - key = PASSWORD_PREF_KEY - title = PASSWORD_PREF_TITLE - setDefaultValue("") - summary = PASSWORD_PREF_SUMMARY - dialogTitle = PASSWORD_PREF_TITLE - - setOnPreferenceChangeListener { _, newValue -> - val res = preferences.edit() - .putString(PASSWORD_PREF_KEY, newValue as String) - .putString(API_TOKEN_PREF_KEY, "") - .commit() - - Toast.makeText(screen.context, TOAST_RESTART_TO_APPLY, Toast.LENGTH_LONG).show() - res - } - } - - screen.addPreference(emailPref) - screen.addPreference(passwordPref) - } - - override fun setupPreferenceScreen(screen: AndroidXPreferenceScreen) { - val emailPref = AndroidXEditTextPreference(screen.context).apply { - key = EMAIL_PREF_KEY - title = EMAIL_PREF_TITLE - setDefaultValue("") - summary = EMAIL_PREF_SUMMARY - dialogTitle = EMAIL_PREF_TITLE - - setOnPreferenceChangeListener { _, newValue -> - val res = preferences.edit() - .putString(EMAIL_PREF_KEY, newValue as String) - .putString(API_TOKEN_PREF_KEY, "") - .commit() - - Toast.makeText(screen.context, TOAST_RESTART_TO_APPLY, Toast.LENGTH_LONG).show() - res - } - } - val passwordPref = AndroidXEditTextPreference(screen.context).apply { - key = PASSWORD_PREF_KEY - title = PASSWORD_PREF_TITLE - setDefaultValue("") - summary = PASSWORD_PREF_SUMMARY - dialogTitle = PASSWORD_PREF_TITLE - - setOnBindEditTextListener { - it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - - setOnPreferenceChangeListener { _, newValue -> - val res = preferences.edit() - .putString(PASSWORD_PREF_KEY, newValue as String) - .putString(API_TOKEN_PREF_KEY, "") - .commit() - - Toast.makeText(screen.context, TOAST_RESTART_TO_APPLY, Toast.LENGTH_LONG).show() - res - } - } - - screen.addPreference(emailPref) - screen.addPreference(passwordPref) - } - - override fun latestUpdatesRequest(page: Int): Request = - throw UnsupportedOperationException("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = - throw UnsupportedOperationException("Not used") - - private fun authIntercept(chain: Interceptor.Chain): Response { - val request = chain.request() - - if (request.url().toString().contains(THUMBNAIL_CDN)) { - return chain.proceed(request) - } - - if (email.isEmpty() || password.isEmpty()) { - throw IOException(ERROR_CREDENTIALS_MISSING) - } - - // Always do logout at the first time to reset the token. - if (userHash.isEmpty() && apiToken.isNotEmpty()) { - doLogout(chain) - } - - if (apiToken.isEmpty()) { - doLogin(chain) - } - - val authRequest = request.newBuilder() - .addHeader("Authorization", "Bearer $apiToken") - .build() - - return chain.proceed(authRequest) - } - - private fun doLogin(chain: Interceptor.Chain) { - val loginPayload = jsonObject( - "device" to jsonObject( - "device" to USER_AGENT, - "device_id" to "window.navigator.userAgent.replace(/\\d+/g, '')", - "platform" to "web" - ), - "email" to email, - "facebook_id" to null, - "password" to password - ) - - val loginBody = RequestBody.create(JSON_MEDIA_TYPE, loginPayload.toString()) - - val loginHeaders = sourceHeadersBuilder() - .add("Content-Type", loginBody.contentType().toString()) - .add("Content-Length", loginBody.contentLength().toString()) - .add("Referer", "$baseUrl/login") - .build() - - val loginRequest = POST("$SERVICE_URL/api/mobile/login", loginHeaders, loginBody) - val response = chain.proceed(loginRequest) - - if (response.code() != 200) { - throw IOException(ERROR_CANNOT_LOGIN) - } - - val apiResult = response.asJson().obj - - apiToken = apiResult["api_token"].string - userHash = apiResult["hash"].string - - response.close() - } - - private fun doLogout(chain: Interceptor.Chain) { - val logoutPayload = jsonObject( - "device" to jsonObject( - "device_id" to "window.navigator.userAgent.replace(/\\d+/g, '')" - ), - "hash_master" to userHash - ) - - val logoutBody = RequestBody.create(JSON_MEDIA_TYPE, logoutPayload.toString()) - - val logoutHeaders = sourceHeadersBuilder() - .add("Content-Type", logoutBody.contentType().toString()) - .add("Content-Length", logoutBody.contentLength().toString()) - .build() - - val logoutRequest = POST("$SERVICE_URL/api/mobile/logout", logoutHeaders, logoutBody) - val response = chain.proceed(logoutRequest) - - if (response.code() != 200) { - throw IOException(ERROR_CANNOT_RENEW_TOKEN) - } - - apiToken = "" - userHash = "" - - response.close() - } - - private fun Response.asJson(): JsonElement = JSON_PARSER.parse(body()!!.string()) - - companion object { - private const val SERVICE_URL = "https://service.socialcomics.com.br" - private const val THUMBNAIL_CDN = "amazonaws.com" - - private const val ACCEPT_JSON = "application/json, text/plain, */*" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/*,*/*;q=0.8" - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private val JSON_PARSER by lazy { JsonParser() } - - private const val EMAIL_PREF_KEY = "email" - private const val EMAIL_PREF_TITLE = "E-mail" - private const val EMAIL_PREF_SUMMARY = "Defina aqui o seu e-mail para o login." - - private const val PASSWORD_PREF_KEY = "password" - private const val PASSWORD_PREF_TITLE = "Senha" - private const val PASSWORD_PREF_SUMMARY = "Defina aqui a sua senha para o login." - - private const val API_TOKEN_PREF_KEY = "api_token" - - private const val ERROR_CANNOT_LOGIN = "Não foi possível realizar o login. Verifique suas informações." - private const val ERROR_CANNOT_RENEW_TOKEN = "Não foi possível renovar o token de autenticação." - private const val ERROR_CREDENTIALS_MISSING = "Informações de login incompletas. Revise as configurações." - private const val ERROR_PAID_CONTENT = "O quadrinho não está disponível no pacote gratuito." - - private const val TOAST_RESTART_TO_APPLY = "Reinicie o Tachiyomi para aplicar as novas configurações." - - private val JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8") - } -} diff --git a/src/pt/taosect/AndroidManifest.xml b/src/pt/taosect/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/taosect/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/taosect/build.gradle b/src/pt/taosect/build.gradle deleted file mode 100644 index 7ed534858..000000000 --- a/src/pt/taosect/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Tao Sect' - pkgNameSuffix = 'pt.taosect' - extClass = '.TaoSect' - extVersionCode = 2 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/taosect/res/mipmap-hdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c0a27c960..000000000 Binary files a/src/pt/taosect/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/taosect/res/mipmap-mdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7640239d1..000000000 Binary files a/src/pt/taosect/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/taosect/res/mipmap-xhdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d642ce103..000000000 Binary files a/src/pt/taosect/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/taosect/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a2f794d2a..000000000 Binary files a/src/pt/taosect/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/taosect/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/taosect/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index dbd743759..000000000 Binary files a/src/pt/taosect/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/taosect/res/web_hi_res_512.png b/src/pt/taosect/res/web_hi_res_512.png deleted file mode 100644 index e3052b9e8..000000000 Binary files a/src/pt/taosect/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/taosect/src/eu/kanade/tachiyomi/extension/pt/taosect/TaoSect.kt b/src/pt/taosect/src/eu/kanade/tachiyomi/extension/pt/taosect/TaoSect.kt deleted file mode 100644 index ad7588f84..000000000 --- a/src/pt/taosect/src/eu/kanade/tachiyomi/extension/pt/taosect/TaoSect.kt +++ /dev/null @@ -1,276 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.taosect - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.lang.UnsupportedOperationException -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class TaoSect : ParsedHttpSource() { - - override val name = "Tao Sect" - - override val baseUrl = "https://taosect.com" - - override val lang = "pt-BR" - - override val supportsLatest = false - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/situacao/ativos", headers) - } - - override fun popularMangaSelector(): String = "div.post-list article.post-projeto" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - val sibling = element.nextElementSibling()!! - - title = sibling.select("h3.titulo-popover").text()!! - thumbnail_url = element.select("div.post-projeto-background")!! - .attr("style") - .substringAfter("url(") - .substringBefore(");") - setUrlWithoutDomain(element.select("a[title]").first()!!.attr("href")) - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/pesquisar-leitor")!!.newBuilder() - .addQueryParameter("leitor_titulo_projeto", query) - - filters.forEach { filter -> - when (filter) { - is CountryFilter -> { - filter.state - .filter { it.state } - .forEach { url.addQueryParameter("leitor_pais_projeto[]", it.id) } - } - is StatusFilter -> { - filter.state - .filter { it.state } - .forEach { url.addQueryParameter("leitor_status_projeto[]", it.id) } - } - is GenreFilter -> { - filter.state.forEach { genre -> - if (genre.isIncluded()) { - url.addQueryParameter("leitor_tem_genero_projeto[]", genre.id) - } else if (genre.isExcluded()) { - url.addQueryParameter("leitor_n_tem_genero_projeto[]", genre.id) - } - } - } - is SortFilter -> { - val sort = when { - filter.state == null -> "a_z" - filter.state!!.ascending -> SORT_LIST[filter.state!!.index].first - else -> SORT_LIST[filter.state!!.index].second - } - - url.addQueryParameter("leitor_ordem_projeto", sort) - } - } - } - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "article.manga_item" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("h2.titulo_manga_item a")!!.text() - thumbnail_url = element.select("div.container_imagem")!! - .attr("style") - .substringAfter("url(") - .substringBefore(");") - setUrlWithoutDomain(element.select("h2.titulo_manga_item a")!!.attr("href")) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val header = document.select("div.cabelho-projeto").first()!! - - title = header.select("h1.titulo-projeto")!!.text() - author = header.select("table.tabela-projeto tr:eq(1) td:eq(1)")!!.text() - artist = header.select("table.tabela-projeto tr:eq(0) td:eq(1)")!!.text() - genre = header.select("table.tabela-projeto tr:eq(10) a").joinToString { it.text() } - status = header.select("table.tabela-projeto tr:eq(4) td:eq(1)")!!.text().toStatus() - description = header.select("table.tabela-projeto tr:eq(9) p")!!.text() - thumbnail_url = header.select("div.imagens-projeto img[alt]").first()!!.attr("src") - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "table.tabela-volumes tr:not(:first-child)" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.select("td[align='left'] a")!!.text() - scanlator = this@TaoSect.name - date_upload = DATE_FORMATTER.tryParseTime(element.select("td[align='right']")!!.text()) - setUrlWithoutDomain(element.select("td[align='left'] a")!!.attr("href")) - } - - override fun pageListRequest(chapter: SChapter): Request { - val projectUrl = "$baseUrl/" + chapter.url - .substringAfter("online/") - .substringBefore("/") - - val newHeaders = headersBuilder() - .set("Referer", projectUrl) - .build() - - return GET(baseUrl + chapter.url, newHeaders) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("script:containsData(var paginas)").first()!! - .data() - .substringAfter("var paginas = [") - .substringBefore("];") - .split(",") - .mapIndexed { i, url -> - Page(i, document.location(), url.replace("\"", "")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - override fun getFilterList(): FilterList = FilterList( - CountryFilter(getCountryList()), - StatusFilter(getStatusList()), - GenreFilter(getGenreList()), - SortFilter() - ) - - private class Tag(val id: String, name: String) : Filter.CheckBox(name) - - private class Genre(val id: String, name: String) : Filter.TriState(name) - - private class CountryFilter(countries: List<Tag>) : Filter.Group<Tag>("País", countries) - - private class StatusFilter(status: List<Tag>) : Filter.Group<Tag>("Status", status) - - private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Gêneros", genres) - - private class SortFilter : Filter.Sort( - "Ordem", - SORT_LIST.map { it.third }.toTypedArray(), - Selection(0, true) - ) - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - private fun SimpleDateFormat.tryParseTime(date: String): Long { - return try { - parse(date)!!.time - } catch (e: ParseException) { - 0L - } - } - - private fun String.toStatus() = when { - contains("Ativo") -> SManga.ONGOING - contains("Finalizado") || contains("Oneshots") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun getCountryList(): List<Tag> = listOf( - Tag("59", "China"), - Tag("60", "Coréia do Sul"), - Tag("13", "Japão") - ) - - private fun getStatusList(): List<Tag> = listOf( - Tag("3", "Ativo"), - Tag("5", "Cancelado"), - Tag("4", "Finalizado"), - Tag("6", "One-shot") - ) - - // [...document.querySelectorAll("#leitor_tem_genero_projeto option")] - // .map(el => `Genre("${el.getAttribute("value")}", "${el.innerText}")`).join(',\n') - private fun getGenreList(): List<Genre> = listOf( - Genre("31", "4Koma"), - Genre("24", "Ação"), - Genre("84", "Adulto"), - Genre("21", "Artes Marciais"), - Genre("25", "Aventura"), - Genre("26", "Comédia"), - Genre("66", "Culinária"), - Genre("78", "Doujinshi"), - Genre("22", "Drama"), - Genre("12", "Ecchi"), - Genre("30", "Escolar"), - Genre("76", "Esporte"), - Genre("23", "Fantasia"), - Genre("29", "Harém"), - Genre("75", "Histórico"), - Genre("83", "Horror"), - Genre("18", "Isekai"), - Genre("20", "Light Novel"), - Genre("61", "Manhua"), - Genre("56", "Psicológico"), - Genre("7", "Romance"), - Genre("27", "Sci-fi"), - Genre("28", "Seinen"), - Genre("55", "Shoujo"), - Genre("54", "Shounen"), - Genre("19", "Slice of life"), - Genre("17", "Sobrenatural"), - Genre("57", "Tragédia"), - Genre("62", "Webtoon") - ) - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private val DATE_FORMATTER by lazy { SimpleDateFormat("(dd/MM/yyyy)", Locale.ENGLISH) } - - private val SORT_LIST = listOf( - Triple("a_z", "z_a", "Nome"), - Triple("dt_asc", "date-desc", "Data") - ) - } -} diff --git a/src/pt/tsukimangas/AndroidManifest.xml b/src/pt/tsukimangas/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/tsukimangas/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/tsukimangas/build.gradle b/src/pt/tsukimangas/build.gradle deleted file mode 100644 index 66261aab4..000000000 --- a/src/pt/tsukimangas/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Tsuki Mangás' - pkgNameSuffix = 'pt.tsukimangas' - extClass = '.TsukiMangas' - extVersionCode = 13 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" - diff --git a/src/pt/tsukimangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/tsukimangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6f76b7f4d..000000000 Binary files a/src/pt/tsukimangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/tsukimangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/tsukimangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1ecc240e9..000000000 Binary files a/src/pt/tsukimangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/tsukimangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/tsukimangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 590cfe9b5..000000000 Binary files a/src/pt/tsukimangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/tsukimangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/tsukimangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6dd850a9d..000000000 Binary files a/src/pt/tsukimangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/tsukimangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/tsukimangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index cc9dc086a..000000000 Binary files a/src/pt/tsukimangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/tsukimangas/res/web_hi_res_512.png b/src/pt/tsukimangas/res/web_hi_res_512.png deleted file mode 100644 index 8f5f3f51d..000000000 Binary files a/src/pt/tsukimangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangas.kt b/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangas.kt deleted file mode 100644 index cba005089..000000000 --- a/src/pt/tsukimangas/src/eu/kanade/tachiyomi/extension/pt/tsukimangas/TsukiMangas.kt +++ /dev/null @@ -1,361 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.tsukimangas - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class TsukiMangas : HttpSource() { - - override val name = "Tsuki Mangás" - - override val baseUrl = "https://tsukimangas.com" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept", ACCEPT) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("User-Agent", USER_AGENT) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/api/v2/home", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = response.asJson().obj - - val popularMangas = result["slides"].array - .map { popularMangaItemParse(it.obj) } - - return MangasPage(popularMangas, false) - } - - private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["title"].string - thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?") - url = "/obra/${obj["id"].int}/${obj["url"].string}" - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/api/v2/home/lastests?page=$page", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.asJson().obj - - val latestMangas = result["data"].array - .map { latestMangaItemParse(it.obj) } - - val hasNextPage = result["page"].int < result["lastPage"].int - return MangasPage(latestMangas, hasNextPage) - } - - private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["title"].string - thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?") - url = "/obra/${obj["id"].int}/${obj["url"].string}" - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/lista-completa") - .build() - - val url = HttpUrl.parse("$baseUrl/api/v2/mangas?page=$page")!!.newBuilder() - url.addQueryParameter("title", query) - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - filter.state - .filter { it.state } - .forEach { url.addQueryParameter("genres", it.name) } - } - - is TypeFilter -> { - if (filter.state > 0) { - url.addQueryParameter("format", filter.state.toString()) - } - } - - is AdultFilter -> { - if (filter.state == Filter.TriState.STATE_INCLUDE) { - url.addQueryParameter("adult_content", "1") - } else if (filter.state == Filter.TriState.STATE_EXCLUDE) { - url.addQueryParameter("adult_content", "false") - } - } - } - } - - return GET(url.toString(), newHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = response.asJson().obj - - val searchResults = result["data"].array - .map { searchMangaItemParse(it.obj) } - - val hasNextPage = result["page"].int < result["lastPage"].int - - return MangasPage(searchResults, hasNextPage) - } - - private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["title"].string - thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?") - url = "/obra/${obj["id"].int}/${obj["url"].string}" - } - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(mangaDetailsApiRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun mangaDetailsApiRequest(manga: SManga): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl + manga.url) - .build() - - val mangaId = manga.url.substringAfter("obra/").substringBefore("/") - - return GET("$baseUrl/api/v2/mangas/$mangaId", newHeaders) - } - - override fun mangaDetailsRequest(manga: SManga): Request { - val newHeaders = headersBuilder() - .removeAll("Accept") - .build() - - return GET(baseUrl + manga.url, newHeaders) - } - - override fun mangaDetailsParse(response: Response): SManga { - val result = response.asJson().obj - - return SManga.create().apply { - title = result["title"].string - thumbnail_url = baseUrl + "/imgs/" + result["poster"].string.substringBefore("?") - description = result["synopsis"].nullString.orEmpty() - status = result["status"].nullString.orEmpty().toStatus() - author = result["author"].nullString.orEmpty() - artist = result["artist"].nullString.orEmpty() - genre = result["genres"].array.joinToString { it.obj["genre"].string } - } - } - - override fun chapterListRequest(manga: SManga): Request { - val mangaId = manga.url.substringAfter("obra/").substringBefore("/") - - val newHeaders = headersBuilder() - .set("Referer", baseUrl + manga.url) - .build() - - return GET("$baseUrl/api/v2/chapters/$mangaId/all", newHeaders) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val mangaUrl = response.request().header("Referer")!!.substringAfter(baseUrl) - - return response.asJson().array - .flatMap { chapterListItemParse(it.obj, mangaUrl) } - .reversed() - } - - private fun chapterListItemParse(obj: JsonObject, mangaUrl: String): List<SChapter> { - val mangaId = mangaUrl.substringAfter("obra/").substringBefore("/") - val mangaSlug = mangaUrl.substringAfterLast("/") - - return obj["versions"].array.map { version -> - SChapter.create().apply { - name = "Cap. " + obj["number"].string + - (if (!obj["title"].nullString.isNullOrEmpty()) " - " + obj["title"].string else "") - chapter_number = obj["number"].string.toFloatOrNull() ?: -1f - scanlator = version.obj["scans"].array - .sortedBy { it.obj["scan"].obj["name"].string } - .joinToString { it.obj["scan"].obj["name"].string } - date_upload = version.obj["created_at"].string.substringBefore(" ").toDate() - url = "/leitor/$mangaId/${version.obj["id"].int}/$mangaSlug/${obj["number"].string}" - } - } - } - - override fun pageListRequest(chapter: SChapter): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl + chapter.url) - .build() - - val mangaId = chapter.url - .substringAfter("leitor/") - .substringBefore("/") - val versionId = chapter.url - .substringAfter("$mangaId/") - .substringBefore("/") - - return GET("$baseUrl/api/v2/chapter/versions/$versionId", newHeaders) - } - - override fun pageListParse(response: Response): List<Page> { - val result = response.asJson().obj - - return result["pages"].array.mapIndexed { i, page -> - val cdnUrl = "https://cdn${page.obj["server"].string}.tsukimangas.com" - Page(i, "$baseUrl/", cdnUrl + page.obj["url"].string) - } - } - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlParse(response: Response): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Accept-Language", ACCEPT_LANGUAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private class Genre(name: String) : Filter.CheckBox(name) - - private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Gêneros", genres) - - private class TypeFilter(types: List<String>) : Filter.Select<String>("Formato", types.toTypedArray()) - - private class AdultFilter : Filter.TriState("Conteúdo adulto") - - override fun getFilterList(): FilterList = FilterList( - GenreFilter(getGenreList()), - TypeFilter(getSerieTypes()), - AdultFilter() - ) - - // [...document.querySelectorAll(".multiselect:first-of-type .multiselect__element span span")] - // .map(i => `Genre("${i.innerHTML}")`).join(",\n") - private fun getGenreList(): List<Genre> = listOf( - Genre("4-koma"), - Genre("Adulto"), - Genre("Artes Marciais"), - Genre("Aventura"), - Genre("Ação"), - Genre("Bender"), - Genre("Comédia"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Esporte"), - Genre("Fantasia"), - Genre("Ficção"), - Genre("Gastronomia"), - Genre("Gender"), - Genre("Guerra"), - Genre("Harém"), - Genre("Histórico"), - Genre("Horror"), - Genre("Isekai"), - Genre("Josei"), - Genre("Magia"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Mecha"), - Genre("Medicina"), - Genre("Militar"), - Genre("Mistério"), - Genre("Musical"), - Genre("One-Shot"), - Genre("Psicológico"), - Genre("Romance"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Sobrenatural"), - Genre("Super Poderes"), - Genre("Suspense"), - Genre("Terror"), - Genre("Thriller"), - Genre("Tragédia"), - Genre("Vida Escolar"), - Genre("Webtoon"), - Genre("Yaoi"), - Genre("Yuri"), - Genre("Zumbi") - ) - - private fun getSerieTypes(): List<String> = listOf( - "Todos", - "Mangá", - "Manhwa", - "Manhua", - "Novel" - ) - - private fun String.toDate(): Long { - return try { - DATE_FORMATTER.parse(this)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.toStatus() = when { - contains("Ativo") -> SManga.ONGOING - contains("Completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun Response.asJson(): JsonElement = JSON_PARSER.parse(body()!!.string()) - - companion object { - private const val ACCEPT = "application/json, text/plain, */*" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/*,*/*;q=0.8" - private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" - // By request of site owner. Detailed at Issue #4912 (in Portuguese). - private val USER_AGENT = "Tachiyomi " + System.getProperty("http.agent") - - private val JSON_PARSER by lazy { JsonParser() } - - private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) } - } -} diff --git a/src/pt/unionmangas/AndroidManifest.xml b/src/pt/unionmangas/AndroidManifest.xml deleted file mode 100644 index f7d3a86a3..000000000 --- a/src/pt/unionmangas/AndroidManifest.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".pt.unionmangas.UnionMangasUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="unionleitor.top" - android:pathPattern="/perfil-manga/..*" - android:scheme="https" /> - <data - android:host="unionleitor.top" - android:pathPattern="/pagina-manga/..*" - android:scheme="https" /> - <data - android:host="unionmangas.top" - android:pathPattern="/perfil-manga/..*" - android:scheme="http" /> - <data - android:host="unionmangas.top" - android:pathPattern="/pagina-manga/..*" - android:scheme="http" /> - <data - android:host="unionmangas.top" - android:pathPattern="/perfil-manga/..*" - android:scheme="https" /> - <data - android:host="unionmangas.top" - android:pathPattern="/pagina-manga/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/pt/unionmangas/build.gradle b/src/pt/unionmangas/build.gradle deleted file mode 100644 index 29edc5641..000000000 --- a/src/pt/unionmangas/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Union Mangás' - pkgNameSuffix = 'pt.unionmangas' - extClass = '.UnionMangas' - extVersionCode = 22 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/unionmangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/unionmangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 1b71ad135..000000000 Binary files a/src/pt/unionmangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/unionmangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/unionmangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5f08eb5c0..000000000 Binary files a/src/pt/unionmangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/unionmangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/unionmangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f70961fd0..000000000 Binary files a/src/pt/unionmangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/unionmangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/unionmangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ff725b3a3..000000000 Binary files a/src/pt/unionmangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/unionmangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/unionmangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e4f74dade..000000000 Binary files a/src/pt/unionmangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/unionmangas/res/web_hi_res_512.png b/src/pt/unionmangas/res/web_hi_res_512.png deleted file mode 100644 index fe53ed3ad..000000000 Binary files a/src/pt/unionmangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangas.kt b/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangas.kt deleted file mode 100644 index 881fe0131..000000000 --- a/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangas.kt +++ /dev/null @@ -1,280 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.unionmangas - -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.nullObj -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class UnionMangas : ParsedHttpSource() { - - // Hardcode the id because the language wasn't specific. - override val id: Long = 6931383302802153355 - - override val name = "Union Mangás" - - override val baseUrl = "https://unionmangas.top" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(3, TimeUnit.MINUTES) - .readTimeout(3, TimeUnit.MINUTES) - .writeTimeout(3, TimeUnit.MINUTES) - .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Accept", ACCEPT) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("Referer", "$baseUrl/home") - - override fun popularMangaRequest(page: Int): Request { - val listPath = if (page == 1) "" else "/visualizacoes/${page - 1}" - val newHeaders = headersBuilder() - .set("Referer", "$baseUrl/lista-mangas$listPath") - .build() - - val pageStr = if (page != 1) "/$page" else "" - return GET("$baseUrl/lista-mangas/visualizacoes$pageStr", newHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val results = super.popularMangaParse(response) - - if (results.mangas.isEmpty()) { - throw Exception(BLOCK_MESSAGE) - } - - return results - } - - override fun popularMangaSelector(): String = "div.col-md-3.col-xs-6:has(img.img-thumbnail)" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("div[id^=bloco-tooltip] > b").first().text().withoutLanguage() - thumbnail_url = element.select("a img").first()?.attr("src") - setUrlWithoutDomain(element.select("a").last().attr("href")) - } - - override fun popularMangaNextPageSelector() = ".pagination li:contains(Next)" - - override fun latestUpdatesRequest(page: Int): Request { - val form = FormBody.Builder() - .add("pagina", page.toString()) - .build() - - val newHeaders = headersBuilder() - .add("Content-Type", form.contentType().toString()) - .add("Content-Length", form.contentLength().toString()) - .add("Origin", baseUrl) - .add("X-Requested-With", "XMLHttpRequest") - .set("Accept", "*/*") - .build() - - return POST("$baseUrl/assets/noticias.php", newHeaders, form) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val results = super.latestUpdatesParse(response) - - if (results.mangas.isEmpty()) { - throw Exception(BLOCK_MESSAGE) - } - - return results - } - - override fun latestUpdatesSelector() = "div.row[style] div.col-md-12[style]" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - val infoElement = element.select("a.link-titulo") - - title = infoElement.last().text().withoutLanguage() - thumbnail_url = infoElement.first()?.select("img")?.attr("src") - setUrlWithoutDomain(infoElement.last().attr("href")) - } - - override fun latestUpdatesNextPageSelector() = "div#linha-botao-mais" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.startsWith(PREFIX_SLUG_SEARCH)) { - val slug = query.removePrefix(PREFIX_SLUG_SEARCH) - return GET("$baseUrl/pagina-manga/$slug", headers) - } - - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_JSON) - .add("X-Requested-With", "XMLHttpRequest") - .build() - - val url = HttpUrl.parse("$baseUrl/assets/busca.php")!!.newBuilder() - .addQueryParameter("titulo", query) - - return GET(url.toString(), newHeaders) - } - - override fun searchMangaParse(response: Response): MangasPage { - val requestUrl = response.request().url().toString() - - if (requestUrl.contains("pagina-manga")) { - val slug = requestUrl.substringAfter("pagina-manga/") - val manga = mangaDetailsParse(response) - .apply { url = "/pagina-manga/$slug" } - return MangasPage(listOf(manga), false) - } - - val result = response.asJson().nullObj - ?: throw Exception(BLOCK_MESSAGE) - - val mangas = result["items"].array - .map { searchMangaFromObject(it.obj) } - - return MangasPage(mangas, false) - } - - private fun searchMangaFromObject(obj: JsonObject): SManga = SManga.create().apply { - title = obj["titulo"].string.withoutLanguage() - thumbnail_url = obj["imagem"].string - setUrlWithoutDomain("$baseUrl/pagina-manga/${obj["url"].string}") - } - - override fun mangaDetailsRequest(manga: SManga): Request { - // Map the mangas that are already in library with the old URL to the new one. - val newUrl = manga.url.replace("perfil-manga", "pagina-manga") - return GET(baseUrl + newUrl, headers) - } - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.select("div.perfil-manga").firstOrNull() - ?: throw Exception(BLOCK_MESSAGE) - val rowInfo = infoElement.select("div.row:eq(2)").first() - - title = infoElement.select("h2").first().text().withoutLanguage() - author = rowInfo.select("div.col-md-8:eq(4)").first().textWithoutLabel() - artist = rowInfo.select("div.col-md-8:eq(5)").first().textWithoutLabel() - genre = rowInfo.select("div.col-md-8:eq(3)").first().textWithoutLabel() - status = rowInfo.select("div.col-md-8:eq(6)").first().text().toStatus() - description = rowInfo.select("div.col-md-8:eq(8)").first().text() - thumbnail_url = infoElement.select(".img-thumbnail").first().attr("src") - } - - override fun chapterListParse(response: Response): List<SChapter> { - val results = super.chapterListParse(response) - - if (results.isEmpty()) { - throw Exception(BLOCK_MESSAGE) - } - - return results - } - - override fun chapterListSelector() = "div.row.capitulos" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val firstColumn = element.select("div.col-md-6:eq(0)").first()!! - val secondColumn = element.select("div.col-md-6:eq(1)").firstOrNull() - - name = firstColumn.select("a").first().text() - scanlator = secondColumn?.select("a")?.joinToString { it.text() } - date_upload = firstColumn.select("span").last()!!.text().toDate() - - // For some reason, setUrlWithoutDomain does not work when the url have spaces. - val absoluteUrlFixed = firstColumn.select("a").first() - .attr("href") - .replace(" ", "%20") - setUrlWithoutDomain(absoluteUrlFixed) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("img.img-responsive.img-manga") - .filter { it.attr("src").contains("/leitor/") } - .mapIndexed { i, element -> - Page(i, document.location(), element.absUrl("src")) - } - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Accept", ACCEPT_IMAGE) - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - override fun searchMangaSelector() = throw Exception("This method should not be called!") - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("This method should not be called!") - - override fun searchMangaNextPageSelector() = throw Exception("This method should not be called!") - - private fun String.toDate(): Long { - return try { - DATE_FORMATTER.parse(this)?.time ?: 0L - } catch (e: ParseException) { - 0L - } - } - - private fun String.toStatus(): Int = when { - contains("Ativo") -> SManga.ONGOING - contains("Completo") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private fun String.withoutLanguage(): String = - replace("(pt-br)", "", true).trim() - - private fun Element.textWithoutLabel(): String = text()!!.substringAfter(":").trim() - - private fun Response.asJson(): JsonElement = JSON_PARSER.parse(body()!!.string()) - - companion object { - private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + - "image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" - private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" - private const val ACCEPT_JSON = "application/json, text/javascript, */*; q=0.01" - private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private const val BLOCK_MESSAGE = "O site está bloqueando o Tachiyomi. " + - "Migre para outra fonte caso o problema persistir." - - private val JSON_PARSER by lazy { JsonParser() } - - private val DATE_FORMATTER by lazy { - SimpleDateFormat("(dd/MM/yyyy)", Locale.ENGLISH) - } - - const val PREFIX_SLUG_SEARCH = "slug:" - } -} diff --git a/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangasUrlActivity.kt b/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangasUrlActivity.kt deleted file mode 100644 index eebd580d3..000000000 --- a/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangasUrlActivity.kt +++ /dev/null @@ -1,38 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.unionmangas - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://unionleitor.top/perfil-manga/xxxxxx intents - * and redirects them to the main Tachiyomi process. - */ -class UnionMangasUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val slug = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${UnionMangas.PREFIX_SLUG_SEARCH}$slug") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("UnionMangasUrl", e.toString()) - } - } else { - Log.e("UnionMangasUrl", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/pt/yesmangas/AndroidManifest.xml b/src/pt/yesmangas/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/pt/yesmangas/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/pt/yesmangas/build.gradle b/src/pt/yesmangas/build.gradle deleted file mode 100644 index 69273f976..000000000 --- a/src/pt/yesmangas/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'YES Mangás' - pkgNameSuffix = 'pt.yesmangas' - extClass = '.YesMangas' - extVersionCode = 7 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/pt/yesmangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3b91c99e5..000000000 Binary files a/src/pt/yesmangas/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/yesmangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 51afc3037..000000000 Binary files a/src/pt/yesmangas/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/yesmangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f0662aa59..000000000 Binary files a/src/pt/yesmangas/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/yesmangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7f3a529c6..000000000 Binary files a/src/pt/yesmangas/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/yesmangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/yesmangas/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index dc63f07bd..000000000 Binary files a/src/pt/yesmangas/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/pt/yesmangas/res/web_hi_res_512.png b/src/pt/yesmangas/res/web_hi_res_512.png deleted file mode 100644 index 9c518e509..000000000 Binary files a/src/pt/yesmangas/res/web_hi_res_512.png and /dev/null differ diff --git a/src/pt/yesmangas/src/eu/kanade/tachiyomi/extension/pt/yesmangas/YesMangas.kt b/src/pt/yesmangas/src/eu/kanade/tachiyomi/extension/pt/yesmangas/YesMangas.kt deleted file mode 100644 index cb2560fbd..000000000 --- a/src/pt/yesmangas/src/eu/kanade/tachiyomi/extension/pt/yesmangas/YesMangas.kt +++ /dev/null @@ -1,146 +0,0 @@ -package eu.kanade.tachiyomi.extension.pt.yesmangas - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import java.util.concurrent.TimeUnit - -class YesMangas : ParsedHttpSource() { - - // Hardcode the id because the language wasn't specific. - override val id: Long = 7187189302580957274 - - override val name = "YES Mangás" - - override val baseUrl = "https://yesmangas1.com" - - override val lang = "pt-BR" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", USER_AGENT) - .add("Origin", baseUrl) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaSelector(): String = "div#destaques div.three.columns a.img" - - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.attr("title").withoutLanguage() - thumbnail_url = element.select("img").attr("data-path").toLargeUrl() - url = element.attr("href") - } - - override fun popularMangaNextPageSelector(): String? = null - - override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) - - override fun latestUpdatesSelector(): String = "div#lancamentos table.u-full-width tbody tr td:eq(0) a" - - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - title = element.attr("title").withoutLanguage() - thumbnail_url = element.select("img").attr("data-path").toLargeUrl() - setUrlWithoutDomain(element.attr("href")) - } - - override fun latestUpdatesNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder() - .addQueryParameter("q", query) - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector(): String = "tbody#leituras tr td:eq(0) a" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.attr("title").withoutLanguage() - thumbnail_url = element.select("img").attr("data-path").toLargeUrl() - setUrlWithoutDomain(element.attr("href")) - } - - override fun searchMangaNextPageSelector(): String? = null - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val container = document.select("div#descricao").first() - - author = container.select("ul li:contains(Autor)").textWithoutLabel() - artist = container.select("ul li:contains(Desenho)").textWithoutLabel() - genre = container.select("ul li:contains(Categorias)").textWithoutLabel() - status = container.select("ul li:contains(Status)").text().toStatus() - description = container.select("article").text() - .substringBefore("Relacionados") - thumbnail_url = container.select("img").first() - .attr("data-path") - .toLargeUrl() - } - - override fun chapterListSelector(): String = "div#capitulos a.button" - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.attr("title").substringAfter(" - ") - chapter_number = element.text().toFloatOrNull() ?: -1f - setUrlWithoutDomain(element.attr("href")) - } - - override fun pageListRequest(chapter: SChapter): Request { - val newHeaders = headersBuilder() - .set("Referer", baseUrl + chapter.url.substringBeforeLast("/")) - .build() - - return GET(baseUrl + chapter.url, newHeaders) - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.read-slideshow a img") - .mapIndexed { i, el -> Page(i, document.location(), el.attr("src")) } - } - - override fun imageUrlParse(document: Document): String = "" - - override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() - - return GET(page.imageUrl!!, newHeaders) - } - - private fun String.withoutLanguage(): String = replace(LANG_REGEX, "") - - private fun String.toLargeUrl(): String = replace(IMAGE_REGEX, "_full.") - - private fun Elements.textWithoutLabel(): String = text()!!.substringAfter(":").trim() - - private fun String.toStatus() = when { - contains("Completo") -> SManga.COMPLETED - contains("Ativo") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - companion object { - private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" - - private val LANG_REGEX = "( )?\\((PT-)?BR\\)".toRegex() - private val IMAGE_REGEX = "_(small|medium|xmedium|xlarge)\\.".toRegex() - } -} diff --git a/src/ru/acomics/AndroidManifest.xml b/src/ru/acomics/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/acomics/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/acomics/build.gradle b/src/ru/acomics/build.gradle deleted file mode 100644 index fbb8b056e..000000000 --- a/src/ru/acomics/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'AComics' - pkgNameSuffix = 'ru.acomics' - extClass = '.AComics' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/acomics/res/mipmap-hdpi/ic_launcher.png b/src/ru/acomics/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 26e62247f..000000000 Binary files a/src/ru/acomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/acomics/res/mipmap-mdpi/ic_launcher.png b/src/ru/acomics/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index cffdcfdbc..000000000 Binary files a/src/ru/acomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/acomics/res/mipmap-xhdpi/ic_launcher.png b/src/ru/acomics/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 041018994..000000000 Binary files a/src/ru/acomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/acomics/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/acomics/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 96d09cd9b..000000000 Binary files a/src/ru/acomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/acomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/acomics/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 74537c6c9..000000000 Binary files a/src/ru/acomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/acomics/src/eu/kanade/tachiyomi/extension/ru/acomics/AComics.kt b/src/ru/acomics/src/eu/kanade/tachiyomi/extension/ru/acomics/AComics.kt deleted file mode 100644 index 0bf950a76..000000000 --- a/src/ru/acomics/src/eu/kanade/tachiyomi/extension/ru/acomics/AComics.kt +++ /dev/null @@ -1,197 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.acomics - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.net.URLEncoder - -class AComics : ParsedHttpSource() { - - override val name = "AComics" - - override val baseUrl = "https://acomics.ru" - - override val lang = "ru" - - private val cookiesHeader by lazy { - val cookies = mutableMapOf<String, String>() - cookies["ageRestrict"] = "17" - buildCookies(cookies) - } - - private fun buildCookies(cookies: Map<String, String>) = - cookies.entries.joinToString(separator = "; ", postfix = ";") { - "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" - } - - override val client = network.client.newBuilder() - .addNetworkInterceptor { chain -> - val newReq = chain - .request() - .newBuilder() - .addHeader("Cookie", cookiesHeader) - .build() - - chain.proceed(newReq) - }.build()!! - - override val supportsLatest = true - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/comics?categories=&ratings[]=1&ratings[]=2&ratings[]=3&ratings[]=4&ratings[]=5ratings[]=6&&type=0&updatable=0&subscribe=0&issue_count=2&sort=subscr_count&skip=${10 * (page - 1)}", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/comics?categories=&ratings[]=1&ratings[]=2&ratings[]=3&ratings[]=4&ratings[]=5ratings[]=6&&type=0&updatable=0&subscribe=0&issue_count=2&sort=last_update&skip=${10 * (page - 1)}", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url: String = if (query.isNotEmpty()) { - "$baseUrl/search?keyword=$query" - } else { - val categories = mutableListOf<Int>() - var status = "0" - val rating = mutableListOf<Int>() - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is GenreList -> { - filter.state.forEach { - if (it.state) { - categories.add(it.id) - } - } - } - is Status -> { - if (filter.state == 1) { - status = "no" - } - if (filter.state == 2) { - status = "yes" - } - } - is RatingList -> { - filter.state.forEach { - if (it.state) { - rating.add(it.id) - } - } - } - } - } - "$baseUrl/comics?categories=${categories.joinToString(",")}&${rating.joinToString { "ratings[]=$it" }}&type=0&updatable=$status&subscribe=0&issue_count=2&sort=subscr_count&skip=${10 * (page - 1)}" - } - return GET(url, headers) - } - - override fun popularMangaSelector() = "table.list-loadable > tbody > tr" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = baseUrl + element.select("a > img").first().attr("src") - element.select("div.title > a").first().let { - manga.setUrlWithoutDomain(it.attr("href") + "/about") - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "span.button:not(:has(a)) + span.button > a" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select(".about-summary").first() - val manga = SManga.create() - manga.author = infoElement.select(".about-summary > p:contains(Автор)").text().split(":")[1] - manga.genre = infoElement.select("a.button").joinToString { it.text() } - manga.description = infoElement.ownText() - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - val res = mutableListOf<SChapter>() - val count = response.asJsoup() - .select(".about-summary > p:contains(Количество выпусков:)") - .text() - .split("Количество выпусков: ")[1].toInt() - - for (index in count downTo 1) { - val chapter = SChapter.create() - chapter.chapter_number = index.toFloat() - chapter.name = index.toString() - val url = response.request().url().toString().split("/about")[0].split(baseUrl)[1] - chapter.url = "$url/$index" - res.add(chapter) - } - return res - } - - override fun chapterListSelector(): Nothing = throw Exception("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used") - - override fun pageListParse(document: Document): List<Page> { - val imageElement = document.select("img#mainImage").first() - return listOf(Page(0, imageUrl = baseUrl + imageElement.attr("src"))) - } - - override fun imageUrlParse(document: Document) = "" - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Категории", genres) - private class Genre(name: String, val id: Int) : Filter.CheckBox(name) - private class Rating(name: String, val id: Int) : Filter.CheckBox(name, state = true) - private class Status : Filter.Select<String>("Статус", arrayOf("Все", "Завершенный", "Продолжающийся")) - - private class RatingList : Filter.Group<Rating>( - "Возрастная категория", - listOf( - Rating("???", 1), - Rating("0+", 2), - Rating("6+", 3), - Rating("12+", 4), - Rating("16+", 5), - Rating("18+", 6) - ) - ) - - override fun getFilterList() = FilterList( - Status(), - RatingList(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("Животные", 1), - Genre("Драма", 2), - Genre("Фэнтези", 3), - Genre("Игры", 4), - Genre("Юмор", 5), - Genre("Журнал", 6), - Genre("Паранормальное", 7), - Genre("Конец света", 8), - Genre("Романтика", 9), - Genre("Фантастика", 10), - Genre("Бытовое", 11), - Genre("Стимпанк", 12), - Genre("Супергерои", 13) - ) -} diff --git a/src/ru/allhentai/AndroidManifest.xml b/src/ru/allhentai/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/allhentai/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/allhentai/build.gradle b/src/ru/allhentai/build.gradle deleted file mode 100644 index e7f6bf81a..000000000 --- a/src/ru/allhentai/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'AllHentai' - pkgNameSuffix = 'ru.allhentai' - extClass = '.AllHentai' - extVersionCode = 3 - libVersion = '1.2' - containsNsfw = true -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/allhentai/res/mipmap-hdpi/ic_launcher.png b/src/ru/allhentai/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 359610927..000000000 Binary files a/src/ru/allhentai/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/allhentai/res/mipmap-mdpi/ic_launcher.png b/src/ru/allhentai/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c23a8e98d..000000000 Binary files a/src/ru/allhentai/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/allhentai/res/mipmap-xhdpi/ic_launcher.png b/src/ru/allhentai/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6b6ce16c8..000000000 Binary files a/src/ru/allhentai/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/allhentai/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/allhentai/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ecbb34fd2..000000000 Binary files a/src/ru/allhentai/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/allhentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/allhentai/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d45ddd771..000000000 Binary files a/src/ru/allhentai/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/allhentai/res/web_hi_res_512.png b/src/ru/allhentai/res/web_hi_res_512.png deleted file mode 100644 index 2bb4c600f..000000000 Binary files a/src/ru/allhentai/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/allhentai/src/eu/kanade/tachiyomi/extension/ru/allhentai/AllHentai.kt b/src/ru/allhentai/src/eu/kanade/tachiyomi/extension/ru/allhentai/AllHentai.kt deleted file mode 100644 index 7bc154e6f..000000000 --- a/src/ru/allhentai/src/eu/kanade/tachiyomi/extension/ru/allhentai/AllHentai.kt +++ /dev/null @@ -1,419 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.allhentai - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.regex.Pattern - -class AllHentai : ParsedHttpSource() { - - override val name = "AllHentai" - - override val baseUrl = "https://allhentai.ru" - - override val lang = "ru" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() - - override fun popularMangaSelector() = "div.tile" - - override fun latestUpdatesSelector() = "div.tile" - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img.lazy").first()?.attr("data-original") - element.select("h3 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.nextLink" - - override fun latestUpdatesNextPageSelector() = "a.nextLink" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> filter.state.forEach { genre -> - if (genre.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(genre.id, arrayOf("=", "=in", "=ex")[genre.state]) - } - } - } - } - if (query.isNotEmpty()) { - url.addQueryParameter("q", query) - } - return GET(url.toString().replace("=%3D", "="), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // max 200 results - override fun searchMangaNextPageSelector(): Nothing? = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.leftContent").first() - val rawCategory = infoElement.select("span.elem_category").text() - val category = if (rawCategory.isNotEmpty()) { - rawCategory.toLowerCase() - } else { - "манга" - } - - val manga = SManga.create() - var authorElement = infoElement.select("span.elem_author").first()?.text() - if (authorElement == null) { - authorElement = infoElement.select("span.elem_screenwriter").first()?.text() - } - manga.author = authorElement - manga.artist = infoElement.select("span.elem_illustrator").first()?.text() - manga.genre = infoElement.select("span.elem_genre").text().split(",").plusElement(category).joinToString { it.trim() } - manga.description = infoElement.select("div.manga-description").text() - manga.status = parseStatus(infoElement.html()) - manga.thumbnail_url = infoElement.select("img").attr("data-full") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("Запрещена публикация произведения по копирайту") -> SManga.LICENSED - element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> SManga.COMPLETED - element.contains("<b>Перевод:</b> продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return if (manga.status != SManga.LICENSED) { - client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga) - } - } else { - Observable.error(java.lang.Exception("Licensed - No chapters to show")) - } - } - - private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { - val document = response.asJsoup() - return document.select(chapterListSelector()).map { chapterFromElement(it, manga) } - } - - override fun chapterListSelector() = "div.chapters-link > table > tbody > tr:has(td > a)" - - private fun chapterFromElement(element: Element, manga: SManga): SChapter { - val urlElement = element.select("a").first() - val urlText = urlElement.text() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mtr=1") - - var translators = "" - val translatorElement = urlElement.attr("title") - if (!translatorElement.isNullOrBlank()) { - translators = translatorElement - .replace("(Переводчик),", "&") - .removeSuffix(" (Переводчик)") - } - chapter.scanlator = translators - - chapter.name = urlText.removeSuffix(" новое").trim() - if (manga.title.length > 25) { - for (word in manga.title.split(' ')) { - chapter.name = chapter.name.removePrefix(word).trim() - } - } - val dots = chapter.name.indexOf("…") - val numbers = chapter.name.findAnyOf(IntRange(0, 9).map { it.toString() })?.first ?: 0 - - if (dots in 0 until numbers) { - chapter.name = chapter.name.substringAfter("…").trim() - } - - chapter.date_upload = element.select("td.d-none").last()?.text()?.let { - try { - SimpleDateFormat("dd.MM.yy", Locale.US).parse(it)?.time ?: 0L - } catch (e: ParseException) { - SimpleDateFormat("dd/MM/yy", Locale.US).parse(it)?.time ?: 0L - } - } ?: 0 - return chapter - } - - override fun chapterFromElement(element: Element): SChapter { - throw Exception("Not used") - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""\s*([0-9]+)(\s-\s)([0-9]+)\s*""") - val extra = Regex("""\s*([0-9]+\sЭкстра)\s*""") - val single = Regex("""\s*Сингл\s*""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - val number = it.groups[3]?.value!! - chapter.chapter_number = number.toFloat() - } - } - extra.containsMatchIn(chapter.name) -> // Extra chapters doesn't contain chapter number - chapter.chapter_number = -2f - single.containsMatchIn(chapter.name) -> // Oneshoots, doujinshi and other mangas with one chapter - chapter.chapter_number = 1f - } - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val beginIndex = html.indexOf("rm_h.init( [") - val endIndex = html.indexOf(");", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex) - - val p = Pattern.compile("'.*?','.*?',\".*?\"") - val m = p.matcher(trimmedHtml) - - val pages = mutableListOf<Page>() - - var i = 0 - while (m.find()) { - val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') - val url = if (urlParts[1].isEmpty() && urlParts[2].startsWith("/static/")) { - baseUrl + urlParts[2] - } else { - if (urlParts[1].endsWith("/manga/")) { - urlParts[0] + urlParts[2] - } else if (urlParts[1].isEmpty()) { - val imageUrl = urlParts[2].split('?') - "https:" + urlParts[0] + imageUrl[0] - } else { - urlParts[1] + urlParts[0] + urlParts[2] - } - } - pages.add(Page(i++, "", url)) - } - return pages - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - - private class Genre(name: String, val id: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - - /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")] - * .map(el => `Genre("${el.textContent.trim()}", $"{el.getAttribute('onclick') - * .substr(31,el.getAttribute('onclick').length-33)"})`).join(',\n') - * on https://readmanga.me/search/advanced - */ - override fun getFilterList() = FilterList( - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("3D", "el_626"), - Genre("ahegao", "el_855"), - Genre("footfuck", "el_912"), - Genre("gender bender", "el_89"), - Genre("handjob", "el_1254"), - Genre("megane", "el_962"), - Genre("Mind break", "el_705"), - Genre("netori", "el_1356"), - Genre("paizuri (titsfuck)", "el_1027"), - Genre("scat", "el_1221"), - Genre("tomboy", "el_881"), - Genre("x-ray", "el_1992"), - Genre("алкоголь", "el_1000"), - Genre("анал", "el_828"), - Genre("андроид", "el_1752"), - Genre("анилингус", "el_1037"), - Genre("арт", "el_1190"), - Genre("бдсм", "el_78"), - Genre("Без текста", "el_3157"), - Genre("без трусиков", "el_993"), - Genre("без цензуры", "el_888"), - Genre("беременность", "el_922"), - Genre("бикини", "el_1126"), - Genre("близнецы", "el_1092"), - Genre("боди-арт", "el_1130"), - Genre("Больница", "el_289"), - Genre("большая грудь", "el_837"), - Genre("Большая попка", "el_3156"), - Genre("борьба", "el_72"), - Genre("буккакэ", "el_82"), - Genre("в бассейне", "el_3599"), - Genre("в ванной", "el_878"), - Genre("в государственном учреждении", "el_86"), - Genre("в общественном месте", "el_866"), - Genre("в первый раз", "el_811"), - Genre("в транспорте", "el_3246"), - Genre("в цвете", "el_290"), - Genre("вампиры", "el_1250"), - Genre("веб", "el_1104"), - Genre("вибратор", "el_867"), - Genre("втроем", "el_3711"), - Genre("гарем", "el_87"), - Genre("гипноз", "el_1423"), - Genre("глубокий минет", "el_3555"), - Genre("горячий источник", "el_1209"), - Genre("групповой секс", "el_88"), - Genre("гяру и гангуро", "el_844"), - Genre("двойное проникновение", "el_911"), - Genre("Девочки волшебницы", "el_292"), - Genre("девчонки", "el_875"), - Genre("демоны", "el_1139"), - Genre("дилдо", "el_868"), - Genre("додзинси", "el_92"), - Genre("Домохозяйка", "el_300"), - Genre("драма", "el_95"), - Genre("дыра в стене", "el_1420"), - Genre("жестокость", "el_883"), - Genre("золотой дождь", "el_1007"), - Genre("зомби", "el_1099"), - Genre("зрелые женщины", "el_1441"), - Genre("Измена", "el_291"), - Genre("изнасилование", "el_124"), - Genre("инопланетяне", "el_990"), - Genre("инцест", "el_85"), - Genre("исполнение желаний", "el_909"), - Genre("исторический", "el_93"), - Genre("камера", "el_869"), - Genre("колготки", "el_849"), - Genre("комикс", "el_1003"), - Genre("косплей", "el_1024"), - Genre("кремпай", "el_3709"), - Genre("куннилингус", "el_5383"), - Genre("купальники", "el_845"), - Genre("латекс и кожа", "el_1047"), - Genre("магия", "el_1128"), - Genre("маленькая грудь", "el_870"), - Genre("мастурбация", "el_882"), - Genre("медсестра", "el_5688"), - Genre("мейдочки", "el_994"), - Genre("Мерзкий дядька", "el_2145"), - Genre("милф", "el_5679"), - Genre("много девушек", "el_860"), - Genre("много спермы", "el_1020"), - Genre("молоко", "el_1029"), - Genre("монстрдевушки", "el_1022"), - Genre("монстры", "el_917"), - Genre("мочеиспускание", "el_1193"), - Genre("мужчина крепкого телосложения", "el_5715"), - Genre("на природе", "el_842"), - Genre("наблюдение", "el_928"), - Genre("научная фантастика", "el_76"), - Genre("не бритая киска", "el_4237"), - Genre("не бритые подмышки", "el_4238"), - Genre("Нетораре", "el_303"), - Genre("обмен телами", "el_5120"), - Genre("обычный секс", "el_1012"), - Genre("огромная грудь", "el_1207"), - Genre("огромный член", "el_884"), - Genre("омораси", "el_81"), - Genre("оральный секс", "el_853"), - Genre("орки", "el_3247"), - Genre("парень пассив", "el_861"), - Genre("парни", "el_874"), - Genre("переодевание", "el_1026"), - Genre("пляж", "el_846"), - Genre("повседневность", "el_90"), - Genre("подглядывание", "el_978"), - Genre("подчинение", "el_885"), - Genre("похищение", "el_1183"), - Genre("превозмогание", "el_71"), - Genre("принуждение", "el_929"), - Genre("прозрачная одежда", "el_924"), - Genre("проституция", "el_3563"), - Genre("психические отклонения", "el_886"), - Genre("публично", "el_1045"), - Genre("пьяные", "el_2055"), - Genre("рабыни", "el_1433"), - Genre("романтика", "el_74"), - Genre("сверхъестественное", "el_634"), - Genre("секс игрушки", "el_871"), - Genre("сексуально возбужденная", "el_925"), - Genre("сибари", "el_80"), - Genre("сильный", "el_913"), - Genre("слабая", "el_455"), - Genre("спортивная форма", "el_891"), - Genre("спящие", "el_972"), - Genre("страпон", "el_872"), - Genre("Суккуб", "el_677"), - Genre("темнокожие", "el_611"), - Genre("тентакли", "el_69"), - Genre("толстушки", "el_1036"), - Genre("трагедия", "el_1321"), - Genre("трап", "el_859"), - Genre("ужасы", "el_75"), - Genre("униформа", "el_1008"), - Genre("ушастые", "el_991"), - Genre("фантазии", "el_1124"), - Genre("фемдом", "el_873"), - Genre("Фестиваль", "el_1269"), - Genre("фетиш", "el_1137"), - Genre("фистинг", "el_821"), - Genre("фурри", "el_91"), - Genre("футанари", "el_77"), - Genre("футанари имеет парня", "el_1426"), - Genre("фэнтези", "el_70"), - Genre("цельный купальник", "el_1257"), - Genre("цундере", "el_850"), - Genre("чикан", "el_1059"), - Genre("чулки", "el_889"), - Genre("шлюха", "el_763"), - Genre("эксгибиционизм", "el_813"), - Genre("Эльфы", "el_286"), - Genre("эччи", "el_798"), - Genre("юмор", "el_73"), - Genre("юные", "el_1162"), - Genre("юри", "el_84"), - Genre("яндере", "el_823"), - Genre("яой", "el_83") - ) -} diff --git a/src/ru/comx/AndroidManifest.xml b/src/ru/comx/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/comx/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/comx/build.gradle b/src/ru/comx/build.gradle deleted file mode 100644 index 56609b049..000000000 --- a/src/ru/comx/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ComX' - pkgNameSuffix = 'ru.comx' - extClass = '.ComX' - extVersionCode = 7 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/comx/res/mipmap-hdpi/ic_launcher.png b/src/ru/comx/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index e2c0b05e1..000000000 Binary files a/src/ru/comx/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/comx/res/mipmap-mdpi/ic_launcher.png b/src/ru/comx/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 031b074a9..000000000 Binary files a/src/ru/comx/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/comx/res/mipmap-xhdpi/ic_launcher.png b/src/ru/comx/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 848e5202c..000000000 Binary files a/src/ru/comx/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/comx/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/comx/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6b9f31cdb..000000000 Binary files a/src/ru/comx/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/comx/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/comx/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 080e0d0cc..000000000 Binary files a/src/ru/comx/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/comx/res/web_hi_res_512.png b/src/ru/comx/res/web_hi_res_512.png deleted file mode 100644 index cee1d04b3..000000000 Binary files a/src/ru/comx/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt b/src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt deleted file mode 100644 index 1995358f9..000000000 --- a/src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt +++ /dev/null @@ -1,223 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.comx - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class ComX : ParsedHttpSource() { - override val name = "Com-x" - - override val baseUrl = "https://com-x.life" - - override val lang = "ru" - - override val supportsLatest = true - - private val userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0" - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addNetworkInterceptor(RateLimitInterceptor(2)) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", userAgent) - .add("Referer", baseUrl) - - override fun popularMangaSelector() = "div.shortstory1" - - override fun latestUpdatesSelector() = "ul.last-comix li" - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/comix/page/$page/", headers) - - override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - - if (mangas.isEmpty()) throw UnsupportedOperationException("Error: Open in WebView and solve the Antirobot!") - - val hasNextPage = popularMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = baseUrl + element.select("img").first().attr("src") - element.select("div.info-poster1 a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = baseUrl + element.select("img").first().attr("src") - element.select("a.comix-last-title").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun popularMangaNextPageSelector() = "div.nextprev:last-child" - - override fun latestUpdatesNextPageSelector(): Nothing? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return POST( - "$baseUrl/comix/", - body = FormBody.Builder() - .add("do", "search") - .add("story", query) - .add("subaction", "search") - .build(), - headers = headers - ) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // max 200 results - override fun searchMangaNextPageSelector(): Nothing? = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.maincont").first() - - val manga = SManga.create() - manga.author = infoElement.select("p:eq(2)").text().removePrefix("Издатель: ") - manga.genre = infoElement.select("p:eq(3)").text() - .removePrefix("Жанр: ").split(",").plusElement("Комикс").joinToString { it.trim() } - - manga.status = parseStatus( - infoElement.select("p:eq(4)").text() - .removePrefix("Статус: ") - ) - - val text = infoElement.select("*").text() - if (!text.contains("Добавить описание на комикс")) { - val fromRemove = "Отслеживать" - val toRemove = "Читать комикс" - val desc = text.removeRange(0, text.indexOf(fromRemove) + fromRemove.length) - manga.description = desc.removeRange(desc.indexOf(toRemove) + toRemove.length, desc.length) - } - - val src = infoElement.select("img").attr("src") - if (src.contains(baseUrl)) { - manga.thumbnail_url = src - } else { - manga.thumbnail_url = baseUrl + src - } - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("Продолжается") -> SManga.ONGOING - element.contains("Завершён") || - element.contains("Лимитка") || - element.contains("Ван шот") || - element.contains("Графический роман") -> SManga.COMPLETED - - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "li[id^=cmx-]" - - private fun chapterResponseParse(document: Document): List<SChapter> { - return document.select(chapterListSelector()).map { chapterFromElement(it) } - } - - private fun chapterPageListParse(document: Document): List<String> { - return document.select("span[class=\"\"]").map { it.text() } - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val id = response.request().url().toString().removePrefix("$baseUrl/").split('-')[0] - - val list = mutableListOf<SChapter>() - list += chapterResponseParse(document) - - val pages = chapterPageListParse(document).distinct() - - for (page in pages) { - val post = POST( - "$baseUrl/engine/mods/comix/listPages.php", - body = FormBody.Builder() - .add("newsid", id) - .add("page", page) - .build(), - headers = headers - ) - - list += chapterResponseParse(client.newCall(post).execute().asJsoup()) - } - - return list - } - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val urlText = urlElement.text() - val chapter = SChapter.create() - chapter.name = urlText.split('/')[0] // Remove english part of name - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.date_upload = 0 - return chapter - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val baseImgUrl = "https://img.com-x.life/comix/" - - val beginTag = "\"images\":[" - val beginIndex = html.indexOf(beginTag) - val endIndex = html.indexOf("]", beginIndex) - - val urls: List<String> = html.substring(beginIndex + beginTag.length, endIndex) - .split(',').map { - val img = it.replace("\\", "").replace("\"", "") - baseImgUrl + img - } - - val pages = mutableListOf<Page>() - for (i in urls.indices) { - pages.add(Page(i, "", urls[i])) - } - - return pages - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - return GET(page.imageUrl!!, headers) - } -} diff --git a/src/ru/desu/AndroidManifest.xml b/src/ru/desu/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/desu/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/desu/build.gradle b/src/ru/desu/build.gradle deleted file mode 100644 index 9720b8201..000000000 --- a/src/ru/desu/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Desu' - pkgNameSuffix = 'ru.desu' - extClass = '.Desu' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/desu/res/mipmap-hdpi/ic_launcher.png b/src/ru/desu/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0cec4aa5f..000000000 Binary files a/src/ru/desu/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/desu/res/mipmap-mdpi/ic_launcher.png b/src/ru/desu/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 62ed9ff07..000000000 Binary files a/src/ru/desu/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/desu/res/mipmap-xhdpi/ic_launcher.png b/src/ru/desu/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d2daa17db..000000000 Binary files a/src/ru/desu/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/desu/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/desu/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2cb012fea..000000000 Binary files a/src/ru/desu/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/desu/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/desu/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 415f0fd8b..000000000 Binary files a/src/ru/desu/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/desu/res/web_hi_res_512.png b/src/ru/desu/res/web_hi_res_512.png deleted file mode 100644 index 49c08a9ca..000000000 Binary files a/src/ru/desu/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt b/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt deleted file mode 100644 index 4c56fddf4..000000000 --- a/src/ru/desu/src/eu/kanade/tachiyomi/extension/ru/desu/Desu.kt +++ /dev/null @@ -1,251 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.desu - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import rx.Observable -import java.util.ArrayList - -class Desu : HttpSource() { - override val name = "Desu" - - override val baseUrl = "https://desu.me/manga/api" - - override val lang = "ru" - - override val supportsLatest = true - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Tachiyomi") - add("Referer", baseUrl) - } - - private fun mangaPageFromJSON(json: String, next: Boolean): MangasPage { - val arr = JSONArray(json) - val ret = ArrayList<SManga>(arr.length()) - for (i in 0 until arr.length()) { - val obj = arr.getJSONObject(i) - ret.add( - SManga.create().apply { - mangaFromJSON(obj, false) - } - ) - } - return MangasPage(ret, next) - } - - private fun SManga.mangaFromJSON(obj: JSONObject, chapter: Boolean) { - val id = obj.getInt("id") - url = "/$id" - title = obj.getString("name") - thumbnail_url = obj.getJSONObject("image").getString("original") - description = obj.getString("description") - genre = if (chapter) { - val jsonArray = obj.getJSONArray("genres") - val genreList = mutableListOf<String>() - for (i in 0 until jsonArray.length()) { - genreList.add(jsonArray.getJSONObject(i).getString("russian")) - } - genreList.joinToString() - } else { - obj.getString("genres") - } - status = when (obj.getString("status")) { - "ongoing" -> SManga.ONGOING - "released" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/?limit=50&order=popular&page=$page") - - override fun popularMangaParse(response: Response) = searchMangaParse(response) - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?limit=50&order=updated&page=$page") - - override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = "$baseUrl/?limit=20&page=$page" - val types = mutableListOf<Type>() - val genres = mutableListOf<Genre>() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is OrderBy -> url += "&order=" + arrayOf("popular", "updated", "name")[filter.state] - is TypeList -> filter.state.forEach { type -> if (type.state) types.add(type) } - is GenreList -> filter.state.forEach { genre -> if (genre.state) genres.add(genre) } - } - } - - if (types.isNotEmpty()) { - url += "&kinds=" + types.joinToString(",") { it.id } - } - if (genres.isNotEmpty()) { - url += "&genres=" + genres.joinToString(",") { it.id } - } - if (query.isNotEmpty()) { - url += "&search=$query" - } - return GET(url) - } - - override fun searchMangaParse(response: Response): MangasPage { - val res = response.body()!!.string() - val obj = JSONObject(res).getJSONArray("response") - val nav = JSONObject(res).getJSONObject("pageNavParams") - val count = nav.getInt("count") - val limit = nav.getInt("limit") - val page = nav.getInt("page") - return mangaPageFromJSON(obj.toString(), count > page * limit) - } - - private fun titleDetailsRequest(manga: SManga): Request { - val titleId = manga.url.substringAfterLast("/") - - val newHeaders = headersBuilder().build() - - return GET("$baseUrl/$titleId", newHeaders) - } - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(titleDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl.substringBefore("/api") + manga.url, headers) - } - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val obj = JSONObject(response.body()!!.string()).getJSONObject("response") - mangaFromJSON(obj, true) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val obj = JSONObject(response.body()!!.string()).getJSONObject("response") - val ret = ArrayList<SChapter>() - - val cid = obj.getInt("id") - - val arr = obj.getJSONObject("chapters").getJSONArray("list") - for (i in 0 until arr.length()) { - val obj2 = arr.getJSONObject(i) - ret.add( - SChapter.create().apply { - val ch = obj2.getString("ch") - val title = if (obj2.getString("title") == "null") "" else obj2.getString("title") - name = if (title.isEmpty()) { - "Глава $ch" - } else { - "$ch - $title" - } - val id = obj2.getString("id") - url = "/$cid/chapter/$id" - chapter_number = ch.toFloat() - date_upload = obj2.getLong("date") * 1000 - } - ) - } - return ret - } - - override fun pageListParse(response: Response): List<Page> { - val obj = JSONObject(response.body()!!.string()).getJSONObject("response") - val pages = obj.getJSONObject("pages") - val list = pages.getJSONArray("list") - val ret = ArrayList<Page>(list.length()) - for (i in 0 until list.length()) { - ret.add(Page(i, "", list.getJSONObject(i).getString("img"))) - } - return ret - } - - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("This method should not be called!") - - private class OrderBy : Filter.Select<String>( - "Сортировка", - arrayOf("Популярность", "Дата", "Имя") - ) - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Жанр", genres) - private class TypeList(types: List<Type>) : Filter.Group<Type>("Тип", types) - - private class Type(name: String, val id: String) : Filter.CheckBox(name) - private class Genre(name: String, val id: String) : Filter.CheckBox(name) - override fun getFilterList() = FilterList( - OrderBy(), - TypeList(getTypeList()), - GenreList(getGenreList()) - ) - - private fun getTypeList() = listOf( - Type("Манга", "manga"), - Type("Манхва", "manhwa"), - Type("Маньхуа", "manhua"), - Type("Ваншот", "one_shot"), - Type("Комикс", "comics") - ) - - private fun getGenreList() = listOf( - Genre("Безумие", "Dementia"), - Genre("Боевые искусства", "Martial Arts"), - Genre("Вампиры", "Vampire"), - Genre("Военное", "Military"), - Genre("Гарем", "Harem"), - Genre("Демоны", "Demons"), - Genre("Детектив", "Mystery"), - Genre("Детское", "Kids"), - Genre("Дзёсей", "Josei"), - Genre("Додзинси", "Doujinshi"), - Genre("Драма", "Drama"), - Genre("Игры", "Game"), - Genre("Исторический", "Historical"), - Genre("Комедия", "Comedy"), - Genre("Космос", "Space"), - Genre("Магия", "Magic"), - Genre("Машины", "Cars"), - Genre("Меха", "Mecha"), - Genre("Музыка", "Music"), - Genre("Пародия", "Parody"), - Genre("Повседневность", "Slice of Life"), - Genre("Полиция", "Police"), - Genre("Приключения", "Adventure"), - Genre("Психологическое", "Psychological"), - Genre("Романтика", "Romance"), - Genre("Самураи", "Samurai"), - Genre("Сверхъестественное", "Supernatural"), - Genre("Сёдзе", "Shoujo"), - Genre("Сёдзе Ай", "Shoujo Ai"), - Genre("Сейнен", "Seinen"), - Genre("Сёнен", "Shounen"), - Genre("Сёнен Ай", "Shounen Ai"), - Genre("Смена пола", "Gender Bender"), - Genre("Спорт", "Sports"), - Genre("Супер сила", "Super Power"), - Genre("Триллер", "Thriller"), - Genre("Ужасы", "Horror"), - Genre("Фантастика", "Sci-Fi"), - Genre("Фэнтези", "Fantasy"), - Genre("Хентай", "Hentai"), - Genre("Школа", "School"), - Genre("Экшен", "Action"), - Genre("Этти", "Ecchi"), - Genre("Юри", "Yuri"), - Genre("Яой", "Yaoi") - ) -} diff --git a/src/ru/henchan/AndroidManifest.xml b/src/ru/henchan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/henchan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/henchan/build.gradle b/src/ru/henchan/build.gradle deleted file mode 100644 index 15fcbfe2a..000000000 --- a/src/ru/henchan/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Henchan' - pkgNameSuffix = 'ru.henchan' - extClass = '.Henchan' - extVersionCode = 21 - libVersion = '1.2' - containsNsfw = true -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/henchan/res/mipmap-hdpi/ic_launcher.png b/src/ru/henchan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b974e38a3..000000000 Binary files a/src/ru/henchan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/henchan/res/mipmap-mdpi/ic_launcher.png b/src/ru/henchan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 88b9ee37f..000000000 Binary files a/src/ru/henchan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/henchan/res/mipmap-xhdpi/ic_launcher.png b/src/ru/henchan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6aacca0ab..000000000 Binary files a/src/ru/henchan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/henchan/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/henchan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index dee6837f0..000000000 Binary files a/src/ru/henchan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/henchan/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/henchan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9f3c1ba11..000000000 Binary files a/src/ru/henchan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/henchan/res/web_hi_res_512.png b/src/ru/henchan/res/web_hi_res_512.png deleted file mode 100644 index 4e8e59b53..000000000 Binary files a/src/ru/henchan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/henchan/src/eu/kanade/tachiyomi/extension/ru/henchan/Henchan.kt b/src/ru/henchan/src/eu/kanade/tachiyomi/extension/ru/henchan/Henchan.kt deleted file mode 100644 index 5a3b8ee33..000000000 --- a/src/ru/henchan/src/eu/kanade/tachiyomi/extension/ru/henchan/Henchan.kt +++ /dev/null @@ -1,457 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.henchan - -import android.annotation.SuppressLint -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.net.URL -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -@Nsfw -class Henchan : ParsedHttpSource() { - - override val name = "Henchan" - - override val baseUrl = "https://hentai-chan.pro" - - private val exhentaiBaseUrl = "http://exhentai-dono.me" - - override val lang = "ru" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/mostfavorites&sort=manga?offset=${20 * (page - 1)}", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/manga/new?offset=${20 * (page - 1)}", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - - val url = if (query.isNotEmpty()) { - "$baseUrl/?do=search&subaction=search&story=$query&search_start=$page" - } else { - var genres = "" - var order = "" - filters.forEach { filter -> - when (filter) { - is GenreList -> { - filter.state - .filter { !it.isIgnored() } - .forEach { f -> - genres += (if (f.isExcluded()) "-" else "") + f.id + '+' - } - } - } - } - - if (genres.isNotEmpty()) { - filters.forEach { filter -> - when (filter) { - is OrderBy -> { - order = filter.toUriPartWithGenres() - } - } - } - "$baseUrl/tags/${genres.dropLast(1)}&sort=manga$order?offset=${20 * (page - 1)}" - } else { - filters.forEach { filter -> - when (filter) { - is OrderBy -> { - order = filter.toUriPartWithoutGenres() - } - } - } - "$baseUrl/$order?offset=${20 * (page - 1)}" - } - } - return GET(url, headers) - } - - override fun popularMangaSelector() = ".content_row" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = ".content_row:not(:has(div.item:containsOwn(Тип)))" - - private fun String.getHQThumbnail(): String? { - val isExHenManga = this.contains("/manganew_thumbs_blur/") - val regex = "(?<=/)manganew_thumbs\\w*?(?=/)".toRegex(RegexOption.IGNORE_CASE) - return this.replace(regex, "showfull_retina/manga") - .replace("_".plus(URL(baseUrl).host), "_hentaichan.ru") // domain-related replacing for very old mangas - .plus(if (isExHenManga) { "#" } else { "" }) // # for later so we know what type manga is it - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").first().attr("src").getHQThumbnail() - - val urlElem = element.select("h2 > a").first() - manga.setUrlWithoutDomain(urlElem.attr("href")) - manga.title = urlElem.text() - - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "#pagination > a:contains(Вперед)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = "#nextlink, ${popularMangaNextPageSelector()}" - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select(".row .item2 h2")[1].text() - manga.genre = document.select(".sidetag > a:eq(2)").joinToString { it.text() } - manga.description = document.select("#description").text() - manga.thumbnail_url = document.select("img#cover").attr("abs:src").getHQThumbnail() - return manga - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return client.newCall(chapterListRequest(manga)) - .asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - // Error message for exceeding last page - if (response.code() == 404) - Observable.just( - listOf( - SChapter.create().apply { - url = manga.url - name = "Chapter" - chapter_number = 1f - } - ) - ) - else throw Exception("HTTP error ${response.code()}") - } - } - .map { response -> - chapterListParse(response) - } - } - - override fun chapterListRequest(manga: SManga): Request { - val url = baseUrl + if (manga.thumbnail_url?.endsWith("#") == true) { - manga.url - } else { - manga.url.replace("/manga/", "/related/") - } - return (GET(url, headers)) - } - - override fun chapterListSelector() = ".related" - - override fun chapterListParse(response: Response): List<SChapter> { - val responseUrl = response.request().url().toString() - val document = response.asJsoup() - - // exhentai chapter - if (responseUrl.contains("/manga/")) { - val chap = SChapter.create() - chap.setUrlWithoutDomain(responseUrl) - chap.name = document.select("a.title_top_a").text() - chap.chapter_number = 1F - - val date = document.select("div.row4_right b")?.text()?.let { - SimpleDateFormat("dd MMMM yyyy", Locale("ru")).parse(it)?.time ?: 0 - } ?: 0 - chap.date_upload = date - return listOf(chap) - } - - // one chapter, nothing related - if (document.select("#right > div:nth-child(4)").text().contains(" похожий на ")) { - val chap = SChapter.create() - chap.setUrlWithoutDomain(document.select("#left > div > a").attr("href")) - chap.name = document.select("#right > div:nth-child(4)").text() - .split(" похожий на ")[1] - .replace("\\\"", "\"") - .replace("\\'", "'") - chap.chapter_number = 1F - chap.date_upload = Date().time // setting to current date because of a sorting in the "Recent updates" section - return listOf(chap) - } - - // has related chapters - val result = mutableListOf<SChapter>() - result.addAll( - document.select(chapterListSelector()).map { - chapterFromElement(it) - } - ) - - var url = document.select("div#pagination_related a:contains(Вперед)").attr("href") - while (url.isNotBlank()) { - val get = GET( - "${response.request().url()}/$url", - headers = headers - ) - val nextPage = client.newCall(get).execute().asJsoup() - result.addAll( - nextPage.select(chapterListSelector()).map { - chapterFromElement(it) - } - ) - - url = nextPage.select("div#pagination_related a:contains(Вперед)").attr("href") - } - - return result.reversed() - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.select("h2 a").attr("href")) - val chapterName = element.select("h2 a").attr("title") - chapter.name = chapterName - chapter.chapter_number = "(глава\\s|часть\\s)(\\d+)".toRegex(RegexOption.IGNORE_CASE).find(chapterName)?.groupValues?.get(2)?.toFloat() ?: -1F - chapter.date_upload = Date().time // setting to current date because of a sorting in the "Recent updates" section - return chapter - } - - override fun pageListRequest(chapter: SChapter): Request { - val url = if (chapter.url.contains("/manga/")) { - exhentaiBaseUrl + chapter.url.replace("/manga/", "/online/") + "?development_access=true" - } else { - baseUrl + chapter.url - } - return GET(url, Headers.Builder().add("Accept", "image/webp,image/apng").build()) - } - - override fun imageUrlParse(document: Document) = throw Exception("Not Used") - - private val gson = Gson() - - private fun Document.parseJsonArray(): JsonArray { - val imgScript = this.select("script:containsData(fullimg)").first().toString() - val imgString = imgScript.substring(imgScript.indexOf('{'), imgScript.lastIndexOf('}') + 1).replace(""", "\"") - return gson.fromJson<JsonObject>(imgString)["fullimg"].array - } - - override fun pageListParse(document: Document): List<Page> { - return document.parseJsonArray().mapIndexed { index, imageUrl -> - Page(index, imageUrl = imageUrl.string.replace(".gif.webp", ".gif")) - } - } - - private class Genre(val id: String, @SuppressLint("DefaultLocale") name: String = id.replace('_', ' ').capitalize()) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres) - private class OrderBy : UriPartFilter( - "Сортировка", - arrayOf("Дата", "Популярность", "Алфавит"), - arrayOf("&n=dateasc" to "", "&n=favasc" to "&n=favdesc", "&n=abcdesc" to "&n=abcasc"), - arrayOf("manga/new&n=dateasc" to "manga/new", "manga/new&n=favasc" to "mostfavorites&sort=manga", "manga/new&n=abcdesc" to "manga/new&n=abcasc") - ) - - private open class UriPartFilter(displayName: String, sortNames: Array<String>, val withGenres: Array<Pair<String, String>>, val withoutGenres: Array<Pair<String, String>>) : - Filter.Sort(displayName, sortNames, Selection(1, false)) { - fun toUriPartWithGenres() = if (state!!.ascending) withGenres[state!!.index].first else withGenres[state!!.index].second - fun toUriPartWithoutGenres() = if (state!!.ascending) withoutGenres[state!!.index].first else withoutGenres[state!!.index].second - } - - override fun getFilterList() = FilterList( - OrderBy(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("3D"), - Genre("action"), - Genre("ahegao"), - Genre("bdsm"), - Genre("corruption"), - Genre("foot_fetish"), - Genre("footfuck"), - Genre("gender_bender"), - Genre("live"), - Genre("lolcon"), - Genre("megane"), - Genre("mind_break"), - Genre("monstergirl"), - Genre("netorare"), - Genre("netori"), - Genre("nipple_penetration"), - Genre("paizuri_(titsfuck)"), - Genre("rpg"), - Genre("scat"), - Genre("shemale"), - Genre("shooter"), - Genre("simulation"), - Genre("skinsuit"), - Genre("tomboy"), - Genre("x-ray"), - Genre("алкоголь"), - Genre("анал"), - Genre("андроид"), - Genre("анилингус"), - Genre("аркада"), - Genre("арт"), - Genre("бабушка"), - Genre("без_текста"), - Genre("без_трусиков"), - Genre("без_цензуры"), - Genre("беременность"), - Genre("бикини"), - Genre("близнецы"), - Genre("боди-арт"), - Genre("больница"), - Genre("большая_грудь"), - Genre("большие_попки"), - Genre("буккаке"), - Genre("в_ванной"), - Genre("в_общественном_месте"), - Genre("в_первый_раз"), - Genre("в_цвете"), - Genre("в_школе"), - Genre("вампиры"), - Genre("веб"), - Genre("вебкам"), - Genre("вибратор"), - Genre("визуальная_новелла"), - Genre("внучка"), - Genre("волосатые_женщины"), - Genre("гаремник"), - Genre("гипноз"), - Genre("глубокий_минет"), - Genre("горячий_источник"), - Genre("грудастая_лоли"), - Genre("групповой_секс"), - Genre("гяру_и_гангуро"), - Genre("двойное_проникновение"), - Genre("девочки_волшебницы"), - Genre("девушка_туалет"), - Genre("демоны"), - Genre("дилдо"), - Genre("дочь"), - Genre("драма"), - Genre("дыра_в_стене"), - Genre("жестокость"), - Genre("за_деньги"), - Genre("зомби"), - Genre("зрелые_женщины"), - Genre("измена"), - Genre("изнасилование"), - Genre("инопланетяне"), - Genre("инцест"), - Genre("исполнение_желаний"), - Genre("камера"), - Genre("квест"), - Genre("колготки"), - Genre("комиксы"), - Genre("косплей"), - Genre("кузина"), - Genre("куннилингус"), - Genre("купальники"), - Genre("латекс_и_кожа"), - Genre("магия"), - Genre("маленькая_грудь"), - Genre("мастурбация"), - Genre("мать"), - Genre("мейдочки"), - Genre("мерзкий_дядька"), - Genre("много_девушек"), - Genre("молоко"), - Genre("монстры"), - Genre("мочеиспускание"), - Genre("мужская_озвучка"), - Genre("мужчина_крепкого_телосложения"), - Genre("мускулистые_женщины"), - Genre("на_природе"), - Genre("наблюдение"), - Genre("непрямой_инцест"), - Genre("обмен_партнерами"), - Genre("обмен_телами"), - Genre("огромная_грудь"), - Genre("огромный_член"), - Genre("остановка_времени"), - Genre("парень_пассив"), - Genre("переодевание"), - Genre("песочница"), - Genre("племянница"), - Genre("пляж"), - Genre("подглядывание"), - Genre("подчинение"), - Genre("похищение"), - Genre("принуждение"), - Genre("прозрачная_одежда"), - Genre("проникновение_в_матку"), - Genre("психические_отклонения"), - Genre("публично"), - Genre("рабыни"), - Genre("романтика"), - Genre("сверхъестественное"), - Genre("секс_игрушки"), - Genre("сестра"), - Genre("сетакон"), - Genre("спортивная_форма"), - Genre("спящие"), - Genre("страпон"), - Genre("темнокожие"), - Genre("тентакли"), - Genre("толстушки"), - Genre("трап"), - Genre("тётя"), - Genre("учитель_и_ученик"), - Genre("ушастые"), - Genre("фантазии"), - Genre("фантастика"), - Genre("фемдом"), - Genre("фестиваль"), - Genre("фистинг"), - Genre("фурри"), - Genre("футанари"), - Genre("футанари_имеет_парня"), - Genre("фэнтези"), - Genre("хоррор"), - Genre("цундере"), - Genre("чикан"), - Genre("чирлидеры"), - Genre("чулки"), - Genre("школьники"), - Genre("школьницы"), - Genre("школьный_купальник"), - Genre("эксгибиционизм"), - Genre("эльфы"), - Genre("эччи"), - Genre("юмор"), - Genre("юри"), - Genre("яндере"), - Genre("яой") - ) -} diff --git a/src/ru/libmanga/AndroidManifest.xml b/src/ru/libmanga/AndroidManifest.xml deleted file mode 100644 index b11155115..000000000 --- a/src/ru/libmanga/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".ru.libmanga.LibMangaActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <!-- LibMangaActivity sites can be added here. --> - <data - android:host="mangalib.me" - android:pathPattern="/..*/v..*/c..*/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/ru/libmanga/build.gradle b/src/ru/libmanga/build.gradle deleted file mode 100644 index c33e750c8..000000000 --- a/src/ru/libmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaLib' - pkgNameSuffix = 'ru.libmanga' - extClass = '.LibManga' - extVersionCode = 39 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/libmanga/res/mipmap-hdpi/ic_launcher.png b/src/ru/libmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6d0ce00bb..000000000 Binary files a/src/ru/libmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/libmanga/res/mipmap-mdpi/ic_launcher.png b/src/ru/libmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c016c115d..000000000 Binary files a/src/ru/libmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/libmanga/res/mipmap-xhdpi/ic_launcher.png b/src/ru/libmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a582a6406..000000000 Binary files a/src/ru/libmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/libmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/libmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 93a37c1cf..000000000 Binary files a/src/ru/libmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/libmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/libmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 32199db52..000000000 Binary files a/src/ru/libmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/libmanga/res/web_hi_res_512.png b/src/ru/libmanga/res/web_hi_res_512.png deleted file mode 100644 index a83690e51..000000000 Binary files a/src/ru/libmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/libmanga/src/eu/kanade/tachiyomi/extension/ru/libmanga/LibManga.kt b/src/ru/libmanga/src/eu/kanade/tachiyomi/extension/ru/libmanga/LibManga.kt deleted file mode 100644 index c521182ef..000000000 --- a/src/ru/libmanga/src/eu/kanade/tachiyomi/extension/ru/libmanga/LibManga.kt +++ /dev/null @@ -1,816 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.libmanga - -import android.app.Application -import android.content.SharedPreferences -import androidx.preference.ListPreference -import androidx.preference.PreferenceScreen -import com.github.salomonbrys.kotson.array -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullArray -import com.github.salomonbrys.kotson.nullInt -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.github.salomonbrys.kotson.toMap -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Locale -import android.support.v7.preference.ListPreference as LegacyListPreference -import android.support.v7.preference.PreferenceScreen as LegacyPreferenceScreen - -class LibManga : ConfigurableSource, HttpSource() { - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_${id}_2", 0x0000) - } - - override val name: String = "Mangalib" - - override val lang = "ru" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override val baseUrl: String = "https://mangalib.me" - - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("Accept", "image/webp,*/*;q=0.8") - add("Referer", baseUrl) - } - - private val jsonParser = JsonParser() - - override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) - - private val latestUpdatesSelector = "div.updates__item" - - override fun latestUpdatesParse(response: Response): MangasPage { - val elements = response.asJsoup().select(latestUpdatesSelector) - val latestMangas = elements?.map { latestUpdatesFromElement(it) } - if (latestMangas != null) - return MangasPage(latestMangas, false) // TODO: use API - return MangasPage(emptyList(), false) - } - - private fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("div.cover").first().let { img -> - manga.thumbnail_url = baseUrl + img.attr("data-src").substringAfter(baseUrl) - .replace("cover_thumb", "cover_250x350") - } - - element.select("a").first().let { link -> - manga.setUrlWithoutDomain(link.attr("href")) - manga.title = element.select("h4").first().text() - } - return manga - } - - private var csrfToken: String = "" - - private fun catalogHeaders() = Headers.Builder() - .apply { - add("Accept", "application/json, text/plain, */*") - add("X-Requested-With", "XMLHttpRequest") - add("x-csrf-token", csrfToken) - } - .build() - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/login", headers) - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - if (csrfToken.isEmpty()) { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .flatMap { response -> - // Obtain token - val resBody = response.body()!!.string() - csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value - return@flatMap fetchPopularMangaFromApi(page) - } - } - return fetchPopularMangaFromApi(page) - } - - private fun fetchPopularMangaFromApi(page: Int): Observable<MangasPage> { - return client.newCall(POST("$baseUrl/filterlist?dir=desc&sort=views&page=$page", catalogHeaders())) - .asObservableSuccess() - .map { response -> - popularMangaParse(response) - } - } - - override fun popularMangaParse(response: Response): MangasPage { - val resBody = response.body()!!.string() - val result = jsonParser.parse(resBody).obj - val items = result["items"] - val popularMangas = items["data"].nullArray?.map { popularMangaFromElement(it) } - - if (popularMangas != null) { - val hasNextPage = items["next_page_url"].nullString != null - return MangasPage(popularMangas, hasNextPage) - } - return MangasPage(emptyList(), false) - } - - private fun popularMangaFromElement(el: JsonElement) = SManga.create().apply { - val slug = el["slug"].string - val cover = el["cover"].string - title = el["name"].string - thumbnail_url = "$baseUrl/uploads/cover/$slug/cover/${cover}_250x350.jpg" - url = "/$slug" - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - if (document.select("body[data-page=home]").isNotEmpty()) - throw Exception("Can't open manga. Try log in via WebView") - - val manga = SManga.create() - - if (document.html().contains("Манга удалена по просьбе правообладателей") || - document.html().contains("Данный тайтл лицензирован на территории РФ.") - ) { - manga.status = SManga.LICENSED - return manga - } - - val body = document.select("div.media-info-list").first() - val rawCategory = body.select("div.media-info-list__title:contains(Тип) + div").text() - - val category = when { - rawCategory == "Комикс западный" -> "комикс" - rawCategory.isNotBlank() -> rawCategory.toLowerCase(Locale.ROOT) - else -> "манга" - } - - val genres = document.select(".media-tags > a").map { it.text() } - manga.thumbnail_url = document.select(".media-sidebar__cover > img").attr("src") - manga.author = body.select("div.media-info-list__title:contains(Автор) + div").text() - manga.artist = body.select("div.media-info-list__title:contains(Художник) + div").text() - manga.status = when ( - body.select("div.media-info-list__title:contains(Статус перевода) + div") - .text() - .toLowerCase(Locale.ROOT) - ) { - "продолжается" -> SManga.ONGOING - "завершен" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - manga.genre = genres.plusElement(category).joinToString { it.trim() } - manga.description = document.select(".media-description__text").text() - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - val dataStr = response - .asJsoup() - .toString() - .substringAfter("window.__DATA__ = ") - .substringBefore("window._SITE_COLOR_") - .substringBeforeLast(";") - - val data = jsonParser.parse(dataStr).obj - val chaptersList = data["chapters"]["list"].nullArray - val slug = data["manga"]["slug"].string - val branches = data["chapters"]["branches"].array.reversed() - val sortingList = preferences.getString(SORTING_PREF, "ms_mixing") - - val chapters: List<SChapter>? = if (branches.isNotEmpty() && !sortingList.equals("ms_mixing")) { - sortChaptersByTranslator(sortingList, chaptersList, slug, branches) - } else { - chaptersList - ?.filter { it["status"].nullInt != 2 } - ?.map { chapterFromElement(it, sortingList, slug) } - } - - return chapters ?: emptyList() - } - - private fun sortChaptersByTranslator - (sortingList: String?, chaptersList: JsonArray?, slug: String, branches: List<JsonElement>): List<SChapter>? { - var chapters: List<SChapter>? = null - when (sortingList) { - "ms_combining" -> { - val tempChaptersList = mutableListOf<SChapter>() - for (currentBranch in branches.withIndex()) { - val teamId = branches[currentBranch.index]["id"].int - chapters = chaptersList - ?.filter { it["branch_id"].int == teamId && it["status"].nullInt != 2 } - ?.map { chapterFromElement(it, sortingList, slug, teamId, branches) } - chapters?.let { tempChaptersList.addAll(it) } - } - chapters = tempChaptersList - } - "ms_largest" -> { - val sizesChaptersLists = mutableListOf<Int>() - for (currentBranch in branches.withIndex()) { - val teamId = branches[currentBranch.index]["id"].int - val chapterSize = chaptersList - ?.filter { it["branch_id"].int == teamId }!!.size - sizesChaptersLists.add(chapterSize) - } - val max = sizesChaptersLists.indexOfFirst { it == sizesChaptersLists.maxOrNull() ?: 0 } - val teamId = branches[max]["id"].int - - chapters = chaptersList - ?.filter { it["branch_id"].int == teamId && it["status"].nullInt != 2 } - ?.map { chapterFromElement(it, sortingList, slug, teamId, branches) } - } - "ms_active" -> { - for (currentBranch in branches.withIndex()) { - val teams = branches[currentBranch.index]["teams"].array - for (currentTeam in teams.withIndex()) { - if (teams[currentTeam.index]["is_active"].int == 1) { - val teamId = branches[currentBranch.index]["id"].int - chapters = chaptersList - ?.filter { it["branch_id"].int == teamId && it["status"].nullInt != 2 } - ?.map { chapterFromElement(it, sortingList, slug, teamId, branches) } - break - } - } - } - chapters ?: throw Exception("Активный перевод не назначен на сайте") - } - } - - return chapters - } - - private fun chapterFromElement - (chapterItem: JsonElement, sortingList: String?, slug: String, teamIdParam: Int? = null, branches: List<JsonElement>? = null): SChapter { - val chapter = SChapter.create() - - val volume = chapterItem["chapter_volume"].int - val number = chapterItem["chapter_number"].string - val teamId = if (teamIdParam != null) "?bid=$teamIdParam" else "" - - val url = "$baseUrl/$slug/v$volume/c$number$teamId" - - chapter.setUrlWithoutDomain(url) - - val nameChapter = chapterItem["chapter_name"].nullString - val fullNameChapter = "Том $volume. Глава $number" - - if (!sortingList.equals("ms_mixing")) { - chapter.scanlator = branches?.let { getScanlatorTeamName(it, chapterItem) } ?: chapterItem["username"].string - } - chapter.name = if (nameChapter.isNullOrBlank()) fullNameChapter else "$fullNameChapter - $nameChapter" - chapter.date_upload = SimpleDateFormat("yyyy-MM-dd", Locale.US) - .parse(chapterItem["chapter_created_at"].string.substringBefore(" "))?.time ?: 0L - - return chapter - } - - private fun getScanlatorTeamName(branches: List<JsonElement>, chapterItem: JsonElement): String? { - var scanlatorData: String? = null - for (currentBranch in branches.withIndex()) { - val branch = branches[currentBranch.index] - val teams = branch["teams"].array - if (chapterItem["branch_id"].int == branch["id"].int) { - for (currentTeam in teams.withIndex()) { - val team = teams[currentTeam.index] - val scanlatorId = chapterItem["chapter_scanlator_id"].int - scanlatorData = if ((scanlatorId == team["id"].int) || - (scanlatorId == 0 && team["is_active"].int == 1) - ) team["name"].string else branch["teams"][0]["name"].string - } - } - } - return scanlatorData - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - """Глава\s(\d+)""".toRegex().find(chapter.name)?.let { - val number = it.groups[1]?.value!! - chapter.chapter_number = number.toFloat() - } - } - - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - - val redirect = document.html() - if (!redirect.contains("window.__info")) { - if (redirect.contains("hold-transition login-page")) { - throw Exception("Для просмотра 18+ контента необходима авторизация через WebView") - } else if (redirect.contains("header__logo")) { - throw Exception("Лицензировано - Главы не доступны") - } - } - - val chapInfo = document - .select("script:containsData(window.__info)") - .first() - .html() - .split("window.__info = ") - .last() - .trim() - .split(";") - .first() - - val chapInfoJson = jsonParser.parse(chapInfo).obj - val servers = chapInfoJson["servers"].asJsonObject.toMap() - val defaultServer: String = chapInfoJson["img"]["server"].string - val autoServer = setOf("secondary", "fourth", defaultServer, "compress") - val imgUrl: String = chapInfoJson["img"]["url"].string - - val serverToUse = when (this.server) { - null -> autoServer - "auto" -> autoServer - else -> listOf(this.server) - } - - // Get pages - val pagesArr = document - .select("script:containsData(window.__pg)") - .first() - .html() - .trim() - .removePrefix("window.__pg = ") - .removeSuffix(";") - - val pagesJson = jsonParser.parse(pagesArr).array - val pages = mutableListOf<Page>() - - pagesJson.forEach { page -> - val keys = servers.keys.filter { serverToUse.indexOf(it) >= 0 }.sortedBy { serverToUse.indexOf(it) } - val serversUrls = keys.map { - servers[it]?.string + imgUrl + page["u"].string - }.joinToString(separator = ",,") { it } - pages.add(Page(page["p"].int, serversUrls)) - } - - return pages - } - - private fun checkImage(url: String): Boolean { - val response = client.newCall(Request.Builder().url(url).head().headers(headers).build()).execute() - return response.isSuccessful && (response.header("Content-Length")?.toInt()!! > 320) - } - - override fun fetchImageUrl(page: Page): Observable<String> { - if (page.imageUrl != null) { - return Observable.just(page.imageUrl) - } - - val urls = page.url.split(",,") - if (urls.size == 1) { - return Observable.just(urls[0]) - } - - return Observable.from(urls).filter { checkImage(it) }.first() - } - - override fun imageUrlParse(response: Response): String = "" - - private fun searchMangaByIdRequest(id: String): Request { - return GET("$baseUrl/$id", headers) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_SLUG_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_SLUG_SEARCH) - client.newCall(searchMangaByIdRequest(realQuery)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = "/$realQuery" - MangasPage(listOf(details), false) - } - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (csrfToken.isEmpty()) { - val tokenResponse = client.newCall(popularMangaRequest(page)).execute() - val resBody = tokenResponse.body()!!.string() - csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value - } - val url = HttpUrl.parse("$baseUrl/filterlist?page=$page")!!.newBuilder() - if (query.isNotEmpty()) { - url.addQueryParameter("name", query) - } - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is CategoryList -> filter.state.forEach { category -> - if (category.state) { - url.addQueryParameter("types[]", category.id) - } - } - is FormatList -> filter.state.forEach { forma -> - if (forma.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(if (forma.isIncluded()) "format[include][]" else "format[exclude][]", forma.id) - } - } - is StatusList -> filter.state.forEach { status -> - if (status.state) { - url.addQueryParameter("status[]", status.id) - } - } - is StatusTitleList -> filter.state.forEach { title -> - if (title.state) { - url.addQueryParameter("manga_status[]", title.id) - } - } - is GenreList -> filter.state.forEach { genre -> - if (genre.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(if (genre.isIncluded()) "genres[include][]" else "genres[exclude][]", genre.id) - } - } - is OrderBy -> { - url.addQueryParameter("dir", if (filter.state!!.ascending) "asc" else "desc") - url.addQueryParameter("sort", arrayOf("rate", "name", "views", "created_at", "last_chapter_at", "chap_count")[filter.state!!.index]) - } - is AgeList -> filter.state.forEach { age -> - if (age.state) { - url.addQueryParameter("caution[]", age.id) - } - } - is TagList -> filter.state.forEach { tag -> - if (tag.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(if (tag.isIncluded()) "tags[include][]" else "tags[exclude][]", tag.id) - } - } - } - } - return POST(url.toString(), catalogHeaders()) - } - - // Hack search method to add some results from search popup - override fun searchMangaParse(response: Response): MangasPage { - val searchRequest = response.request().url().queryParameter("name") - val mangas = mutableListOf<SManga>() - - if (!searchRequest.isNullOrEmpty()) { - val popupSearchHeaders = headers - .newBuilder() - .add("Accept", "application/json, text/plain, */*") - .add("X-Requested-With", "XMLHttpRequest") - .build() - - // +200ms - val popup = client.newCall( - GET("$baseUrl/search?query=$searchRequest", popupSearchHeaders) - ) - .execute().body()!!.string() - - val jsonList = jsonParser.parse(popup).array - jsonList.forEach { - mangas.add(popularMangaFromElement(it)) - } - } - val searchedMangas = popularMangaParse(response) - - // Filtered out what find in popup search - mangas.addAll( - searchedMangas.mangas.filter { search -> - mangas.find { search.title == it.title } == null - } - ) - - return MangasPage(mangas, searchedMangas.hasNextPage) - } - - private class SearchFilter(name: String, val id: String) : Filter.TriState(name) - private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name) - - private class CategoryList(categories: List<CheckFilter>) : Filter.Group<CheckFilter>("Тип", categories) - private class FormatList(formas: List<SearchFilter>) : Filter.Group<SearchFilter>("Формат выпуска", formas) - private class StatusList(statuses: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус перевода", statuses) - private class StatusTitleList(titles: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус тайтла", titles) - private class GenreList(genres: List<SearchFilter>) : Filter.Group<SearchFilter>("Жанры", genres) - private class TagList(tags: List<SearchFilter>) : Filter.Group<SearchFilter>("Теги", tags) - private class AgeList(ages: List<CheckFilter>) : Filter.Group<CheckFilter>("Возрастное ограничение", ages) - - override fun getFilterList() = FilterList( - OrderBy(), - CategoryList(getCategoryList()), - FormatList(getFormatList()), - GenreList(getGenreList()), - TagList(getTagList()), - StatusList(getStatusList()), - StatusTitleList(getStatusTitleList()), - AgeList(getAgeList()) - ) - - private class OrderBy : Filter.Sort( - "Сортировка", - arrayOf("Рейтинг", "Имя", "Просмотры", "Дате добавления", "Дате обновления", "Кол-во глав"), - Selection(0, false) - ) - - /* - * Use console - * Object.entries(__FILTER_ITEMS__.types).map(([k, v]) => `SearchFilter("${v.label}", "${v.id}")`).join(',\n') - * on /manga-list - */ - private fun getCategoryList() = listOf( - CheckFilter("Манга", "1"), - CheckFilter("OEL-манга", "4"), - CheckFilter("Манхва", "5"), - CheckFilter("Маньхуа", "6"), - CheckFilter("Руманга", "8"), - CheckFilter("Комикс западный", "9") - ) - - private fun getFormatList() = listOf( - SearchFilter("4-кома (Ёнкома)", "1"), - SearchFilter("Сборник", "2"), - SearchFilter("Додзинси", "3"), - SearchFilter("Сингл", "4"), - SearchFilter("В цвете", "5"), - SearchFilter("Веб", "6") - ) - - /* - * Use console - * Object.entries(__FILTER_ITEMS__.status).map(([k, v]) => `SearchFilter("${v.label}", "${v.id}")`).join(',\n') - * on /manga-list - */ - private fun getStatusList() = listOf( - CheckFilter("Продолжается", "1"), - CheckFilter("Завершен", "2"), - CheckFilter("Заморожен", "3"), - CheckFilter("Заброшен", "4") - ) - - private fun getStatusTitleList() = listOf( - CheckFilter("Онгоинг", "1"), - CheckFilter("Завершён", "2"), - CheckFilter("Анонс", "3"), - CheckFilter("Приостановлен", "4"), - CheckFilter("Выпуск прекращён", "5"), - ) - - /* - * Use console - * __FILTER_ITEMS__.genres.map(it => `SearchFilter("${it.name}", "${it.id}")`).join(',\n') - * on /manga-list - */ - private fun getGenreList() = listOf( - SearchFilter("арт", "32"), - SearchFilter("боевик", "34"), - SearchFilter("боевые искусства", "35"), - SearchFilter("вампиры", "36"), - SearchFilter("гарем", "37"), - SearchFilter("гендерная интрига", "38"), - SearchFilter("героическое фэнтези", "39"), - SearchFilter("детектив", "40"), - SearchFilter("дзёсэй", "41"), - SearchFilter("драма", "43"), - SearchFilter("игра", "44"), - SearchFilter("исекай", "79"), - SearchFilter("история", "45"), - SearchFilter("киберпанк", "46"), - SearchFilter("кодомо", "76"), - SearchFilter("комедия", "47"), - SearchFilter("махо-сёдзё", "48"), - SearchFilter("меха", "49"), - SearchFilter("мистика", "50"), - SearchFilter("научная фантастика", "51"), - SearchFilter("омегаверс", "77"), - SearchFilter("повседневность", "52"), - SearchFilter("постапокалиптика", "53"), - SearchFilter("приключения", "54"), - SearchFilter("психология", "55"), - SearchFilter("романтика", "56"), - SearchFilter("самурайский боевик", "57"), - SearchFilter("сверхъестественное", "58"), - SearchFilter("сёдзё", "59"), - SearchFilter("сёдзё-ай", "60"), - SearchFilter("сёнэн", "61"), - SearchFilter("сёнэн-ай", "62"), - SearchFilter("спорт", "63"), - SearchFilter("сэйнэн", "64"), - SearchFilter("трагедия", "65"), - SearchFilter("триллер", "66"), - SearchFilter("ужасы", "67"), - SearchFilter("фантастика", "68"), - SearchFilter("фэнтези", "69"), - SearchFilter("школа", "70"), - SearchFilter("эротика", "71"), - SearchFilter("этти", "72"), - SearchFilter("юри", "73"), - SearchFilter("яой", "74") - ) - - private fun getTagList() = listOf( - SearchFilter("Азартные игры", "304"), - SearchFilter("Алхимия", "225"), - SearchFilter("Ангелы", "226"), - SearchFilter("Антигерой", "175"), - SearchFilter("Антиутопия", "227"), - SearchFilter("Апокалипсис", "228"), - SearchFilter("Армия", "229"), - SearchFilter("Артефакты", "230"), - SearchFilter("Боги", "215"), - SearchFilter("Бои на мечах", "231"), - SearchFilter("Борьба за власть", "231"), - SearchFilter("Брат и сестра", "233"), - SearchFilter("Будущее", "234"), - SearchFilter("Ведьма", "338"), - SearchFilter("Вестерн", "235"), - SearchFilter("Видеоигры", "185"), - SearchFilter("Виртуальная реальность", "195"), - SearchFilter("Владыка демонов", "236"), - SearchFilter("Военные", "179"), - SearchFilter("Война", "237"), - SearchFilter("Волшебники / маги", "281"), - SearchFilter("Волшебные существа", "239"), - SearchFilter("Воспоминания из другого мира", "240"), - SearchFilter("Выживание", "193"), - SearchFilter("ГГ женщина", "243"), - SearchFilter("ГГ имба", "291"), - SearchFilter("ГГ мужчина", "244"), - SearchFilter("Геймеры", "241"), - SearchFilter("Гильдии", "242"), - SearchFilter("Глупый ГГ", "297"), - SearchFilter("Гоблины", "245"), - SearchFilter("Горничные", "169"), - SearchFilter("Гяру", "178"), - SearchFilter("Демоны", "151"), - SearchFilter("Драконы", "246"), - SearchFilter("Дружба", "247"), - SearchFilter("Жестокий мир", "249"), - SearchFilter("Животные компаньоны", "250"), - SearchFilter("Завоевание мира", "251"), - SearchFilter("Зверолюди", "162"), - SearchFilter("Злые духи", "252"), - SearchFilter("Зомби", "149"), - SearchFilter("Игровые элементы", "253"), - SearchFilter("Империи", "254"), - SearchFilter("Квесты", "255"), - SearchFilter("Космос", "256"), - SearchFilter("Кулинария", "152"), - SearchFilter("Культивация", "160"), - SearchFilter("Легендарное оружие", "257"), - SearchFilter("Лоли", "187"), - SearchFilter("Магическая академия", "258"), - SearchFilter("Магия", "168"), - SearchFilter("Мафия", "172"), - SearchFilter("Медицина", "153"), - SearchFilter("Месть", "259"), - SearchFilter("Монстр Девушки", "188"), - SearchFilter("Монстры", "189"), - SearchFilter("Музыка", "190"), - SearchFilter("Навыки / способности", "260"), - SearchFilter("Насилие / жестокость", "262"), - SearchFilter("Наёмники", "261"), - SearchFilter("Нежить", "263"), - SearchFilter("Ниндая", "180"), - SearchFilter("Обратный Гарем", "191"), - SearchFilter("Огнестрельное оружие", "264"), - SearchFilter("Офисные Работники", "181"), - SearchFilter("Пародия", "265"), - SearchFilter("Пираты", "340"), - SearchFilter("Подземелья", "266"), - SearchFilter("Политика", "267"), - SearchFilter("Полиция", "182"), - SearchFilter("Преступники / Криминал", "186"), - SearchFilter("Призраки / Духи", "177"), - SearchFilter("Путешествие во времени", "194"), - SearchFilter("Разумные расы", "268"), - SearchFilter("Ранги силы", "248"), - SearchFilter("Реинкарнация", "148"), - SearchFilter("Роботы", "269"), - SearchFilter("Рыцари", "270"), - SearchFilter("Самураи", "183"), - SearchFilter("Система", "271"), - SearchFilter("Скрытие личности", "273"), - SearchFilter("Спасение мира", "274"), - SearchFilter("Спортивное тело", "334"), - SearchFilter("Средневековье", "173"), - SearchFilter("Стимпанк", "272"), - SearchFilter("Супергерои", "275"), - SearchFilter("Традиционные игры", "184"), - SearchFilter("Умный ГГ", "302"), - SearchFilter("Учитель / ученик", "276"), - SearchFilter("Философия", "277"), - SearchFilter("Хикикомори", "166"), - SearchFilter("Холодное оружие", "278"), - SearchFilter("Шантаж", "279"), - SearchFilter("Эльфы", "216"), - SearchFilter("Якудза", "164"), - SearchFilter("Япония", "280") - - ) - - private fun getAgeList() = listOf( - CheckFilter("Отсутствует", "0"), - CheckFilter("16+", "1"), - CheckFilter("18+", "2") - ) - - companion object { - const val PREFIX_SLUG_SEARCH = "slug:" - private const val SERVER_PREF = "MangaLibImageServer" - private const val SERVER_PREF_Title = "Сервер изображений" - - private const val SORTING_PREF = "MangaLibSorting" - private const val SORTING_PREF_Title = "Способ выбора переводчиков" - } - - private var server: String? = preferences.getString(SERVER_PREF, null) - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val serverPref = ListPreference(screen.context).apply { - key = SERVER_PREF - title = SERVER_PREF_Title - entries = arrayOf("Основной", "Второй (тестовый)", "Третий (эконом трафика)", "Авто") - entryValues = arrayOf("secondary", "fourth", "compress", "auto") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - server = newValue.toString() - true - } - } - - val sortingPref = ListPreference(screen.context).apply { - key = SORTING_PREF - title = SORTING_PREF_Title - entries = arrayOf( - "Полный список (без повторных переводов)", "Все переводы (друг за другом)", - "Наибольшее число глав", "Активный перевод" - ) - entryValues = arrayOf("ms_mixing", "ms_combining", "ms_largest", "ms_active") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - preferences.edit().putString(SORTING_PREF, selected).commit() - } - } - - screen.addPreference(sortingPref) - screen.addPreference(serverPref) - } - - override fun setupPreferenceScreen(screen: LegacyPreferenceScreen) { - val serverPref = LegacyListPreference(screen.context).apply { - key = SERVER_PREF - title = SERVER_PREF_Title - entries = arrayOf("Основной", "Второй (тестовый)", "Третий (эконом трафика)", "Авто") - entryValues = arrayOf("secondary", "fourth", "compress", "auto") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - server = newValue.toString() - true - } - } - - val sortingPref = LegacyListPreference(screen.context).apply { - key = SORTING_PREF - title = SORTING_PREF_Title - entries = arrayOf( - "Полный список (без повторных переводов)", "Все переводы (друг за другом)", - "Наибольшее число глав", "Активный перевод" - ) - entryValues = arrayOf("ms_mixing", "ms_combining", "ms_largest", "ms_active") - summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - preferences.edit().putString(SORTING_PREF, selected).commit() - } - } - - screen.addPreference(sortingPref) - screen.addPreference(serverPref) - } -} diff --git a/src/ru/libmanga/src/eu/kanade/tachiyomi/extension/ru/libmanga/LibMangaActivity.kt b/src/ru/libmanga/src/eu/kanade/tachiyomi/extension/ru/libmanga/LibMangaActivity.kt deleted file mode 100644 index c9a5f6bd5..000000000 --- a/src/ru/libmanga/src/eu/kanade/tachiyomi/extension/ru/libmanga/LibMangaActivity.kt +++ /dev/null @@ -1,41 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.libmanga - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://mangalib.me/xxx intents and redirects them to - * the main tachiyomi process. The idea is to not install the intent filter unless - * you have this extension installed, but still let the main tachiyomi app control - * things. - */ -class LibMangaActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 0) { - val titleid = pathSegments[0] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${LibManga.PREFIX_SLUG_SEARCH}$titleid") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("LibMangaActivity", e.toString()) - } - } else { - Log.e("LibMangaActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/ru/mangachan/AndroidManifest.xml b/src/ru/mangachan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/mangachan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/mangachan/build.gradle b/src/ru/mangachan/build.gradle deleted file mode 100644 index 21a2fd296..000000000 --- a/src/ru/mangachan/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangachan' - pkgNameSuffix = 'ru.mangachan' - extClass = '.Mangachan' - extVersionCode = 9 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangachan/res/mipmap-hdpi/ic_launcher.png b/src/ru/mangachan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index dc1b4f864..000000000 Binary files a/src/ru/mangachan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangachan/res/mipmap-mdpi/ic_launcher.png b/src/ru/mangachan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 67ff35ffd..000000000 Binary files a/src/ru/mangachan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangachan/res/mipmap-xhdpi/ic_launcher.png b/src/ru/mangachan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 594e9df70..000000000 Binary files a/src/ru/mangachan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangachan/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/mangachan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a5b48076d..000000000 Binary files a/src/ru/mangachan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangachan/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/mangachan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 76230a650..000000000 Binary files a/src/ru/mangachan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangachan/res/web_hi_res_512.png b/src/ru/mangachan/res/web_hi_res_512.png deleted file mode 100755 index e8038d7b3..000000000 Binary files a/src/ru/mangachan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/mangachan/src/eu/kanade/tachiyomi/extension/ru/mangachan/Mangachan.kt b/src/ru/mangachan/src/eu/kanade/tachiyomi/extension/ru/mangachan/Mangachan.kt deleted file mode 100644 index 0e349bf7f..000000000 --- a/src/ru/mangachan/src/eu/kanade/tachiyomi/extension/ru/mangachan/Mangachan.kt +++ /dev/null @@ -1,307 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.mangachan - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Mangachan : ParsedHttpSource() { - - override val id: Long = 7 - - override val name = "Mangachan" - - override val baseUrl = "https://manga-chan.me" - - override val lang = "ru" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var pageNum = 1 - when { - page < 1 -> pageNum = 1 - page >= 1 -> pageNum = page - } - val url = if (query.isNotEmpty()) { - "$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum" - } else { - - var genres = "" - var order = "" - var statusParam = true - var status = "" - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is GenreList -> { - filter.state.forEach { f -> - if (!f.isIgnored()) { - genres += (if (f.isExcluded()) "-" else "") + f.id + '+' - } - } - } - is OrderBy -> { - if (filter.state!!.ascending && filter.state!!.index == 0) { - statusParam = false - } - } - is Status -> status = arrayOf("", "all_done", "end", "ongoing", "new_ch")[filter.state] - } - } - - if (genres.isNotEmpty()) { - for (filter in filters) { - when (filter) { - is OrderBy -> { - order = if (filter.state!!.ascending) { - arrayOf("", "&n=favasc", "&n=abcdesc", "&n=chasc")[filter.state!!.index] - } else { - arrayOf("&n=dateasc", "&n=favdesc", "&n=abcasc", "&n=chdesc")[filter.state!!.index] - } - } - } - } - if (statusParam) { - "$baseUrl/tags/${genres.dropLast(1)}$order?offset=${20 * (pageNum - 1)}&status=$status" - } else { - "$baseUrl/tags/$status/${genres.dropLast(1)}/$order?offset=${20 * (pageNum - 1)}" - } - } else { - for (filter in filters) { - when (filter) { - is OrderBy -> { - order = if (filter.state!!.ascending) { - arrayOf("manga/new", "manga/new&n=favasc", "manga/new&n=abcdesc", "manga/new&n=chasc")[filter.state!!.index] - } else { - arrayOf("manga/new&n=dateasc", "mostfavorites", "catalog", "sortch")[filter.state!!.index] - } - } - } - } - if (statusParam) { - "$baseUrl/$order?offset=${20 * (pageNum - 1)}&status=$status" - } else { - "$baseUrl/$order/$status?offset=${20 * (pageNum - 1)}" - } - } - } - return GET(url, headers) - } - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/newestch?page=$page") - - override fun popularMangaSelector() = "div.content_row" - - override fun latestUpdatesSelector() = "ul.area_rightNews li" - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.manga_images img").first().attr("src") - element.select("h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a:nth-child(1)").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a:contains(Вперед)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = "a:contains(Далее)" - - private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - var hasNextPage = false - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val nextSearchPage = document.select(searchMangaNextPageSelector()) - if (nextSearchPage.isNotEmpty()) { - val query = document.select("input#searchinput").first().attr("value") - val pageNum = nextSearchPage.let { selector -> - val onClick = selector.attr("onclick") - onClick?.split("""\\d+""") - } - nextSearchPage.attr("href", "$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum") - hasNextPage = true - } - - val nextGenresPage = document.select(searchGenresNextPageSelector()) - if (nextGenresPage.isNotEmpty()) { - hasNextPage = true - } - - return MangasPage(mangas, hasNextPage) - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("table.mangatitle").first() - val descElement = document.select("div#description").first() - val imgElement = document.select("img#cover").first() - val rawCategory = infoElement.select("tr:eq(1) > td:eq(1)").text() - val category = if (rawCategory.isNotEmpty()) { - rawCategory.toLowerCase() - } else { - "манга" - } - val manga = SManga.create() - manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() - manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text().split(",").plusElement(category).joinToString { it.trim() } - manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) - manga.description = descElement.textNodes().first().text() - manga.thumbnail_url = imgElement.attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("перевод завершен") -> SManga.COMPLETED - element.contains("перевод продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "table.table_cha tr:gt(1)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("div.date").first()?.text()?.let { - SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it)?.time ?: 0L - } ?: 0 - return chapter - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val beginIndex = html.indexOf("fullimg\":[") + 10 - val endIndex = html.indexOf(",]", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") - val pageUrls = trimmedHtml.split(',') - - return pageUrls.mapIndexed { i, url -> Page(i, "", url) } - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres) - private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) - private class Status : Filter.Select<String>("Статус", arrayOf("Все", "Перевод завершен", "Выпуск завершен", "Онгоинг", "Новые главы")) - private class OrderBy : Filter.Sort( - "Сортировка", - arrayOf("Дата", "Популярность", "Имя", "Главы"), - Selection(1, false) - ) - - override fun getFilterList() = FilterList( - Status(), - OrderBy(), - GenreList(getGenreList()) - ) - - /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")] - * .map(el => `Genre("${el.getAttribute('href').substr(6)}")`).join(',\n') - * on https://mangachan.me/ - */ - private fun getGenreList() = listOf( - Genre("18_плюс"), - Genre("bdsm"), - Genre("арт"), - Genre("боевик"), - Genre("боевые_искусства"), - Genre("вампиры"), - Genre("веб"), - Genre("гарем"), - Genre("гендерная_интрига"), - Genre("героическое_фэнтези"), - Genre("детектив"), - Genre("дзёсэй"), - Genre("додзинси"), - Genre("драма"), - Genre("игра"), - Genre("инцест"), - Genre("искусство"), - Genre("история"), - Genre("киберпанк"), - Genre("кодомо"), - Genre("комедия"), - Genre("литРПГ"), - Genre("махо-сёдзё"), - Genre("меха"), - Genre("мистика"), - Genre("музыка"), - Genre("научная_фантастика"), - Genre("повседневность"), - Genre("постапокалиптика"), - Genre("приключения"), - Genre("психология"), - Genre("романтика"), - Genre("самурайский_боевик"), - Genre("сборник"), - Genre("сверхъестественное"), - Genre("сказка"), - Genre("спорт"), - Genre("супергерои"), - Genre("сэйнэн"), - Genre("сёдзё"), - Genre("сёдзё-ай"), - Genre("сёнэн"), - Genre("сёнэн-ай"), - Genre("тентакли"), - Genre("трагедия"), - Genre("триллер"), - Genre("ужасы"), - Genre("фантастика"), - Genre("фурри"), - Genre("фэнтези"), - Genre("школа"), - Genre("эротика"), - Genre("юри"), - Genre("яой"), - Genre("ёнкома") - ) -} diff --git a/src/ru/mangaclub/AndroidManifest.xml b/src/ru/mangaclub/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/mangaclub/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/mangaclub/build.gradle b/src/ru/mangaclub/build.gradle deleted file mode 100644 index 34c36173d..000000000 --- a/src/ru/mangaclub/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaClub' - pkgNameSuffix = 'ru.mangaclub' - extClass = '.Mangaclub' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangaclub/res/mipmap-hdpi/ic_launcher.png b/src/ru/mangaclub/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 887857657..000000000 Binary files a/src/ru/mangaclub/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaclub/res/mipmap-mdpi/ic_launcher.png b/src/ru/mangaclub/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d746a45c3..000000000 Binary files a/src/ru/mangaclub/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaclub/res/mipmap-xhdpi/ic_launcher.png b/src/ru/mangaclub/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 20e502b44..000000000 Binary files a/src/ru/mangaclub/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaclub/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/mangaclub/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7db29b22a..000000000 Binary files a/src/ru/mangaclub/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaclub/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/mangaclub/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c53a05b25..000000000 Binary files a/src/ru/mangaclub/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaclub/res/web_hi_res_512.png b/src/ru/mangaclub/res/web_hi_res_512.png deleted file mode 100644 index cb28555b4..000000000 Binary files a/src/ru/mangaclub/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/mangaclub/src/eu/kanade/tachiyomi/extension/ru/mangaclub/Mangaclub.kt b/src/ru/mangaclub/src/eu/kanade/tachiyomi/extension/ru/mangaclub/Mangaclub.kt deleted file mode 100644 index 51e7e9792..000000000 --- a/src/ru/mangaclub/src/eu/kanade/tachiyomi/extension/ru/mangaclub/Mangaclub.kt +++ /dev/null @@ -1,198 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.mangaclub - -import android.net.Uri -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Mangaclub : ParsedHttpSource() { - - // Info - override val name: String = "MangaClub" - override val baseUrl: String = "https://mangaclub.ru" - override val lang: String = "ru" - override val supportsLatest: Boolean = false - override val client: OkHttpClient = network.cloudflareClient - - // Popular - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/page/$page/", headers) - override fun popularMangaNextPageSelector(): String = "a i.icon-right-open" - override fun popularMangaSelector(): String = "div.shortstory" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - thumbnail_url = element.select("img").attr("abs:src") - element.select(".title > a").apply { - title = this.text().substringBefore("/").trim() - setUrlWithoutDomain(this.attr("abs:href")) - } - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotBlank()) { - val formBody = FormBody.Builder() - .add("do", "search") - .add("subaction", "search") - .add("search_start", page.toString()) - .add("full_search", "0") - .add("result_from", ((page - 1) * 8 + 1).toString()) - .add("story", query) - .build() - val searchHeaders = headers.newBuilder().add("Content-Type", "application/x-www-form-urlencoded").build() - return POST("$baseUrl/index.php?do=search", searchHeaders, formBody) - } - - val uri = Uri.parse(baseUrl).buildUpon() - - for (filter in filters) { - if (filter is Tag && filter.values[filter.state].isNotEmpty()) { - uri.appendEncodedPath("tags/${filter.values[filter.state]}") - } - } - uri.appendPath("page").appendPath(page.toString()) - return GET(uri.toString(), headers) - } - - override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector() - override fun searchMangaSelector(): String = popularMangaSelector() - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Details - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - thumbnail_url = document.select("div.image img").attr("abs:src") - title = document.select(".title").text().substringBefore("/").trim() - author = document.select("a[href*=author]").text().trim() - artist = author - status = when (document.select("a[href*=status_translation]")?.first()?.text()) { - "Продолжается" -> SManga.ONGOING - "Завершен" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - description = document.select("div.description").text().trim() - genre = document.select("div.info a[href*=tags]").joinToString(", ") { it.text() } - } - - // Chapters - override fun chapterListSelector(): String = ".chapter-item" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val link = element.select("a") - name = link.text().trim() - chapter_number = name.substringAfter("Глава").replace(",", ".").trim().toFloat() - setUrlWithoutDomain(link.attr("abs:href")) - date_upload = parseDate(element.select(".date").text().trim()) - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("dd.MM.yyyy", Locale.US).parse(date)?.time ?: 0 - } - - // Pages - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select(".manga-lines-page a").forEach { - add(Page(it.attr("data-p").toInt(), "", it.attr("abs:data-i"))) - } - } - - override fun imageUrlParse(document: Document): String = - throw Exception("imageUrlParse Not Used") - - // Filters - private class Categories(values: Array<Pair<String, String>>) : - Filter.Select<String>("Категории", values.map { it.first }.toTypedArray()) - - private class Tag(values: Array<String>) : Filter.Select<String>("Жанр", values) - private class Sort(values: List<Pair<String, String>>) : Filter.Sort( - "Сортировать результат поиска", - values.map { it.first }.toTypedArray(), - Selection(2, false) - ) - - private class Status : Filter.Select<String>( - "Статус", - arrayOf("", "Завершен", "Продолжается", "Заморожено/Заброшено") - ) - - override fun getFilterList() = FilterList( - Filter.Header("NOTE: Filters are ignored if using text search."), - Tag(tag) - ) - - private val categoriesArray = arrayOf( - Pair("", ""), - Pair("Манга", "1"), - Pair("Манхва", "2"), - Pair("Маньхуа", "3"), - Pair("Веб-манхва", "6") - ) - - private val tag = arrayOf( - "", - "Боевые искусства", - "Боевик", - "Вампиры", - "Гарем", - "Гендерная интрига", - "Героическое фэнтези", - "Додзинси", - "Дзёсэй", - "Драма", - "Детектив", - "Игра", - "История", - "Киберпанк", - "Комедия", - "Мистика", - "Меха", - "Махо-сёдзё", - "Научная фантастика", - "Повседневность", - "Приключения", - "Психология", - "Романтика", - "Самурайский боевик", - "Сверхъестественное", - "Сёдзё", - "Сёдзё для взрослых", - "Сёдзё-ай", - "Сёнэн", - "Сёнэн-ай", - "Спокон", - "Сэйнэн", - "Спорт", - "Трагедия", - "Триллер", - "Ужасы", - "Фантастика", - "Фэнтези", - "Школа", - "Эротика", - "Этти", - "Юри", - "Яой" - ) - - private val sortables = listOf( - Pair("По заголовку", "title"), - Pair("По количеству комментариев", "comm_num"), - Pair("По количеству просмотров", "news_read"), - Pair("По имени автора", "autor"), - Pair("По рейтингу", "rating") - ) -} diff --git a/src/ru/mangahub/AndroidManifest.xml b/src/ru/mangahub/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/mangahub/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/mangahub/build.gradle b/src/ru/mangahub/build.gradle deleted file mode 100644 index 39b03b97b..000000000 --- a/src/ru/mangahub/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangahub' - pkgNameSuffix = 'ru.mangahub' - extClass = '.Mangahub' - extVersionCode = 9 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangahub/res/mipmap-hdpi/ic_launcher.png b/src/ru/mangahub/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index de61c1bdd..000000000 Binary files a/src/ru/mangahub/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangahub/res/mipmap-mdpi/ic_launcher.png b/src/ru/mangahub/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 951e69ca7..000000000 Binary files a/src/ru/mangahub/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangahub/res/mipmap-xhdpi/ic_launcher.png b/src/ru/mangahub/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a0b366173..000000000 Binary files a/src/ru/mangahub/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangahub/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/mangahub/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fda0c342a..000000000 Binary files a/src/ru/mangahub/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangahub/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/mangahub/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 45a4c520c..000000000 Binary files a/src/ru/mangahub/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangahub/src/eu/kanade/tachiyomi/extension/ru/mangahub/Mangahub.kt b/src/ru/mangahub/src/eu/kanade/tachiyomi/extension/ru/mangahub/Mangahub.kt deleted file mode 100644 index a0a14ed35..000000000 --- a/src/ru/mangahub/src/eu/kanade/tachiyomi/extension/ru/mangahub/Mangahub.kt +++ /dev/null @@ -1,138 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.mangahub - -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -open class Mangahub : ParsedHttpSource() { - - override val name = "Mangahub" - - override val baseUrl = "https://mangahub.ru" - - override val lang = "ru" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - private val jsonParser = JsonParser() - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/explore?filter[sort]=rating&filter[dateStart][left_number]=1900&filter[dateStart][right_number]=2099&page=$page", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/explore?filter[sort]=update&filter[dateStart][left_number]=1900&filter[dateStart][right_number]=2099&page=$page", headers) - - override fun popularMangaSelector() = "div.comic-grid-col-xl" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.comic-grid-image").attr("data-background-image") - manga.title = element.select("a.comic-grid-name").text() - manga.setUrlWithoutDomain(element.select("a.comic-grid-name").attr("href")) - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "li.next > a" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/manga?query=$query&sort=rating_short&page=$page") - } - - override fun searchMangaSelector() = "div.comic-grid-col-xl" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.comic-grid-image").attr("data-background-image") - manga.title = element.select("a.comic-grid-name").text() - manga.setUrlWithoutDomain(element.select("a.comic-grid-name").attr("href")) - return manga - } - - override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select("a[itemprop]")?.text() - manga.genre = document.select("div.tag").text().replace(" ", ", ") - manga.description = document.select("div.markdown-style").text() - manga.status = parseStatus(document.select("div.sticky-top span.status-label").toString()) - manga.thumbnail_url = document.select("img.cover-detail-img").attr("src") - return manga - } - - private fun parseStatus(elements: String): Int = when { - elements.contains("Переведена") or elements.contains("Выпуск завершен") -> SManga.COMPLETED - else -> SManga.ONGOING - } - - override fun chapterListSelector() = "div.py-2.px-3" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("div.align-items-center > a").first() - val chapter = SChapter.create() - chapter.name = urlElement.text() - chapter.date_upload = element.select("div.text-muted").text()?.let { - SimpleDateFormat("dd.MM.yyyy", Locale.US).parse(it)?.time ?: 0L - } ?: 0 - chapter.setUrlWithoutDomain(urlElement.attr("href")) - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("(Глава\\s)((\\d|\\.)+)") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - chapter.chapter_number = it.groups[2]?.value!!.toFloat() - } - } - } - } - - override fun pageListParse(document: Document): List<Page> { - val chapInfo = document.select("reader").attr("data-store").replace(""", "\"").replace("\\/", "/") - val chapter = jsonParser.parse(chapInfo).asJsonObject - val scans = chapter["scans"].asJsonArray - - val pages = mutableListOf<Page>() - scans.mapIndexed { i, page -> - pages.add(Page(i, "", "https:${page.asJsonObject.get("src").asString}")) - } - - return pages - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } -} diff --git a/src/ru/mangaonlinebiz/AndroidManifest.xml b/src/ru/mangaonlinebiz/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/mangaonlinebiz/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/mangaonlinebiz/build.gradle b/src/ru/mangaonlinebiz/build.gradle deleted file mode 100644 index 025cbc32d..000000000 --- a/src/ru/mangaonlinebiz/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangaonlinebiz' - pkgNameSuffix = 'ru.mangaonlinebiz' - extClass = '.MangaOnlineBiz' - extVersionCode = 8 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangaonlinebiz/res/mipmap-hdpi/ic_launcher.png b/src/ru/mangaonlinebiz/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index ea7a5f1cb..000000000 Binary files a/src/ru/mangaonlinebiz/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaonlinebiz/res/mipmap-mdpi/ic_launcher.png b/src/ru/mangaonlinebiz/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index 00874669d..000000000 Binary files a/src/ru/mangaonlinebiz/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaonlinebiz/res/mipmap-xhdpi/ic_launcher.png b/src/ru/mangaonlinebiz/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index 91f44a8e4..000000000 Binary files a/src/ru/mangaonlinebiz/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaonlinebiz/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/mangaonlinebiz/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 54a44ddd2..000000000 Binary files a/src/ru/mangaonlinebiz/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaonlinebiz/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/mangaonlinebiz/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index c0f5a93f4..000000000 Binary files a/src/ru/mangaonlinebiz/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangaonlinebiz/res/web_hi_res_512.png b/src/ru/mangaonlinebiz/res/web_hi_res_512.png deleted file mode 100755 index e8038d7b3..000000000 Binary files a/src/ru/mangaonlinebiz/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/mangaonlinebiz/src/eu/kanade/tachiyomi/extension/ru/mangaonlinebiz/MangaOnlineBiz.kt b/src/ru/mangaonlinebiz/src/eu/kanade/tachiyomi/extension/ru/mangaonlinebiz/MangaOnlineBiz.kt deleted file mode 100644 index cfee7d248..000000000 --- a/src/ru/mangaonlinebiz/src/eu/kanade/tachiyomi/extension/ru/mangaonlinebiz/MangaOnlineBiz.kt +++ /dev/null @@ -1,236 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.mangaonlinebiz - -import com.github.salomonbrys.kotson.float -import com.github.salomonbrys.kotson.forEach -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaOnlineBiz : ParsedHttpSource() { - override val name = "MangaOnlineBiz" - - override val baseUrl = "https://manga-online.biz" - - override val lang = "ru" - - override val supportsLatest = true - - private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36" - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", userAgent) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/genre/all/page/$page", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/genre/all/order/new/page/$page") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = if (query.isNotBlank()) { - "$baseUrl/search-ajax/?query=$query" - } else { - var ret = String() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> { - ret = "$baseUrl/genre/${filter.values[filter.state].id}/page/$page" - } - } - } - ret - } - return GET(url, headers) - } - - override fun popularMangaSelector() = "a.genre" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaParse(response: Response): MangasPage { - if (!response.request().url().toString().contains("search-ajax")) { - return popularMangaParse(response) - } - val jsonData = response.body()!!.string() - val json = JsonParser().parse(jsonData).asJsonObject - val results = json.getAsJsonArray("results") - val mangas = mutableListOf<SManga>() - results.forEach { - val element = it.asJsonObject - val manga = SManga.create() - manga.setUrlWithoutDomain(element.get("url").string) - manga.title = element.get("title").string.split("/").first() - val image = element.get("image").string - if (image.startsWith("http")) { - manga.thumbnail_url = image - } else { - manga.thumbnail_url = baseUrl + image - } - - mangas.add(manga) - } - - return MangasPage(mangas, false) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img").first().attr("src") - manga.setUrlWithoutDomain(element.attr("href")) - element.select("div.content").first().let { - manga.title = it.text().split("/").first() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = throw Exception("Not Used") - - override fun popularMangaNextPageSelector() = "a.button.next" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = throw Exception("Not Used") - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select(".items .item").first() - val manga = SManga.create() - manga.genre = infoElement.select("a.label").joinToString { it.text() } - manga.description = infoElement.select(".description").text() - manga.thumbnail_url = infoElement.select("img").first().attr("src") - if (infoElement.text().contains("Перевод: закончен")) { - manga.status = SManga.COMPLETED - } else if (infoElement.text().contains("Перевод: продолжается")) { - manga.status = SManga.ONGOING - } - - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - val html = response.body()!!.string() - - val jsonData = html.split("App.Collection.MangaChapter(").last().split("]);").first() + "]" - val mangaName = html.split("mangaName: '").last().split("' });").first() - val json = JsonParser().parse(jsonData).asJsonArray - val chapterList = mutableListOf<SChapter>() - json.forEach { - chapterList.add(chapterFromElement(mangaName, it.asJsonObject)) - } - return chapterList - } - - override fun chapterListSelector(): String = throw Exception("Not Used") - - private fun chapterFromElement(mangaName: String, element: JsonObject): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain("/$mangaName/${element.get("volume").string}/${element.get("number").string})/1") - chapter.name = "Том ${element.get("volume").string} - Глава ${element.get("number").string} ${element.get("title").string}" - chapter.chapter_number = element.get("number").float - chapter.date_upload = SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(element.get("date").string)?.time ?: 0L - return chapter - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val jsonData = html.split("new App.Router.Chapter(").last().split("});").first() + "}" - val json = JsonParser().parse(jsonData).asJsonObject - val cdnUrl = json.get("srcBaseUrl").string - val pages = json.get("pages").asJsonObject - val resPages = mutableListOf<Page>() - pages.forEach { page, jsonElement -> - resPages.add(Page(page.toInt(), imageUrl = "$cdnUrl/${jsonElement.asJsonObject.get("src").string}")) - } - return resPages - } - - private class Genre(name: String, val id: String) : Filter.CheckBox(name) { - override fun toString(): String { - return name - } - } - - private class GenreList(genres: Array<Genre>) : Filter.Select<Genre>("Genres", genres, 0) - - override fun getFilterList() = FilterList( - GenreList(getGenreList()) - ) - - /* [...document.querySelectorAll(".categories .item")] - * .map(el => `Genre("${el.textContent.trim()}", "${el.getAttribute('href')}")`).join(',\n') - * on https://manga-online.biz/genre/all/ - */ - private fun getGenreList() = arrayOf( - Genre("Все", "all"), - Genre("Боевик", "boevik"), - Genre("Боевые искусства", "boevye_iskusstva"), - Genre("Вампиры", "vampiry"), - Genre("Гарем", "garem"), - Genre("Гендерная интрига", "gendernaya_intriga"), - Genre("Героическое фэнтези", "geroicheskoe_fehntezi"), - Genre("Детектив", "detektiv"), - Genre("Дзёсэй", "dzyosehj"), - Genre("Додзинси", "dodzinsi"), - Genre("Драма", "drama"), - Genre("Игра", "igra"), - Genre("История", "istoriya"), - Genre("Меха", "mekha"), - Genre("Мистика", "mistika"), - Genre("Научная фантастика", "nauchnaya_fantastika"), - Genre("Повседневность", "povsednevnost"), - Genre("Постапокалиптика", "postapokaliptika"), - Genre("Приключения", "priklyucheniya"), - Genre("Психология", "psihologiya"), - Genre("Романтика", "romantika"), - Genre("Самурайский боевик", "samurajskij_boevik"), - Genre("Сверхъестественное", "sverhestestvennoe"), - Genre("Сёдзё", "syodzyo"), - Genre("Сёдзё-ай", "syodzyo-aj"), - Genre("Сёнэн", "syonen"), - Genre("Спорт", "sport"), - Genre("Сэйнэн", "sejnen"), - Genre("Трагедия", "tragediya"), - Genre("Триллер", "triller"), - Genre("Ужасы", "uzhasy"), - Genre("Фантастика", "fantastika"), - Genre("Фэнтези", "fentezi"), - Genre("Школа", "shkola"), - Genre("Этти", "etti"), - Genre("Юри", "yuri"), - Genre("Военный", "voennyj"), - Genre("Жосей", "zhosej"), - Genre("Магия", "magiya"), - Genre("Полиция", "policiya"), - Genre("Смена пола", "smena-pola"), - Genre("Супер сила", "super-sila"), - Genre("Эччи", "echchi"), - Genre("Яой", "yaoj"), - Genre("Сёнэн-ай", "syonen-aj") - ) - - override fun imageUrlParse(document: Document) = throw Exception("Not Used") - - override fun searchMangaSelector(): String = throw Exception("Not Used") - - override fun chapterFromElement(element: Element): SChapter = throw Exception("Not Used") - - override fun pageListParse(document: Document): List<Page> = throw Exception("Not Used") -} diff --git a/src/ru/mangapoisk/AndroidManifest.xml b/src/ru/mangapoisk/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/mangapoisk/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/mangapoisk/build.gradle b/src/ru/mangapoisk/build.gradle deleted file mode 100644 index 50babf7f1..000000000 --- a/src/ru/mangapoisk/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaPoisk' - pkgNameSuffix = 'ru.mangapoisk' - extClass = '.MangaPoisk' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangapoisk/res/mipmap-hdpi/ic_launcher.png b/src/ru/mangapoisk/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 35dbf52c2..000000000 Binary files a/src/ru/mangapoisk/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangapoisk/res/mipmap-mdpi/ic_launcher.png b/src/ru/mangapoisk/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 023102910..000000000 Binary files a/src/ru/mangapoisk/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangapoisk/res/mipmap-xhdpi/ic_launcher.png b/src/ru/mangapoisk/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index fe93752be..000000000 Binary files a/src/ru/mangapoisk/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangapoisk/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/mangapoisk/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 293967719..000000000 Binary files a/src/ru/mangapoisk/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangapoisk/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/mangapoisk/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6e636316b..000000000 Binary files a/src/ru/mangapoisk/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mangapoisk/res/web_hi_res_512.png b/src/ru/mangapoisk/res/web_hi_res_512.png deleted file mode 100644 index 8585108c7..000000000 Binary files a/src/ru/mangapoisk/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/mangapoisk/src/eu/kanade/tachiyomi/extension/ru/mangapoisk/MangaPoisk.kt b/src/ru/mangapoisk/src/eu/kanade/tachiyomi/extension/ru/mangapoisk/MangaPoisk.kt deleted file mode 100644 index af878f49f..000000000 --- a/src/ru/mangapoisk/src/eu/kanade/tachiyomi/extension/ru/mangapoisk/MangaPoisk.kt +++ /dev/null @@ -1,248 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.mangapoisk - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -class MangaPoisk : ParsedHttpSource() { - override val name = "MangaPoisk" - - override val baseUrl = "https://mangapoisk.ru" - - override val lang = "ru" - - override val supportsLatest = true - - private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.3987.163 Safari/537.36" - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", userAgent) - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/manga?sortBy=popular&page=$page", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/manga?sortBy=-last_chapter_at&page=$page", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = if (query.isNotBlank()) { - "$baseUrl/search?q=$query" - } else { - val url = HttpUrl.parse("$baseUrl/manga")!!.newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is OrderBy -> { - val ord = arrayOf("-year", "popular", "name", "-published_at", "-last_chapter_at")[filter.state!!.index] - val ordRev = arrayOf("year", "-popular", "-name", "published_at", "last_chapter_at")[filter.state!!.index] - url.addQueryParameter("sortBy", if (filter.state!!.ascending) "$ordRev" else "$ord") - } - is StatusList -> filter.state.forEach { status -> - if (status.state) { - url.addQueryParameter("translated[]", status.id) - } - } - is GenreList -> filter.state.forEach { genre -> - if (genre.state) { - url.addQueryParameter("genres[]", genre.id) - } - } - } - } - return GET(url.toString(), headers) - } - return GET(url, headers) - } - - override fun popularMangaSelector() = "article.card" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaParse(response: Response): MangasPage { - return popularMangaParse(response) - } - - private fun getImage(first: Element): String? { - val image = first.attr("data-src") - if (image.isNotEmpty()) { - return image - } - return first.attr("src") - } - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - thumbnail_url = getImage(element.select("a > img").first()) - - element.select("a.card-about").first().let { - setUrlWithoutDomain(it.attr("href")) - } - - element.select("a > h2.entry-title").first().let { - title = it.text().split("/").first() - } - } - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.page-link" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("article div.card-body").first() - val manga = SManga.create() - manga.genre = infoElement.select(".post-info > span:eq(10) > a").joinToString { it.text() } - manga.description = infoElement.select(".post-info > div .manga-description.entry").text() - manga.status = parseStatus(infoElement.select(".post-info > span:eq(7)").text()) - manga.thumbnail_url = infoElement.select("img.img-fluid").first().attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("Статус: Завершена") -> SManga.COMPLETED - element.contains("Статус: Выпускается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga) - } - } - private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { - val document = response.asJsoup() - return document.select(chapterListSelector()).map { chapterFromElement(it, manga) } - } - override fun chapterListRequest(manga: SManga): Request { - return GET("$baseUrl${manga.url}/chaptersList", headers) - } - override fun chapterListSelector() = ".chapter-item" - - private fun chapterFromElement(element: Element, manga: SManga): SChapter { - val title = element.select("span.chapter-title").first().text() - val urlElement = element.select("a").first() - val urlText = urlElement.text() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - - chapter.name = urlText.trim() - chapter.chapter_number = "Глава\\s(\\d+)".toRegex(RegexOption.IGNORE_CASE).find(title)?.groupValues?.get(1)?.toFloat() ?: -1F - chapter.date_upload = element.select("span.chapter-date").first()?.text()?.let { - try { - when { - it.contains("минут") -> Date(System.currentTimeMillis() - it.split("\\s".toRegex())[0].toLong() * 60 * 1000).time - it.contains("час") -> Date(System.currentTimeMillis() - it.split("\\s".toRegex())[0].toLong() * 60 * 60 * 1000).time - it.contains("дня") || it.contains("дней") -> Date(System.currentTimeMillis() - it.split("\\s".toRegex())[0].toLong() * 24 * 60 * 60 * 1000).time - else -> SimpleDateFormat("dd MMMM yyyy", Locale("ru")).parse(it)?.time ?: 0L - } - } catch (e: Exception) { - Date(System.currentTimeMillis()).time - } - } ?: 0 - return chapter - } - override fun pageListParse(document: Document): List<Page> { - return document.select(".img-fluid.page-image").mapIndexed { index, element -> - Page(index, "", getImage(element)) - } - } - - private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name) - - private class StatusList(statuses: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус", statuses) - private class GenreList(genres: List<CheckFilter>) : Filter.Group<CheckFilter>("Жанры", genres) - override fun getFilterList() = FilterList( - OrderBy(), - StatusList(getStatusList()), - GenreList(getGenreList()) - ) - - private class OrderBy : Filter.Sort( - "Сортировка", - arrayOf("Год", "Популярности", "Алфавиту", "Дате добавления", "Дате обновления"), - Selection(1, false) - ) - - private fun getStatusList() = listOf( - CheckFilter("Выпускается", "0"), - CheckFilter("Завершена", "1"), - ) - - private fun getGenreList() = listOf( - CheckFilter("приключения", "1"), - CheckFilter("романтика", "2"), - CheckFilter("боевик", "3"), - CheckFilter("комедия", "4"), - CheckFilter("сверхъестественное", "5"), - CheckFilter("драма", "6"), - CheckFilter("фэнтези", "7"), - CheckFilter("сёнэн", "8"), - CheckFilter("этти", "7"), - CheckFilter("вампиры", "10"), - CheckFilter("школа", "11"), - CheckFilter("сэйнэн", "12"), - CheckFilter("повседневность", "18"), - CheckFilter("сёнэн-ай", "19"), - CheckFilter("гарем", "29"), - CheckFilter("героическое фэнтези", "30"), - CheckFilter("боевые искусства", "31"), - CheckFilter("психология", "38"), - CheckFilter("сёдзё", "57"), - CheckFilter("игра", "105"), - CheckFilter("триллер", "120"), - CheckFilter("детектив", "121"), - CheckFilter("трагедия", "122"), - CheckFilter("история", "123"), - CheckFilter("сёдзё-ай", "147"), - CheckFilter("спорт", "160"), - CheckFilter("научная фантастика", "171"), - CheckFilter("гендерная интрига", "172"), - CheckFilter("дзёсэй", "230"), - CheckFilter("ужасы", "260"), - CheckFilter("постапокалиптика", "310"), - CheckFilter("киберпанк", "355"), - CheckFilter("меха", "356"), - CheckFilter("эротика", "380"), - CheckFilter("яой", "612"), - CheckFilter("самурайский боевик", "916"), - CheckFilter("махо-сёдзё", "1472"), - CheckFilter("додзинси", "1785"), - CheckFilter("кодомо", "1789"), - CheckFilter("юри", "3197"), - CheckFilter("арт", "7332"), - CheckFilter("омегаверс", "7514"), - CheckFilter("бара", "8119") - ) - - override fun imageUrlParse(document: Document) = throw Exception("Not Used") - - override fun searchMangaSelector(): String = throw Exception("Not Used") - - override fun chapterFromElement(element: Element): SChapter = throw Exception("Not Used") -} diff --git a/src/ru/mintmanga/AndroidManifest.xml b/src/ru/mintmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/mintmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/mintmanga/build.gradle b/src/ru/mintmanga/build.gradle deleted file mode 100644 index 1c3da9ae2..000000000 --- a/src/ru/mintmanga/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mintmanga' - pkgNameSuffix = 'ru.mintmanga' - extClass = '.Mintmanga' - extVersionCode = 26 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/mintmanga/res/mipmap-hdpi/ic_launcher.png b/src/ru/mintmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 78bc7512c..000000000 Binary files a/src/ru/mintmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mintmanga/res/mipmap-mdpi/ic_launcher.png b/src/ru/mintmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ff183f248..000000000 Binary files a/src/ru/mintmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mintmanga/res/mipmap-xhdpi/ic_launcher.png b/src/ru/mintmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 6a6ef9e52..000000000 Binary files a/src/ru/mintmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mintmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/mintmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8b12a60a8..000000000 Binary files a/src/ru/mintmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mintmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/mintmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c555bd882..000000000 Binary files a/src/ru/mintmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/mintmanga/src/eu/kanade/tachiyomi/extension/ru/mintmanga/Mintmanga.kt b/src/ru/mintmanga/src/eu/kanade/tachiyomi/extension/ru/mintmanga/Mintmanga.kt deleted file mode 100644 index 4c0e3d5be..000000000 --- a/src/ru/mintmanga/src/eu/kanade/tachiyomi/extension/ru/mintmanga/Mintmanga.kt +++ /dev/null @@ -1,376 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.mintmanga - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.regex.Pattern - -class Mintmanga : ParsedHttpSource() { - - override val id: Long = 6 - - override val name = "Mintmanga" - - override val baseUrl = "https://mintmanga.live" - - override val lang = "ru" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) - - override fun popularMangaSelector() = "div.tile" - - override fun latestUpdatesSelector() = "div.tile" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img.lazy").first()?.attr("data-original") - element.select("h3 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.nextLink" - - override fun latestUpdatesNextPageSelector() = "a.nextLink" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> filter.state.forEach { genre -> - if (genre.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(genre.id, arrayOf("=", "=in", "=ex")[genre.state]) - } - } - is Category -> filter.state.forEach { category -> - if (category.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(category.id, arrayOf("=", "=in", "=ex")[category.state]) - } - } - is AgeList -> filter.state.forEach { age -> - if (age.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(age.id, arrayOf("=", "=in", "=ex")[age.state]) - } - } - is More -> filter.state.forEach { more -> - if (more.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(more.id, arrayOf("=", "=in", "=ex")[more.state]) - } - } - is FilList -> filter.state.forEach { fils -> - if (fils.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(fils.id, arrayOf("=", "=in", "=ex")[fils.state]) - } - } - is OrderBy -> { - if (filter.state == 0) { - url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() - } else { - val ord = arrayOf("not", "year", "name", "rate", "popularity", "votes", "created", "updated")[filter.state] - url = HttpUrl.parse("$baseUrl/list?sortType=$ord")!!.newBuilder() - return GET(url.toString(), headers) - } - } - } - } - if (query.isNotEmpty()) { - url.addQueryParameter("q", query) - } - return GET(url.toString().replace("=%3D", "="), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // max 200 results - override fun searchMangaNextPageSelector(): Nothing? = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.leftContent").first() - val rawCategory = infoElement.select("span.elem_category").text() - val category = if (rawCategory.isNotEmpty()) { - rawCategory.toLowerCase() - } else { - "манга" - } - - val manga = SManga.create() - var authorElement = infoElement.select("span.elem_author").first()?.text() - if (authorElement == null) { - authorElement = infoElement.select("span.elem_screenwriter").first()?.text() - } - manga.author = authorElement - manga.artist = infoElement.select("span.elem_illustrator").first()?.text() - manga.genre = infoElement.select("span.elem_genre").text().split(",").plusElement(category).joinToString { it.trim() } - manga.description = infoElement.select("div.manga-description").text() - manga.status = parseStatus(infoElement.html()) - manga.thumbnail_url = infoElement.select("img").attr("data-full") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("Запрещена публикация произведения по копирайту") -> SManga.LICENSED - element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> SManga.COMPLETED - element.contains("<b>Перевод:</b> продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return if (manga.status != SManga.LICENSED) { - client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga) - } - } else { - Observable.error(java.lang.Exception("Licensed - No chapters to show")) - } - } - - private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { - val document = response.asJsoup() - return document.select(chapterListSelector()).map { chapterFromElement(it, manga) } - } - - override fun chapterListSelector() = "div.chapters-link > table > tbody > tr:has(td > a)" - - private fun chapterFromElement(element: Element, manga: SManga): SChapter { - val urlElement = element.select("a").first() - val urlText = urlElement.text() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mtr=1") - - var translators = "" - val translatorElement = urlElement.attr("title") - if (!translatorElement.isNullOrBlank()) { - translators = translatorElement - .replace("(Переводчик),", "&") - .removeSuffix(" (Переводчик)") - } - chapter.scanlator = translators - - chapter.name = urlText.removeSuffix(" новое").trim() - if (manga.title.length > 25) { - for (word in manga.title.split(' ')) { - chapter.name = chapter.name.removePrefix(word).trim() - } - } - val dots = chapter.name.indexOf("…") - val numbers = chapter.name.findAnyOf(IntRange(0, 9).map { it.toString() })?.first ?: 0 - - if (dots in 0 until numbers) { - chapter.name = chapter.name.substringAfter("…").trim() - } - - chapter.date_upload = element.select("td.d-none").last()?.text()?.let { - try { - SimpleDateFormat("dd.MM.yy", Locale.US).parse(it)?.time ?: 0L - } catch (e: ParseException) { - SimpleDateFormat("dd/MM/yy", Locale.US).parse(it)?.time ?: 0L - } - } ?: 0 - return chapter - } - - override fun chapterFromElement(element: Element): SChapter { - throw Exception("Not used") - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""\s*([0-9]+)(\s-\s)([0-9]+)\s*""") - val extra = Regex("""\s*([0-9]+\sЭкстра)\s*""") - val single = Regex("""\s*Сингл\s*""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - val number = it.groups[3]?.value!! - chapter.chapter_number = number.toFloat() - } - } - extra.containsMatchIn(chapter.name) -> // Extra chapters doesn't contain chapter number - chapter.chapter_number = -2f - single.containsMatchIn(chapter.name) -> // Oneshoots, doujinshi and other mangas with one chapter - chapter.chapter_number = 1f - } - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val beginIndex = html.indexOf("rm_h.init( [") - val endIndex = html.indexOf(");", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex) - - val p = Pattern.compile("'.*?','.*?',\".*?\"") - val m = p.matcher(trimmedHtml) - - val pages = mutableListOf<Page>() - - var i = 0 - while (m.find()) { - val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') - val url = if (urlParts[1].isEmpty() && urlParts[2].startsWith("/static/")) { - baseUrl + urlParts[2] - } else { - if (urlParts[1].endsWith("/manga/")) { - urlParts[0] + urlParts[2] - } else { - urlParts[1] + urlParts[0] + urlParts[2] - } - } - pages.add(Page(i++, "", url)) - } - return pages - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - - private class OrderBy : Filter.Select<String>( - "Сортировать\n(отдельно от фильтров)", - arrayOf("Без(фильтры)", "По году", "По алфавиту", "По популярности", "Популярно сейчас", "По рейтингу", "Новинки", "По дате обновления") - ) - - private class Genre(name: String, val id: String) : Filter.TriState(name) - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Жанры", genres) - private class Category(categories: List<Genre>) : Filter.Group<Genre>("Категории", categories) - private class AgeList(ages: List<Genre>) : Filter.Group<Genre>("Возрастная рекомендация", ages) - private class More(moren: List<Genre>) : Filter.Group<Genre>("Прочее", moren) - private class FilList(fils: List<Genre>) : Filter.Group<Genre>("Фильтры", fils) - - /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")] - * .map(el => `Genre("${el.textContent.trim()}", $"{el.getAttribute('onclick') - * .substr(31,el.getAttribute('onclick').length-33)"})`).join(',\n') - * on https://mintmanga.live/search/advanced - */ - override fun getFilterList() = FilterList( - OrderBy(), - Category(getCategoryList()), - GenreList(getGenreList()), - AgeList(getAgeList()), - More(getMore()), - FilList(getFilList()) - ) - private fun getFilList() = listOf( - Genre("Высокий рейтинг", "s_high_rate"), - Genre("Сингл", "s_single"), - Genre("Для взрослых", "s_mature"), - Genre("Завершенная", "s_completed"), - Genre("Переведено", "s_translated"), - Genre("Длинная", "s_many_chapters"), - Genre("Ожидает загрузки", "s_wait_upload"), - ) - private fun getMore() = listOf( - Genre("В цвете", "el_4614"), - Genre("Веб", "el_1355"), - Genre("Выпуск приостановлен", "el_5232"), - Genre("Не Яой", "el_1874"), - Genre("Сборник", "el_1348") - ) - - private fun getAgeList() = listOf( - Genre("NC-17", "el_3969"), - Genre("R", "el_3968"), - Genre("R18+", "el_3990") - ) - - private fun getCategoryList() = listOf( - Genre("Ёнкома", "el_2741"), - Genre("Комикс западный", "el_1903"), - Genre("Комикс русский", "el_2173"), - Genre("Манхва", "el_1873"), - Genre("Маньхуа", "el_1875"), - Genre("Ранобэ", "el_5688"), - ) - - private fun getGenreList() = listOf( - Genre("арт", "el_2220"), - Genre("бара", "el_1353"), - Genre("боевик", "el_1346"), - Genre("боевые искусства", "el_1334"), - Genre("вампиры", "el_1339"), - Genre("гарем", "el_1333"), - Genre("гендерная интрига", "el_1347"), - Genre("героическое фэнтези", "el_1337"), - Genre("детектив", "el_1343"), - Genre("дзёсэй", "el_1349"), - Genre("додзинси", "el_1332"), - Genre("драма", "el_1310"), - Genre("игра", "el_5229"), - Genre("история", "el_1311"), - Genre("киберпанк", "el_1351"), - Genre("комедия", "el_1328"), - Genre("меха", "el_1318"), - Genre("научная фантастика", "el_1325"), - Genre("омегаверс", "el_5676"), - Genre("повседневность", "el_1327"), - Genre("постапокалиптика", "el_1342"), - Genre("приключения", "el_1322"), - Genre("психология", "el_1335"), - Genre("романтика", "el_1313"), - Genre("самурайский боевик", "el_1316"), - Genre("сверхъестественное", "el_1350"), - Genre("сёдзё", "el_1314"), - Genre("сёдзё-ай", "el_1320"), - Genre("сёнэн", "el_1326"), - Genre("сёнэн-ай", "el_1330"), - Genre("спорт", "el_1321"), - Genre("сэйнэн", "el_1329"), - Genre("трагедия", "el_1344"), - Genre("триллер", "el_1341"), - Genre("ужасы", "el_1317"), - Genre("фэнтези", "el_1323"), - Genre("школа", "el_1319"), - Genre("эротика", "el_1340"), - Genre("этти", "el_1354"), - Genre("юри", "el_1315"), - Genre("яой", "el_1336") - ) -} diff --git a/src/ru/nudemoon/AndroidManifest.xml b/src/ru/nudemoon/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/nudemoon/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/nudemoon/build.gradle b/src/ru/nudemoon/build.gradle deleted file mode 100644 index 234274d88..000000000 --- a/src/ru/nudemoon/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Nude-Moon' - pkgNameSuffix = 'ru.nudemoon' - extClass = '.Nudemoon' - extVersionCode = 7 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/nudemoon/res/mipmap-hdpi/ic_launcher.png b/src/ru/nudemoon/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 75abbb1cb..000000000 Binary files a/src/ru/nudemoon/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/nudemoon/res/mipmap-mdpi/ic_launcher.png b/src/ru/nudemoon/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f26c1dcb6..000000000 Binary files a/src/ru/nudemoon/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/nudemoon/res/mipmap-xhdpi/ic_launcher.png b/src/ru/nudemoon/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index a5ee737e8..000000000 Binary files a/src/ru/nudemoon/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/nudemoon/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/nudemoon/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index de6a2f282..000000000 Binary files a/src/ru/nudemoon/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/nudemoon/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/nudemoon/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 850e07e4f..000000000 Binary files a/src/ru/nudemoon/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/nudemoon/res/web_hi_res_512.png b/src/ru/nudemoon/res/web_hi_res_512.png deleted file mode 100644 index d5756edd1..000000000 Binary files a/src/ru/nudemoon/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/nudemoon/src/eu/kanade/tachiyomi/extension/ru/nudemoon/Nudemoon.kt b/src/ru/nudemoon/src/eu/kanade/tachiyomi/extension/ru/nudemoon/Nudemoon.kt deleted file mode 100644 index b6e02340f..000000000 --- a/src/ru/nudemoon/src/eu/kanade/tachiyomi/extension/ru/nudemoon/Nudemoon.kt +++ /dev/null @@ -1,306 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.nudemoon - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.net.URLEncoder -import java.text.SimpleDateFormat -import java.util.Locale - -@Nsfw -class Nudemoon : ParsedHttpSource() { - - override val name = "Nude-Moon" - - override val baseUrl = "https://nude-moon.net" - - override val lang = "ru" - - override val supportsLatest = true - - private val cookiesHeader by lazy { - val cookies = mutableMapOf<String, String>() - cookies["NMfYa"] = "1" - buildCookies(cookies) - } - - private fun buildCookies(cookies: Map<String, String>) = - cookies.entries.joinToString(separator = "; ", postfix = ";") { - "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" - } - - override val client = network.client.newBuilder() - .addNetworkInterceptor { chain -> - val newReq = chain - .request() - .newBuilder() - .addHeader("Cookie", cookiesHeader) - .addHeader("Referer", baseUrl) - .build() - - chain.proceed(newReq) - }.build()!! - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/all_manga?views&rowstart=${30 * (page - 1)}", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/all_manga?date&rowstart=${30 * (page - 1)}", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // Search by query on this site works really badly, i don't even sure of the need to implement it - val url = if (query.isNotEmpty()) { - "$baseUrl/search?stext=${URLEncoder.encode(query, "CP1251")}&rowstart=${30 * (page - 1)}" - } else { - var genres = "" - var order = "" - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is GenreList -> { - filter.state.forEach { f -> - if (f.state) { - genres += f.id + '+' - } - } - } - } - } - - if (genres.isNotEmpty()) { - for (filter in filters) { - when (filter) { - is OrderBy -> { - // The site has no ascending order - order = arrayOf("&date", "&views", "&like")[filter.state!!.index] - } - } - } - "$baseUrl/tags/${genres.dropLast(1)}$order&rowstart=${30 * (page - 1)}" - } else { - for (filter in filters) { - when (filter) { - is OrderBy -> { - // The site has no ascending order - order = arrayOf("all_manga?date", "all_manga?views", "all_manga?like")[filter.state!!.index] - } - } - } - "$baseUrl/$order&rowstart=${30 * (page - 1)}" - } - } - return GET(url, headers) - } - - override fun popularMangaSelector() = "tr[valign=top]" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.thumbnail_url = element.select("img[class^=news]").attr("abs:src") - element.select("a:has(h2)").let { - manga.title = it.text() - manga.setUrlWithoutDomain(it.attr("href")) - } - - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.small:contains(Следующая)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select("div.tbl1 a[href^=mangaka]").text() - manga.genre = document.select("div.tbl2 span.tag-links a").joinToString { it.text() } - manga.description = document.select(".description").text() - manga.thumbnail_url = document.select("tr[valign=top] img[class^=news]").attr("abs:src") - - return manga - } - - override fun chapterListRequest(manga: SManga): Request { - - val chapterUrl = if (manga.title.contains("\\s#\\d+".toRegex())) - "/vse_glavy/" + manga.title.split("\\s#\\d+".toRegex())[0].replace("\\W".toRegex(), "_") - else - manga.url - - return GET(baseUrl + chapterUrl, headers) - } - - override fun chapterListSelector() = popularMangaSelector() - - override fun chapterListParse(response: Response): List<SChapter> { - val responseUrl = response.request().url().toString() - val document = response.asJsoup() - - if (!responseUrl.contains("/vse_glavy/")) { - return listOf(chapterFromElement(document)) - } - - // Order chapters by its number 'cause on the site they are in random order - return document.select(chapterListSelector()).sortedByDescending { - val regex = "#(\\d+)".toRegex() - val chapterName = it.select("img[class^=news]").first().parent().attr("title") - regex.find(chapterName)?.groupValues?.get(1)?.toInt() ?: 0 - }.map { chapterFromElement(it) } - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - val infoElem = element.select("tr[valign=top]").first().parent() - val chapterName = infoElem.select("h1, h2").text() - var chapterUrl = infoElem.select("a[title]:has(img)").attr("href") - if (!chapterUrl.contains("-online")) { - chapterUrl = chapterUrl.replace("/\\d+".toRegex(), "$0-online") - } else { - chapter.chapter_number = 1F - } - - chapter.setUrlWithoutDomain(if (!chapterUrl.startsWith("/")) "/$chapterUrl" else chapterUrl) - chapter.name = chapterName - chapter.date_upload = infoElem.text().substringAfter("Дата:").substringBefore("Просмотров").trim().let { - try { - SimpleDateFormat("dd MMMM yyyy", Locale("ru")).parse(it)?.time ?: 0L - } catch (e: Exception) { - 0 - } - } - - return chapter - } - - override fun pageListParse(response: Response): List<Page> { - val imgScript = response.asJsoup().select("script:containsData(var images)").first().data() - - return Regex("""images\[(\d+)].src\s=\s'(http.*)'""").findAll(imgScript).map { - Page(it.groupValues[1].toInt(), imageUrl = it.groupValues[2]) - }.toList() - } - - override fun imageUrlParse(document: Document) = throw Exception("Not Used") - - override fun pageListParse(document: Document): List<Page> = throw Exception("Not Used") - - private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.CheckBox(name.capitalize()) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres) - private class OrderBy : Filter.Sort( - "Сортировка", - arrayOf("Дата", "Просмотры", "Лайки"), - Selection(1, false) - ) - - override fun getFilterList() = FilterList( - OrderBy(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("анал"), - Genre("без цензуры"), - Genre("беременные"), - Genre("близняшки"), - Genre("большие груди"), - Genre("в бассейне"), - Genre("в больнице"), - Genre("в ванной"), - Genre("в общественном месте"), - Genre("в первый раз"), - Genre("в транспорте"), - Genre("в туалете"), - Genre("гарем"), - Genre("гипноз"), - Genre("горничные"), - Genre("горячий источник"), - Genre("групповой секс"), - Genre("драма"), - Genre("запредельное"), - Genre("золотой дождь"), - Genre("зрелые женщины"), - Genre("идолы"), - Genre("извращение"), - Genre("измена"), - Genre("имеют парня"), - Genre("клизма"), - Genre("колготки"), - Genre("комиксы"), - Genre("комиксы 3D"), - Genre("косплей"), - Genre("мастурбация"), - Genre("мерзкий мужик"), - Genre("много спермы"), - Genre("молоко"), - Genre("монстры"), - Genre("на камеру"), - Genre("на природе"), - Genre("обычный секс"), - Genre("огромный член"), - Genre("пляж"), - Genre("подглядывание"), - Genre("принуждение"), - Genre("продажность"), - Genre("пьяные"), - Genre("рабыни"), - Genre("романтика"), - Genre("с ушками"), - Genre("секс игрушки"), - Genre("спящие"), - Genre("страпон"), - Genre("студенты"), - Genre("суккуб"), - Genre("тентакли"), - Genre("толстушки"), - Genre("трапы"), - Genre("ужасы"), - Genre("униформа"), - Genre("учитель и ученик"), - Genre("фемдом"), - Genre("фетиш"), - Genre("фурри"), - Genre("футанари"), - Genre("футфетиш"), - Genre("фэнтези"), - Genre("цветная"), - Genre("чикан"), - Genre("чулки"), - Genre("шимейл"), - Genre("эксгибиционизм"), - Genre("юмор"), - Genre("юри"), - Genre("ahegao"), - Genre("BDSM"), - Genre("ganguro"), - Genre("gender bender"), - Genre("megane"), - Genre("mind break"), - Genre("monstergirl"), - Genre("netorare"), - Genre("nipple penetration"), - Genre("titsfuck"), - Genre("x-ray") - ) -} diff --git a/src/ru/readmanga/AndroidManifest.xml b/src/ru/readmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/readmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/readmanga/build.gradle b/src/ru/readmanga/build.gradle deleted file mode 100644 index 00db7d8b7..000000000 --- a/src/ru/readmanga/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Readmanga' - pkgNameSuffix = 'ru.readmanga' - extClass = '.Readmanga' - extVersionCode = 25 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/readmanga/res/mipmap-hdpi/ic_launcher.png b/src/ru/readmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3e550725e..000000000 Binary files a/src/ru/readmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/readmanga/res/mipmap-mdpi/ic_launcher.png b/src/ru/readmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b3df119e7..000000000 Binary files a/src/ru/readmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/readmanga/res/mipmap-xhdpi/ic_launcher.png b/src/ru/readmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b3158fb26..000000000 Binary files a/src/ru/readmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/readmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/readmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 35ef8d93a..000000000 Binary files a/src/ru/readmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/readmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/readmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e63084ad6..000000000 Binary files a/src/ru/readmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/readmanga/src/eu/kanade/tachiyomi/extension/ru/readmanga/Readmanga.kt b/src/ru/readmanga/src/eu/kanade/tachiyomi/extension/ru/readmanga/Readmanga.kt deleted file mode 100644 index b4543eb55..000000000 --- a/src/ru/readmanga/src/eu/kanade/tachiyomi/extension/ru/readmanga/Readmanga.kt +++ /dev/null @@ -1,375 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.readmanga - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.regex.Pattern - -class Readmanga : ParsedHttpSource() { - - override val id: Long = 5 - - override val name = "Readmanga" - - override val baseUrl = "https://readmanga.live" - - override val lang = "ru" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() - - override fun popularMangaSelector() = "div.tile" - - override fun latestUpdatesSelector() = "div.tile" - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img.lazy").first()?.attr("data-original") - element.select("h3 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.nextLink" - - override fun latestUpdatesNextPageSelector() = "a.nextLink" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> filter.state.forEach { genre -> - if (genre.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(genre.id, arrayOf("=", "=in", "=ex")[genre.state]) - } - } - is Category -> filter.state.forEach { category -> - if (category.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(category.id, arrayOf("=", "=in", "=ex")[category.state]) - } - } - is AgeList -> filter.state.forEach { age -> - if (age.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(age.id, arrayOf("=", "=in", "=ex")[age.state]) - } - } - is More -> filter.state.forEach { more -> - if (more.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(more.id, arrayOf("=", "=in", "=ex")[more.state]) - } - } - is FilList -> filter.state.forEach { fils -> - if (fils.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(fils.id, arrayOf("=", "=in", "=ex")[fils.state]) - } - } - is OrderBy -> { - if (filter.state == 0) { - url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() - } else { - val ord = arrayOf("not", "name", "rate", "popularity", "votes", "created", "updated")[filter.state] - url = HttpUrl.parse("$baseUrl/list?sortType=$ord")!!.newBuilder() - return GET(url.toString(), headers) - } - } - } - } - if (query.isNotEmpty()) { - url.addQueryParameter("q", query) - } - return GET(url.toString().replace("=%3D", "="), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // max 200 results - override fun searchMangaNextPageSelector(): Nothing? = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.leftContent").first() - val rawCategory = infoElement.select("span.elem_category").text() - val category = if (rawCategory.isNotEmpty()) { - rawCategory.toLowerCase() - } else { - "манга" - } - - val manga = SManga.create() - var authorElement = infoElement.select("span.elem_author").first()?.text() - if (authorElement == null) { - authorElement = infoElement.select("span.elem_screenwriter").first()?.text() - } - manga.author = authorElement - manga.artist = infoElement.select("span.elem_illustrator").first()?.text() - manga.genre = infoElement.select("span.elem_genre").text().split(",").plusElement(category).joinToString { it.trim() } - manga.description = infoElement.select("div.manga-description").text() - manga.status = parseStatus(infoElement.html()) - manga.thumbnail_url = infoElement.select("img").attr("data-full") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("Запрещена публикация произведения по копирайту") -> SManga.LICENSED - element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> SManga.COMPLETED - element.contains("<b>Перевод:</b> продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return if (manga.status != SManga.LICENSED) { - client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga) - } - } else { - Observable.error(java.lang.Exception("Licensed - No chapters to show")) - } - } - - private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { - val document = response.asJsoup() - return document.select(chapterListSelector()).map { chapterFromElement(it, manga) } - } - - override fun chapterListSelector() = "div.chapters-link > table > tbody > tr:has(td > a)" - - private fun chapterFromElement(element: Element, manga: SManga): SChapter { - val urlElement = element.select("a").first() - val urlText = urlElement.text() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mtr=1") - - var translators = "" - val translatorElement = urlElement.attr("title") - if (!translatorElement.isNullOrBlank()) { - translators = translatorElement - .replace("(Переводчик),", "&") - .removeSuffix(" (Переводчик)") - } - chapter.scanlator = translators - - chapter.name = urlText.removeSuffix(" новое").trim() - if (manga.title.length > 25) { - for (word in manga.title.split(' ')) { - chapter.name = chapter.name.removePrefix(word).trim() - } - } - val dots = chapter.name.indexOf("…") - val numbers = chapter.name.findAnyOf(IntRange(0, 9).map { it.toString() })?.first ?: 0 - - if (dots in 0 until numbers) { - chapter.name = chapter.name.substringAfter("…").trim() - } - - chapter.date_upload = element.select("td.d-none").last()?.text()?.let { - try { - SimpleDateFormat("dd.MM.yy", Locale.US).parse(it)?.time ?: 0L - } catch (e: ParseException) { - SimpleDateFormat("dd/MM/yy", Locale.US).parse(it)?.time ?: 0L - } - } ?: 0 - return chapter - } - - override fun chapterFromElement(element: Element): SChapter { - throw Exception("Not used") - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""\s*([0-9]+)(\s-\s)([0-9]+)\s*""") - val extra = Regex("""\s*([0-9]+\sЭкстра)\s*""") - val single = Regex("""\s*Сингл\s*""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - val number = it.groups[3]?.value!! - chapter.chapter_number = number.toFloat() - } - } - extra.containsMatchIn(chapter.name) -> // Extra chapters doesn't contain chapter number - chapter.chapter_number = -2f - single.containsMatchIn(chapter.name) -> // Oneshoots, doujinshi and other mangas with one chapter - chapter.chapter_number = 1f - } - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val beginIndex = html.indexOf("rm_h.init( [") - val endIndex = html.indexOf(");", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex) - - val p = Pattern.compile("'.*?','.*?',\".*?\"") - val m = p.matcher(trimmedHtml) - - val pages = mutableListOf<Page>() - - var i = 0 - while (m.find()) { - val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') - val url = if (urlParts[1].isEmpty() && urlParts[2].startsWith("/static/")) { - baseUrl + urlParts[2] - } else { - if (urlParts[1].endsWith("/manga/")) { - urlParts[0] + urlParts[2] - } else { - urlParts[1] + urlParts[0] + urlParts[2] - } - } - pages.add(Page(i++, "", url)) - } - return pages - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - - private class OrderBy : Filter.Select<String>( - "Сортировать\n(отдельно от фильтров)", - arrayOf("Без(фильтры)", "По алфавиту", "По популярности", "Популярно сейчас", "По рейтингу", "Новинки", "По дате обновления") - ) - - private class Genre(name: String, val id: String) : Filter.TriState(name) - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Жанры", genres) - private class Category(categories: List<Genre>) : Filter.Group<Genre>("Категории", categories) - private class AgeList(ages: List<Genre>) : Filter.Group<Genre>("Возрастная рекомендация", ages) - private class More(moren: List<Genre>) : Filter.Group<Genre>("Прочее", moren) - private class FilList(fils: List<Genre>) : Filter.Group<Genre>("Фильтры", fils) - - /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")] - * .map(el => `Genre("${el.textContent.trim()}", $"{el.getAttribute('onclick') - * .substr(31,el.getAttribute('onclick').length-33)"})`).join(',\n') - * on https://readmanga.me/search/advanced - */ - - override fun getFilterList() = FilterList( - OrderBy(), - Category(getCategoryList()), - GenreList(getGenreList()), - AgeList(getAgeList()), - More(getMore()), - FilList(getFilList()) - ) - - private fun getFilList() = listOf( - Genre("Высокий рейтинг", "s_high_rate"), - Genre("Сингл", "s_single"), - Genre("Для взрослых", "s_mature"), - Genre("Завершенная", "s_completed"), - Genre("Переведено", "s_translated"), - Genre("Длинная", "s_many_chapters"), - Genre("Ожидает загрузки", "s_wait_upload"), - Genre("Продается", "s_sale") - ) - private fun getMore() = listOf( - Genre("В цвете", "el_7290"), - Genre("Веб", "el_2160"), - Genre("Выпуск приостановлен", "el_8033"), - Genre("Сборник", "el_2157") - ) - - private fun getAgeList() = listOf( - Genre("G", "el_6180"), - Genre("PG", "el_6179"), - Genre("PG-13", "el_6181") - ) - - private fun getCategoryList() = listOf( - Genre("Ёнкома", "el_2161"), - Genre("Комикс западный", "el_3515"), - Genre("Манхва", "el_3001"), - Genre("Маньхуа", "el_3002"), - Genre("Ранобэ", "el_8575"), - ) - - private fun getGenreList() = listOf( - Genre("арт", "el_5685"), - Genre("боевик", "el_2155"), - Genre("боевые искусства", "el_2143"), - Genre("вампиры", "el_2148"), - Genre("гарем", "el_2142"), - Genre("гендерная интрига", "el_2156"), - Genre("героическое фэнтези", "el_2146"), - Genre("детектив", "el_2152"), - Genre("дзёсэй", "el_2158"), - Genre("додзинси", "el_2141"), - Genre("драма", "el_2118"), - Genre("игра", "el_2154"), - Genre("история", "el_2119"), - Genre("киберпанк", "el_8032"), - Genre("кодомо", "el_2137"), - Genre("комедия", "el_2136"), - Genre("махо-сёдзё", "el_2147"), - Genre("меха", "el_2126"), - Genre("научная фантастика", "el_2133"), - Genre("повседневность", "el_2135"), - Genre("постапокалиптика", "el_2151"), - Genre("приключения", "el_2130"), - Genre("психология", "el_2144"), - Genre("романтика", "el_2121"), - Genre("самурайский боевик", "el_2124"), - Genre("сверхъестественное", "el_2159"), - Genre("сёдзё", "el_2122"), - Genre("сёдзё-ай", "el_2128"), - Genre("сёнэн", "el_2134"), - Genre("сёнэн-ай", "el_2139"), - Genre("спорт", "el_2129"), - Genre("сэйнэн", "el_2138"), - Genre("трагедия", "el_2153"), - Genre("триллер", "el_2150"), - Genre("ужасы", "el_2125"), - Genre("фэнтези", "el_2131"), - Genre("школа", "el_2127"), - Genre("этти", "el_2149"), - Genre("юри", "el_2123") - ) -} diff --git a/src/ru/remanga/AndroidManifest.xml b/src/ru/remanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/remanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/remanga/build.gradle b/src/ru/remanga/build.gradle deleted file mode 100644 index a07800a0e..000000000 --- a/src/ru/remanga/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Remanga' - pkgNameSuffix = 'ru.remanga' - extClass = '.Remanga' - extVersionCode = 20 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-dataimage') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/remanga/res/mipmap-hdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c3d066c2f..000000000 Binary files a/src/ru/remanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/remanga/res/mipmap-mdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ff1c36b9c..000000000 Binary files a/src/ru/remanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/remanga/res/mipmap-xhdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f47cbf324..000000000 Binary files a/src/ru/remanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/remanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8b2ac8c37..000000000 Binary files a/src/ru/remanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/remanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/remanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 35136d6bd..000000000 Binary files a/src/ru/remanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/remanga/res/web_hi_res_512.png b/src/ru/remanga/res/web_hi_res_512.png deleted file mode 100644 index 205c899cf..000000000 Binary files a/src/ru/remanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt deleted file mode 100644 index 569c1d9ff..000000000 --- a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/Remanga.kt +++ /dev/null @@ -1,609 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.remanga - -import BookDto -import BranchesDto -import GenresDto -import LibraryDto -import MangaDetDto -import PageDto -import PageWrapperDto -import PaidPageDto -import SeriesWrapperDto -import UserDto -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.Application -import android.content.SharedPreferences -import android.os.Build -import android.support.v7.preference.EditTextPreference -import android.support.v7.preference.PreferenceScreen -import android.text.InputType -import android.widget.Toast -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.JsonObject -import com.google.gson.JsonSyntaxException -import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import org.json.JSONObject -import org.jsoup.Jsoup -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import kotlin.math.absoluteValue -import kotlin.random.Random - -class Remanga : ConfigurableSource, HttpSource() { - override val name = "Remanga" - - override val baseUrl = "https://api.remanga.org" - - override val lang = "ru" - - override val supportsLatest = true - - private var token: String = "" - - protected open val userAgentRandomizer = " ${Random.nextInt().absoluteValue}" - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/78.0$userAgentRandomizer") - .add("Referer", "https://www.google.com") - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private fun authIntercept(chain: Interceptor.Chain): Response { - val request = chain.request() - if (username.isEmpty() or password.isEmpty()) { - return chain.proceed(request) - } - - if (token.isEmpty()) { - token = this.login(chain, username, password) - } - val authRequest = request.newBuilder() - .addHeader("Authorization", "bearer $token") - .build() - return chain.proceed(authRequest) - } - - override val client: OkHttpClient = - network.client.newBuilder() - .addInterceptor(DataImageInterceptor()) - .addInterceptor { authIntercept(it) } - .build() - - private val count = 30 - - private var branches = mutableMapOf<String, List<BranchesDto>>() - - private fun login(chain: Interceptor.Chain, username: String, password: String): String { - val jsonObject = JSONObject() - jsonObject.put("user", username) - jsonObject.put("password", password) - val body = RequestBody.create(MEDIA_TYPE, jsonObject.toString()) - val response = chain.proceed(POST("$baseUrl/api/users/login/", headers, body)) - if (response.code() == 400) { - throw Exception("Failed to login") - } - val user = gson.fromJson<SeriesWrapperDto<UserDto>>(response.body()?.charStream()!!) - return user.content.access_token - } - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/api/search/catalog/?ordering=-rating&count=$count&page=$page", headers) - - override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response) - - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/api/titles/last-chapters/?page=$page&count=$count", headers) - - override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) - - override fun searchMangaParse(response: Response): MangasPage { - val page = gson.fromJson<PageWrapperDto<LibraryDto>>(response.body()?.charStream()!!) - val mangas = page.content.map { - it.toSManga() - } - return MangasPage(mangas, page.props.page < page.props.total_pages) - } - - private fun LibraryDto.toSManga(): SManga = - SManga.create().apply { - // Do not change the title name to ensure work with a multilingual catalog! - title = en_name - url = "/api/titles/$dir/" - thumbnail_url = if (img.high.isNotEmpty()) { - "$baseUrl/${img.high}" - } else "$baseUrl/${img.mid}" - } - - private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US) } - - private fun parseDate(date: String?): Long { - date ?: return Date().time - return try { - simpleDateFormat.parse(date)!!.time - } catch (_: Exception) { - Date().time - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = HttpUrl.parse("$baseUrl/api/search/catalog/?page=$page")!!.newBuilder() - if (query.isNotEmpty()) { - url = HttpUrl.parse("$baseUrl/api/search/?page=$page")!!.newBuilder() - url.addQueryParameter("query", query) - } - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is OrderBy -> { - val ord = arrayOf("id", "chapter_date", "rating", "votes", "views", "count_chapters", "random")[filter.state!!.index] - url.addQueryParameter("ordering", if (filter.state!!.ascending) "$ord" else "-$ord") - } - is CategoryList -> filter.state.forEach { category -> - if (category.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(if (category.isIncluded()) "categories" else "exclude_categories", category.id) - } - } - is TypeList -> filter.state.forEach { type -> - if (type.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(if (type.isIncluded()) "types" else "exclude_types", type.id) - } - } - is StatusList -> filter.state.forEach { status -> - if (status.state) { - url.addQueryParameter("status", status.id) - } - } - is AgeList -> filter.state.forEach { age -> - if (age.state) { - url.addQueryParameter("age_limit", age.id) - } - } - is GenreList -> filter.state.forEach { genre -> - if (genre.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(if (genre.isIncluded()) "genres" else "exclude_genres", genre.id) - } - } - } - } - return GET(url.toString(), headers) - } - - private fun parseStatus(status: Int): Int { - return when (status) { - 0 -> SManga.COMPLETED - 1 -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } - - private fun parseType(type: GenresDto): GenresDto { - return when (type.name) { - "Западный комикс" -> GenresDto(type.id, "Комикс") - else -> type - } - } - - private fun MangaDetDto.toSManga(): SManga { - val o = this - return SManga.create().apply { - // Do not change the title name to ensure work with a multilingual catalog! - title = en_name - url = "/api/titles/$dir/" - thumbnail_url = "$baseUrl/${img.high}" - this.description = "Русское название: " + rus_name + "\n" + Jsoup.parse(o.description).text() - genre = (genres + parseType(type)).joinToString { it.name } - status = parseStatus(o.status.id) - } - } - private fun titleDetailsRequest(manga: SManga): Request { - val titleId = manga.url - - val newHeaders = headersBuilder().build() - - return GET("$baseUrl/$titleId", newHeaders) - } - - // Workaround to allow "Open in browser" use the real URL. - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - var warnLogin = false - return client.newCall(titleDetailsRequest(manga)) - .asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - if (response.code() == 401) warnLogin = true else throw Exception("HTTP error ${response.code()}") - } - } - .map { response -> - (if (warnLogin) manga.apply { description = "Авторизуйтесь для просмотра списка глав" } else mangaDetailsParse(response)) - .apply { - initialized = true - } - } - } - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl.replace("api.", "") + "/manga/" + manga.url.substringAfter("/api/titles/", "/"), headers) - } - override fun mangaDetailsParse(response: Response): SManga { - val series = gson.fromJson<SeriesWrapperDto<MangaDetDto>>(response.body()?.charStream()!!) - branches[series.content.en_name] = series.content.branches - return series.content.toSManga() - } - - private fun mangaBranches(manga: SManga): List<BranchesDto> { - val responseString = client.newCall(GET("$baseUrl/${manga.url}")).execute().body()?.string() ?: return emptyList() - // manga requiring login return "content" as a JsonArray instead of the JsonObject we expect - return if (gson.fromJson<JsonObject>(responseString)["content"].isJsonObject) { - val series = gson.fromJson<SeriesWrapperDto<MangaDetDto>>(responseString) - branches[series.content.en_name] = series.content.branches - series.content.branches - } else { - emptyList() - } - } - - private fun selector(b: BranchesDto): Int = b.count_chapters - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - val branch = branches.getOrElse(manga.title) { mangaBranches(manga) } - return when { - branch.isEmpty() -> { - return Observable.just(listOf()) - } - manga.status == SManga.LICENSED -> { - Observable.error(Exception("Licensed - No chapters to show")) - } - else -> { - val branchId = branch.maxByOrNull { selector(it) }!!.id - client.newCall(chapterListRequest(branchId)) - .asObservableSuccess() - .map { response -> - chapterListParse(response) - } - } - } - } - - private fun chapterListRequest(branch: Long): Request { - return GET("$baseUrl/api/titles/chapters/?branch_id=$branch", headers) - } - - @SuppressLint("DefaultLocale") - private fun chapterName(book: BookDto): String { - var chapterName = "${book.tome}. Глава ${book.chapter}" - if (book.name.isNotBlank()) { - chapterName += " ${book.name.capitalize()}" - } - return chapterName - } - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = gson.fromJson<PageWrapperDto<BookDto>>(response.body()?.charStream()!!) - return chapters.content.filter { !it.is_paid or it.is_bought }.map { chapter -> - SChapter.create().apply { - chapter_number = chapter.chapter.split(".").take(2).joinToString(".").toFloat() - name = chapterName(chapter) - url = "/api/titles/chapters/${chapter.id}" - date_upload = parseDate(chapter.upload_date) - scanlator = if (chapter.publishers.isNotEmpty()) { - chapter.publishers.joinToString { it.name } - } else null - } - } - } - - @TargetApi(Build.VERSION_CODES.N) - override fun pageListParse(response: Response): List<Page> { - val body = response.body()?.string()!! - return try { - val page = gson.fromJson<SeriesWrapperDto<PageDto>>(body) - page.content.pages.filter { it.height > 1 }.map { - Page(it.page, "", it.link) - } - } catch (e: JsonSyntaxException) { - val page = gson.fromJson<SeriesWrapperDto<PaidPageDto>>(body) - val result = mutableListOf<Page>() - page.content.pages.forEach { - it.filter { page -> page.height > 1 }.forEach { page -> - result.add(Page(result.size, "", page.link)) - } - } - return result - } - } - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) - - override fun imageUrlRequest(page: Page): Request = throw NotImplementedError("Unused") - - override fun imageUrlParse(response: Response): String = throw NotImplementedError("Unused") - - override fun imageRequest(page: Page): Request { - val refererHeaders = headersBuilder().build() - return GET(page.imageUrl!!, refererHeaders) - } - - private class SearchFilter(name: String, val id: String) : Filter.TriState(name) - private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name) - - private class CategoryList(categories: List<SearchFilter>) : Filter.Group<SearchFilter>("Категории", categories) - private class TypeList(types: List<SearchFilter>) : Filter.Group<SearchFilter>("Типы", types) - private class StatusList(statuses: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус", statuses) - private class GenreList(genres: List<SearchFilter>) : Filter.Group<SearchFilter>("Жанры", genres) - private class AgeList(ages: List<CheckFilter>) : Filter.Group<CheckFilter>("Возрастное ограничение", ages) - - override fun getFilterList() = FilterList( - OrderBy(), - GenreList(getGenreList()), - CategoryList(getCategoryList()), - TypeList(getTypeList()), - StatusList(getStatusList()), - AgeList(getAgeList()) - ) - - private class OrderBy : Filter.Sort( - "Сортировка", - arrayOf("Новизне", "Последним обновлениям", "Популярности", "Лайкам", "Просмотрам", "По кол-ву глав", "Мне повезет"), - Selection(2, false) - ) - - private fun getAgeList() = listOf( - CheckFilter("Для всех", "0"), - CheckFilter("16+", "1"), - CheckFilter("18+", "2") - ) - - private fun getTypeList() = listOf( - SearchFilter("Манга", "0"), - SearchFilter("Манхва", "1"), - SearchFilter("Маньхуа", "2"), - SearchFilter("Западный комикс", "3"), - SearchFilter("Русскомикс", "4"), - SearchFilter("Индонезийский комикс", "5"), - SearchFilter("Новелла", "6"), - SearchFilter("Другое", "7") - ) - - private fun getStatusList() = listOf( - CheckFilter("Закончен", "0"), - CheckFilter("Продолжается", "1"), - CheckFilter("Заморожен", "2") - ) - - private fun getCategoryList() = listOf( - SearchFilter("алхимия", "47"), - SearchFilter("ангелы", "48"), - SearchFilter("антигерой", "26"), - SearchFilter("антиутопия", "49"), - SearchFilter("апокалипсис", "50"), - SearchFilter("аристократия", "117"), - SearchFilter("армия", "51"), - SearchFilter("артефакты", "52"), - SearchFilter("боги", "45"), - SearchFilter("борьба за власть", "52"), - SearchFilter("будущее", "55"), - SearchFilter("в цвете", "6"), - SearchFilter("вампиры", "112"), - SearchFilter("веб", "5"), - SearchFilter("вестерн", "56"), - SearchFilter("видеоигры", "35"), - SearchFilter("виртуальная реальность", "44"), - SearchFilter("владыка демонов", "57"), - SearchFilter("военные", "29"), - SearchFilter("волшебные существа", "59"), - SearchFilter("воспоминания из другого мира", "60"), - SearchFilter("врачи / доктора", "116"), - SearchFilter("выживание", "41"), - SearchFilter("гг женщина", "63"), - SearchFilter("гг мужчина", "64"), - SearchFilter("гг силён с самого начала", "110"), - SearchFilter("геймеры", "61"), - SearchFilter("гильдии", "62"), - SearchFilter("гяру", "28"), - SearchFilter("девушки-монстры", "37"), - SearchFilter("демоны", "15"), - SearchFilter("драконы", "66"), - SearchFilter("дружба", "67"), - SearchFilter("ёнкома", "62"), - SearchFilter("жестокий мир", "69"), - SearchFilter("животные компаньоны", "70"), - SearchFilter("завоевание мира", "71"), - SearchFilter("зверолюди", "19"), - SearchFilter("зомби", "14"), - SearchFilter("игровые элементы", "73"), - SearchFilter("исекай", "115"), - SearchFilter("квесты", "75"), - SearchFilter("космос", "76"), - SearchFilter("кулинария", "16"), - SearchFilter("культивация", "18"), - SearchFilter("лоли", "108"), - SearchFilter("магическая академия", "78"), - SearchFilter("магия", "22"), - SearchFilter("мафия", "24"), - SearchFilter("медицина", "17"), - SearchFilter("месть", "79"), - SearchFilter("монстры", "38"), - SearchFilter("музыка", "39"), - SearchFilter("навыки / способности", "80"), - SearchFilter("наёмники", "81"), - SearchFilter("насилие / жестокость", "82"), - SearchFilter("нежить", "83"), - SearchFilter("ниндзя", "30"), - SearchFilter("оборотни", "113"), - SearchFilter("обратный гарем", "40"), - SearchFilter("пародия", "85"), - SearchFilter("подземелья", "86"), - SearchFilter("политика", "87"), - SearchFilter("полиция", "32"), - SearchFilter("преступники / криминал", "36"), - SearchFilter("призраки / духи", "27"), - SearchFilter("прокачка", "118"), - SearchFilter("путешествия во времени", "43"), - SearchFilter("разумные расы", "88"), - SearchFilter("ранги силы", "68"), - SearchFilter("реинкарнация", "13"), - SearchFilter("роботы", "89"), - SearchFilter("рыцари", "90"), - SearchFilter("самураи", "33"), - SearchFilter("сборник", "10"), - SearchFilter("сингл", "11"), - SearchFilter("система", "91"), - SearchFilter("скрытие личности", "93"), - SearchFilter("спасение мира", "94"), - SearchFilter("средневековье", "25"), - SearchFilter("спасение мира", "94"), - SearchFilter("средневековье", "25"), - SearchFilter("стимпанк", "92"), - SearchFilter("супергерои", "95"), - SearchFilter("традиционные игры", "34"), - SearchFilter("тупой гг", "109"), - SearchFilter("умный гг", "111"), - SearchFilter("управление", "114"), - SearchFilter("философия", "97"), - SearchFilter("хентай", "12"), - SearchFilter("хикикомори", "21"), - SearchFilter("шантаж", "99"), - SearchFilter("эльфы", "46") - ) - - private fun getGenreList() = listOf( - SearchFilter("арт", "1"), - SearchFilter("бдсм", "44"), - SearchFilter("боевик", "2"), - SearchFilter("боевые искусства", "3"), - SearchFilter("вампиры", "4"), - SearchFilter("гарем", "5"), - SearchFilter("гендерная интрига", "6"), - SearchFilter("героическое фэнтези", "7"), - SearchFilter("детектив", "8"), - SearchFilter("дзёсэй", "9"), - SearchFilter("додзинси", "10"), - SearchFilter("драма", "11"), - SearchFilter("игра", "12"), - SearchFilter("история", "13"), - SearchFilter("киберпанк", "14"), - SearchFilter("кодомо", "15"), - SearchFilter("комедия", "16"), - SearchFilter("махо-сёдзё", "17"), - SearchFilter("меха", "18"), - SearchFilter("мистика", "19"), - SearchFilter("научная фантастика", "20"), - SearchFilter("повседневность", "21"), - SearchFilter("постапокалиптика", "22"), - SearchFilter("приключения", "23"), - SearchFilter("психология", "24"), - SearchFilter("романтика", "25"), - SearchFilter("сверхъестественное", "27"), - SearchFilter("сёдзё", "28"), - SearchFilter("сёдзё-ай", "29"), - SearchFilter("сёнэн", "30"), - SearchFilter("сёнэн-ай", "31"), - SearchFilter("спорт", "32"), - SearchFilter("сэйнэн", "33"), - SearchFilter("трагедия", "34"), - SearchFilter("триллер", "35"), - SearchFilter("ужасы", "36"), - SearchFilter("фантастика", "37"), - SearchFilter("фэнтези", "38"), - SearchFilter("школа", "39"), - SearchFilter("эротика", "42"), - SearchFilter("этти", "40"), - SearchFilter("юри", "41"), - SearchFilter("яой", "43") - ) - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true)) - } - - private fun androidx.preference.PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference { - return androidx.preference.EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - - if (isPassword) { - setOnBindEditTextListener { - it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - } - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - screen.addPreference(screen.supportEditTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) - screen.addPreference(screen.supportEditTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password)) - } - - private fun PreferenceScreen.supportEditTextPreference(title: String, default: String, value: String): EditTextPreference { - return EditTextPreference(context).apply { - key = title - this.title = title - summary = value - this.setDefaultValue(default) - dialogTitle = title - - setOnPreferenceChangeListener { _, newValue -> - try { - val res = preferences.edit().putString(title, newValue as String).commit() - Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show() - res - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - } - - private fun getPrefUsername(): String = preferences.getString(USERNAME_TITLE, USERNAME_DEFAULT)!! - private fun getPrefPassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!! - - private val gson by lazy { Gson() } - private val username by lazy { getPrefUsername() } - private val password by lazy { getPrefPassword() } - - companion object { - private val MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8") - private const val USERNAME_TITLE = "Username" - private const val USERNAME_DEFAULT = "" - private const val PASSWORD_TITLE = "Password" - private const val PASSWORD_DEFAULT = "" - } -} diff --git a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt b/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt deleted file mode 100644 index 7fa4555b9..000000000 --- a/src/ru/remanga/src/eu/kanade/tachiyomi/extension/ru/remanga/dto/Dto.kt +++ /dev/null @@ -1,106 +0,0 @@ -data class GenresDto( - val id: Int, - val name: String -) - -data class BranchesDto( - val id: Long, - val count_chapters: Int -) - -data class ImgDto( - val high: String, - val mid: String, - val low: String -) - -data class LibraryDto( - val id: Long, - val en_name: String, - val rus_name: String, - val dir: String, - val issue_year: Int, - val genres: List<GenresDto>, - val img: ImgDto -) - -data class StatusDto( - val id: Int, - val name: String -) - -data class MangaDetDto( - val id: Long, - val en_name: String, - val rus_name: String, - val dir: String, - val description: String, - val issue_year: Int, - val img: ImgDto, - val type: GenresDto, - val genres: List<GenresDto>, - val branches: List<BranchesDto>, - val status: StatusDto -) - -data class PropsDto( - val total_items: Int, - val total_pages: Int, - val page: Int -) - -data class PageWrapperDto<T>( - val msg: String, - val content: List<T>, - val props: PropsDto, - val last: Boolean -) - -data class SeriesWrapperDto<T>( - val msg: String, - val content: T, - val props: PropsDto -) - -data class PublisherDto( - val name: String, - val dir: String -) - -data class BookDto( - val id: Long, - val tome: Int, - val chapter: String, - val name: String, - val upload_date: String, - val is_paid: Boolean, - val is_bought: Boolean, - val publishers: List<PublisherDto> -) - -data class PagesDto( - val id: Int, - val height: Int, - val link: String, - val page: Int, - val count_comments: Int -) - -data class PageDto( - val pages: List<PagesDto> -) - -data class UserDto( - val access_token: String -) - -data class PaidPagesDto( - val id: Long, - val link: String, - val height: Int, - val page: Int -) - -data class PaidPageDto( - val pages: List<List<PaidPagesDto>> -) diff --git a/src/ru/risensteam/AndroidManifest.xml b/src/ru/risensteam/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/risensteam/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/risensteam/build.gradle b/src/ru/risensteam/build.gradle deleted file mode 100644 index 91eda1f4e..000000000 --- a/src/ru/risensteam/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Risens Team' - pkgNameSuffix = 'ru.risensteam' - extClass = '.RisensTeam' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/risensteam/res/mipmap-hdpi/ic_launcher.png b/src/ru/risensteam/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a499e50f6..000000000 Binary files a/src/ru/risensteam/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/risensteam/res/mipmap-mdpi/ic_launcher.png b/src/ru/risensteam/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4485c6f65..000000000 Binary files a/src/ru/risensteam/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/risensteam/res/mipmap-xhdpi/ic_launcher.png b/src/ru/risensteam/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 86474a4f2..000000000 Binary files a/src/ru/risensteam/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/risensteam/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/risensteam/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7649df867..000000000 Binary files a/src/ru/risensteam/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/risensteam/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/risensteam/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 250f226f4..000000000 Binary files a/src/ru/risensteam/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/risensteam/res/web_hi_res_512.png b/src/ru/risensteam/res/web_hi_res_512.png deleted file mode 100644 index 11b680ecc..000000000 Binary files a/src/ru/risensteam/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/risensteam/src/eu/kanade/tachiyomi/extension/ru/risensteam/RisensTeam.kt b/src/ru/risensteam/src/eu/kanade/tachiyomi/extension/ru/risensteam/RisensTeam.kt deleted file mode 100644 index b84832223..000000000 --- a/src/ru/risensteam/src/eu/kanade/tachiyomi/extension/ru/risensteam/RisensTeam.kt +++ /dev/null @@ -1,142 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.risensteam - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.string -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -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.online.HttpSource -import okhttp3.MediaType -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import rx.Observable -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -class RisensTeam : HttpSource() { - - override val name = "Risens Team" - - override val baseUrl = "https://risens.team" - - override val lang = "ru" - - override val supportsLatest = false - - override val versionId: Int = 2 - - private val gson by lazy { Gson() } - - // Popular (source only returns manga sorted by latest) - - override fun popularMangaRequest(page: Int): Request { - return GET("https://risens.team/api/title/list?type=1", headers) - } - - private fun mangaFromJson(json: JsonElement): SManga { - return SManga.create().apply { - url = "${json["id"].int}/${json["furl"].string}" - title = json["title"].string - thumbnail_url = baseUrl + json["poster"].string - description = json["description"].nullString - status = try { if (json["active"].int == 1) SManga.ONGOING else SManga.UNKNOWN } catch (_: Exception) { SManga.UNKNOWN } - } - } - - override fun popularMangaParse(response: Response): MangasPage { - val mangas = gson.fromJson<JsonArray>(response.body()!!.string()) - .map { json -> mangaFromJson(json) } - - return MangasPage(mangas, false) - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val rbody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), """{"queryString":"$query","limit":3}""") - return POST("$baseUrl/api/title/search", headers, rbody) - } - - override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) - - // Details - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - return client.newCall(apiMangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET("$baseUrl/api/title/show/${manga.url.substringBefore("/")}") - } - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET("$baseUrl/title/${manga.url}") - } - - override fun mangaDetailsParse(response: Response): SManga { - return mangaFromJson(gson.fromJson<JsonObject>(response.body()!!.string())) - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request = apiMangaDetailsRequest(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - return gson.fromJson<JsonObject>(response.body()!!.string())["entities"].asJsonArray.map { json -> - SChapter.create().apply { - url = json["id"].int.toString() - name = listOfNotNull(json["label"].nullString, json["name"].nullString).joinToString(" - ") - date_upload = json["updated_at"].toDate() - } - } - } - - private val simpleDateFormat by lazy { - SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) - } - - private fun JsonElement.toDate(): Long { - val date = this.nullString ?: return 0 - return try { - simpleDateFormat.parse(date)?.time ?: 0 - } catch (e: ParseException) { - 0 - } - } - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return GET("$baseUrl/api/yandex/chapter/${chapter.url}", headers) - } - - override fun pageListParse(response: Response): List<Page> { - return gson.fromJson<JsonArray>(response.body()!!.string()) - .mapIndexed { i, json -> Page(i, "", json.string) } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/ru/selfmanga/AndroidManifest.xml b/src/ru/selfmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/selfmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/selfmanga/build.gradle b/src/ru/selfmanga/build.gradle deleted file mode 100644 index 4630059b0..000000000 --- a/src/ru/selfmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Selfmanga' - pkgNameSuffix = 'ru.selfmanga' - extClass = '.Selfmanga' - extVersionCode = 7 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/selfmanga/res/mipmap-hdpi/ic_launcher.png b/src/ru/selfmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index bbf49d82f..000000000 Binary files a/src/ru/selfmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/selfmanga/res/mipmap-mdpi/ic_launcher.png b/src/ru/selfmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index bd16a829a..000000000 Binary files a/src/ru/selfmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/selfmanga/res/mipmap-xhdpi/ic_launcher.png b/src/ru/selfmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 85a153630..000000000 Binary files a/src/ru/selfmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/selfmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/selfmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 85456dc0b..000000000 Binary files a/src/ru/selfmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/selfmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/selfmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 44cf6c9b3..000000000 Binary files a/src/ru/selfmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/selfmanga/res/web_hi_res_512.png b/src/ru/selfmanga/res/web_hi_res_512.png deleted file mode 100644 index 713072936..000000000 Binary files a/src/ru/selfmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/selfmanga/src/eu/kanade/tachiyomi/extension/ru/selfmanga/Selfmanga.kt b/src/ru/selfmanga/src/eu/kanade/tachiyomi/extension/ru/selfmanga/Selfmanga.kt deleted file mode 100644 index 21e2c18ff..000000000 --- a/src/ru/selfmanga/src/eu/kanade/tachiyomi/extension/ru/selfmanga/Selfmanga.kt +++ /dev/null @@ -1,248 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.selfmanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.regex.Pattern - -class Selfmanga : ParsedHttpSource() { - - override val name = "Selfmanga" - - override val baseUrl = "https://selfmanga.ru" - - override val lang = "ru" - - override val supportsLatest = true - - override fun popularMangaSelector() = "div.tile" - - override fun latestUpdatesSelector() = "div.tile" - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("img.lazy").first().attr("data-original") - element.select("h3 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a.nextLink" - - override fun latestUpdatesNextPageSelector() = "a.nextLink" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search/advanced")!!.newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> filter.state.forEach { genre -> - if (genre.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(genre.id, arrayOf("=", "=in", "=ex")[genre.state]) - } - } - is Category -> filter.state.forEach { category -> - if (category.state != Filter.TriState.STATE_IGNORE) { - url.addQueryParameter(category.id, arrayOf("=", "=in", "=ex")[category.state]) - } - } - } - } - if (query.isNotEmpty()) { - url.addQueryParameter("q", query) - } - return GET(url.toString().replace("=%3D", "="), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // max 200 results - override fun searchMangaNextPageSelector(): Nothing? = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.leftContent").first() - - val manga = SManga.create() - manga.author = infoElement.select("span.elem_author").first()?.text() - manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") - manga.description = infoElement.select("div.manga-description").text() - manga.status = parseStatus(infoElement.html()) - manga.thumbnail_url = infoElement.select("img").attr("data-full") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> SManga.LICENSED - element.contains("<h1 class=\"names\"> Сингл") || element.contains("выпуск завершен") -> SManga.COMPLETED - element.contains("выпуск продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.chapters-link tbody tr" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val urlText = urlElement.text() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mtr=1") - if (urlText.endsWith(" новое")) { - chapter.name = urlText.dropLast(6) - } else { - chapter.name = urlText - } - chapter.date_upload = element.select("td.hidden-xxs").last()?.text()?.let { - try { - SimpleDateFormat("dd/MM/yy", Locale.US).parse(it)?.time ?: 0L - } catch (e: ParseException) { - SimpleDateFormat("dd.MM.yy", Locale.US).parse(it)?.time ?: 0L - } - } ?: 0 - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - val basic = Regex("""\s*([0-9]+)(\s-\s)([0-9]+)\s*""") - val extra = Regex("""\s*([0-9]+\sЭкстра)\s*""") - val single = Regex("""\s*Сингл\s*""") - when { - basic.containsMatchIn(chapter.name) -> { - basic.find(chapter.name)?.let { - val number = it.groups[3]?.value!! - chapter.chapter_number = number.toFloat() - } - } - extra.containsMatchIn(chapter.name) -> // Extra chapters doesn't contain chapter number - chapter.chapter_number = -2f - single.containsMatchIn(chapter.name) -> // Oneshoots, doujinshi and other mangas with one chapter - chapter.chapter_number = 1f - } - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val beginIndex = html.indexOf("rm_h.init( [") - val endIndex = html.indexOf(");", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex) - - val p = Pattern.compile("'.*?','.*?',\".*?\"") - val m = p.matcher(trimmedHtml) - - val pages = mutableListOf<Page>() - - var i = 0 - while (m.find()) { - val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') - val url = if (urlParts[1].isEmpty() && urlParts[2].startsWith("/static/")) { - baseUrl + urlParts[2] - } else { - if (urlParts[1].endsWith("/manga/")) { - urlParts[0] + urlParts[2] - } else { - urlParts[1] + urlParts[0] + urlParts[2] - } - } - pages.add(Page(i++, "", url)) - } - return pages - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - override fun imageRequest(page: Page): Request { - val imgHeader = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("Referer", baseUrl) - }.build() - return GET(page.imageUrl!!, imgHeader) - } - - private class Genre(name: String, val id: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) - private class Category(categories: List<Genre>) : Filter.Group<Genre>("Category", categories) - - /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")] - * .map(el => `Genre("${el.textContent.trim()}", $"{el.getAttribute('onclick') - * .substr(31,el.getAttribute('onclick').length-33)"})`).join(',\n') - * on https://selfmanga.ru/search/advanced - */ - override fun getFilterList() = FilterList( - Category(getCategoryList()), - GenreList(getGenreList()) - ) - - private fun getCategoryList() = listOf( - Genre("Артбук", "el_5894"), - Genre("Веб", "el_2160"), - Genre("Журнал", "el_4983"), - Genre("Ранобэ", "el_5215"), - Genre("Сборник", "el_2157") - ) - - private fun getGenreList() = listOf( - Genre("боевик", "el_2155"), - Genre("боевые искусства", "el_2143"), - Genre("вампиры", "el_2148"), - Genre("гарем", "el_2142"), - Genre("гендерная интрига", "el_2156"), - Genre("героическое фэнтези", "el_2146"), - Genre("детектив", "el_2152"), - Genre("дзёсэй", "el_2158"), - Genre("додзинси", "el_2141"), - Genre("драма", "el_2118"), - Genre("ёнкома", "el_2161"), - Genre("история", "el_2119"), - Genre("комедия", "el_2136"), - Genre("махо-сёдзё", "el_2147"), - Genre("мистика", "el_2132"), - Genre("научная фантастика", "el_2133"), - Genre("повседневность", "el_2135"), - Genre("постапокалиптика", "el_2151"), - Genre("приключения", "el_2130"), - Genre("психология", "el_2144"), - Genre("романтика", "el_2121"), - Genre("сверхъестественное", "el_2159"), - Genre("сёдзё", "el_2122"), - Genre("сёдзё-ай", "el_2128"), - Genre("сёнэн", "el_2134"), - Genre("сёнэн-ай", "el_2139"), - Genre("спорт", "el_2129"), - Genre("сэйнэн", "el_5838"), - Genre("трагедия", "el_2153"), - Genre("триллер", "el_2150"), - Genre("ужасы", "el_2125"), - Genre("фантастика", "el_2140"), - Genre("фэнтези", "el_2131"), - Genre("школа", "el_2127"), - Genre("этти", "el_4982") - ) -} diff --git a/src/ru/yaoichan/AndroidManifest.xml b/src/ru/yaoichan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/yaoichan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/ru/yaoichan/build.gradle b/src/ru/yaoichan/build.gradle deleted file mode 100644 index d1ea2dde3..000000000 --- a/src/ru/yaoichan/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Yaoichan' - pkgNameSuffix = 'ru.yaoichan' - extClass = '.Yaoichan' - extVersionCode = 1 - libVersion = '1.2' - containsNsfw = true -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/yaoichan/res/mipmap-hdpi/ic_launcher.png b/src/ru/yaoichan/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 84c832102..000000000 Binary files a/src/ru/yaoichan/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/yaoichan/res/mipmap-mdpi/ic_launcher.png b/src/ru/yaoichan/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fd12030ba..000000000 Binary files a/src/ru/yaoichan/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/yaoichan/res/mipmap-xhdpi/ic_launcher.png b/src/ru/yaoichan/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 30ff23252..000000000 Binary files a/src/ru/yaoichan/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/yaoichan/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/yaoichan/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index df3af6c1a..000000000 Binary files a/src/ru/yaoichan/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 208623080..000000000 Binary files a/src/ru/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ru/yaoichan/res/web_hi_res_512.png b/src/ru/yaoichan/res/web_hi_res_512.png deleted file mode 100644 index 1d33fc5a7..000000000 Binary files a/src/ru/yaoichan/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt b/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt deleted file mode 100644 index cc71b7e70..000000000 --- a/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt +++ /dev/null @@ -1,271 +0,0 @@ -package eu.kanade.tachiyomi.extension.ru.yaoichan - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -@Nsfw -class Yaoichan : ParsedHttpSource() { - - override val name = "Yaoichan" - - override val baseUrl = "https://yaoi-chan.me" - - override val lang = "ru" - - override val supportsLatest = true - - private val rateLimitInterceptor = RateLimitInterceptor(2) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(rateLimitInterceptor).build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/manga/new?offset=${20 * (page - 1)}", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = if (query.isNotEmpty()) { - "$baseUrl/?do=search&subaction=search&story=$query&search_start=$page" - } else { - - var genres = "" - var order = "" - var statusParam = true - var status = "" - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is GenreList -> { - filter.state.forEach { f -> - if (!f.isIgnored()) { - genres += (if (f.isExcluded()) "-" else "") + f.id + '+' - } - } - } - is OrderBy -> { - if (filter.state!!.ascending && filter.state!!.index == 0) { - statusParam = false - } - } - is Status -> status = arrayOf("", "all_done", "end", "ongoing", "new_ch")[filter.state] - } - } - - if (genres.isNotEmpty()) { - for (filter in filters) { - when (filter) { - is OrderBy -> { - order = if (filter.state!!.ascending) { - arrayOf("", "&n=favasc", "&n=abcdesc", "&n=chasc")[filter.state!!.index] - } else { - arrayOf("&n=dateasc", "&n=favdesc", "&n=abcasc", "&n=chdesc")[filter.state!!.index] - } - } - } - } - if (statusParam) { - "$baseUrl/tags/${genres.dropLast(1)}$order?offset=${20 * (page - 1)}&status=$status" - } else { - "$baseUrl/tags/$status/${genres.dropLast(1)}/$order?offset=${20 * (page - 1)}" - } - } else { - for (filter in filters) { - when (filter) { - is OrderBy -> { - order = if (filter.state!!.ascending) { - arrayOf("manga/new", "manga/new&n=favasc", "manga/new&n=abcdesc", "manga/new&n=chasc")[filter.state!!.index] - } else { - arrayOf("manga/new&n=dateasc", "mostfavorites", "catalog", "sortch")[filter.state!!.index] - } - } - } - } - if (statusParam) { - "$baseUrl/$order?offset=${20 * (page - 1)}&status=$status" - } else { - "$baseUrl/$order/$status?offset=${20 * (page - 1)}" - } - } - } - return GET(url, headers) - } - - override fun popularMangaSelector() = "div.content_row" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.manga_images img").first().attr("src") - element.select("h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a:contains(Вперед)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = "a:contains(Далее), ${popularMangaNextPageSelector()}" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("table.mangatitle").first() - val descElement = document.select("div#description").first() - val imgElement = document.select("img#cover").first() - val rawCategory = infoElement.select("tr:eq(1) > td:eq(1)").text() - val category = if (rawCategory.isNotEmpty()) { - rawCategory.toLowerCase() - } else { - "манга" - } - val manga = SManga.create() - manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() - manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text().split(",").plusElement(category).joinToString { it.trim() } - manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) - manga.description = descElement.textNodes().first().text() - manga.thumbnail_url = imgElement.attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("перевод завершен") -> SManga.COMPLETED - element.contains("перевод продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "table.table_cha tr:gt(1)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("div.date").first()?.text()?.let { - SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it)?.time ?: 0L - } ?: 0 - return chapter - } - - override fun pageListParse(response: Response): List<Page> { - val html = response.body()!!.string() - val beginIndex = html.indexOf("fullimg\":[") + 10 - val endIndex = html.indexOf(",]", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") - val pageUrls = trimmedHtml.split(',') - - return pageUrls.mapIndexed { i, url -> Page(i, "", url) } - } - - override fun pageListParse(document: Document): List<Page> { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres) - private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) - private class Status : Filter.Select<String>("Статус", arrayOf("Все", "Перевод завершен", "Выпуск завершен", "Онгоинг", "Новые главы")) - private class OrderBy : Filter.Sort( - "Сортировка", - arrayOf("Дата", "Популярность", "Имя", "Главы"), - Selection(1, false) - ) - - override fun getFilterList() = FilterList( - Status(), - OrderBy(), - GenreList(getGenreList()) - ) - - /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")] - * .map(el => `Genre("${el.getAttribute('href').substr(6)}")`).join(',\n') - * on https://yaoi-chan.me/catalog - */ - private fun getGenreList() = listOf( - Genre("18 плюс"), - Genre("bdsm"), - Genre("арт"), - Genre("бара"), - Genre("боевик"), - Genre("боевые искусства"), - Genre("вампиры"), - Genre("веб"), - Genre("гарем"), - Genre("гендерная интрига"), - Genre("героическое фэнтези"), - Genre("групповой секс"), - Genre("детектив"), - Genre("дзёсэй"), - Genre("додзинси"), - Genre("драма"), - Genre("игра"), - Genre("инцест"), - Genre("искусство"), - Genre("история"), - Genre("киберпанк"), - Genre("комедия"), - Genre("литРПГ"), - Genre("махо-сёдзё"), - Genre("меха"), - Genre("мистика"), - Genre("мужская беременность"), - Genre("музыка"), - Genre("научная фантастика"), - Genre("омегаверс"), - Genre("переодевание"), - Genre("повседневность"), - Genre("постапокалиптика"), - Genre("приключения"), - Genre("психология"), - Genre("романтика"), - Genre("самурайский боевик"), - Genre("сборник"), - Genre("сверхъестественное"), - Genre("сетакон"), - Genre("сказка"), - Genre("спорт"), - Genre("супергерои"), - Genre("сэйнэн"), - Genre("сёдзё"), - Genre("сёдзё-ай"), - Genre("сёнэн"), - Genre("сёнэн-ай"), - Genre("тентакли"), - Genre("трагедия"), - Genre("триллер"), - Genre("ужасы"), - Genre("фантастика"), - Genre("фурри"), - Genre("фэнтези"), - Genre("школа"), - Genre("эротика"), - Genre("юмор"), - Genre("юри"), - Genre("яой"), - Genre("ёнкома") - ) -} diff --git a/src/th/nekopost/AndroidManifest.xml b/src/th/nekopost/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/th/nekopost/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/th/nekopost/build.gradle b/src/th/nekopost/build.gradle deleted file mode 100644 index 9388e8dfd..000000000 --- a/src/th/nekopost/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Nekopost' - pkgNameSuffix = 'th.nekopost' - extClass = '.Nekopost' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/th/nekopost/res/mipmap-hdpi/ic_launcher.png b/src/th/nekopost/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0eb1c3f64..000000000 Binary files a/src/th/nekopost/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/th/nekopost/res/mipmap-mdpi/ic_launcher.png b/src/th/nekopost/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 00a92c30d..000000000 Binary files a/src/th/nekopost/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/th/nekopost/res/mipmap-xhdpi/ic_launcher.png b/src/th/nekopost/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f7e232a5c..000000000 Binary files a/src/th/nekopost/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/th/nekopost/res/mipmap-xxhdpi/ic_launcher.png b/src/th/nekopost/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 435229f04..000000000 Binary files a/src/th/nekopost/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/th/nekopost/res/mipmap-xxxhdpi/ic_launcher.png b/src/th/nekopost/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index da031792c..000000000 Binary files a/src/th/nekopost/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/th/nekopost/res/web_hi_res_512.png b/src/th/nekopost/res/web_hi_res_512.png deleted file mode 100644 index f7b793e7e..000000000 Binary files a/src/th/nekopost/res/web_hi_res_512.png and /dev/null differ diff --git a/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/NPUtils.kt b/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/NPUtils.kt deleted file mode 100644 index bab46114f..000000000 --- a/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/NPUtils.kt +++ /dev/null @@ -1,79 +0,0 @@ -package eu.kanade.tachiyomi.extension.th.nekopost - -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.ArrayList -import java.util.Locale - -class NPArrayList<E>(c: Collection<E>, val mangaList: List<Element>) : ArrayList<E>(c) { - override fun isEmpty(): Boolean = mangaList.isEmpty() - - fun isNotEmpty(): Boolean = mangaList.isNotEmpty() - - fun isListEmpty(): Boolean = super.isEmpty() - - fun isListNotEmpty(): Boolean = !isListEmpty() -} - -object NPUtils { - private val urlWithoutDomainFromFullUrlRegex: Regex = Regex("^https://www\\.nekopost\\.net/manga/(.*)$") - - fun getMangaOrChapterAlias(url: String): String { - val (urlWithoutDomain) = urlWithoutDomainFromFullUrlRegex.find(url)!!.destructured - return urlWithoutDomain - } - - fun convertDateStringToEpoch(dateStr: String, format: String = "yyyy-MM-dd"): Long = SimpleDateFormat(format, Locale("th")).parse(dateStr)?.time ?: 0L - - fun getSearchQuery(keyword: String = "", genreList: Array<String>, statusList: Array<String>): String { - val keywordQuery = "ip_keyword=$keyword" - - val genreQuery = genreList.joinToString("&") { genre -> "ip_genre[]=${getValueOf(Genre, genre)}" } - - val statusQuery = statusList.let { - if (it.isNotEmpty()) it.map { status -> getValueOf(Status, status) } - else Status.map { status -> status.second } - }.joinToString("&") { status -> "ip_status[]=$status" } - - val typeQuery = "ip_type[]=m" - - return "$keywordQuery&$genreQuery&$statusQuery&$typeQuery" - } - - val Genre = arrayOf( - Pair("Fantasy", 1), - Pair("Action", 2), - Pair("Drama", 3), - Pair("Sport", 5), - Pair("Sci-fi", 7), - Pair("Comedy", 8), - Pair("Slice of Life", 9), - Pair("Romance", 10), - Pair("Adventure", 13), - Pair("Yaoi", 23), - Pair("Yuri", 24), - Pair("Trap", 25), - Pair("Gender Bender", 26), - Pair("Mystery", 32), - Pair("Doujinshi", 37), - Pair("Grume", 41), - Pair("Shoujo", 42), - Pair("School Life", 43), - Pair("Isekai", 44), - Pair("Shounen", 46), - Pair("Second Life", 45), - Pair("Horror", 47), - Pair("One short", 48), - Pair("Seinen", 49) - ).sortedWith(compareBy { it.first }).toTypedArray() - - val Status = arrayOf( - Pair("Ongoing", 1), - Pair("Completed", 2), - Pair("Licensed", 3) - ) - - fun <T, F, S> getValueOf(array: Array<T>, name: F): S? where T : Pair<F, S> = array.find { genre -> genre.first == name }?.second - - val monthList: List<String> = listOf("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC") -} diff --git a/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/Nekopost.kt b/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/Nekopost.kt deleted file mode 100644 index ed60292c2..000000000 --- a/src/th/nekopost/src/eu/kanade/tachiyomi/extension/th/nekopost/Nekopost.kt +++ /dev/null @@ -1,477 +0,0 @@ -package eu.kanade.tachiyomi.extension.th.nekopost - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.net.URL -import java.util.Calendar -import java.util.Locale -import kotlin.collections.set - -class Nekopost : ParsedHttpSource() { - override val baseUrl: String = "https://www.nekopost.net/manga/" - - private val mangaListUrl: String = "https://www.nekopost.net/project/ajax_load_update/m/" - private val baseFileUrl: String = "https://fs.nekopost.net/" - private val legacyChapterDataUrl: String = "https://www.nekopost.net/reader/loadChapterContent/" - private val searchUrl: String = "https://www.nekopost.net/search/" - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } - - override val lang: String = "th" - override val name: String = "Nekopost" - - override val supportsLatest: Boolean = true - - private var latestMangaList: HashSet<String> = HashSet() - private var popularMangaList: HashSet<String> = HashSet() - - private val projectList: HashMap<Int, ProjectParser.ProjectData> = HashMap() - private val projectParser: ProjectParser = ProjectParser() - - object NP { - class Chapter : SChapter { - override lateinit var url: String - - override lateinit var name: String - - override var date_upload: Long = 0 - - override var chapter_number: Float = -1f - - override var scanlator: String? = null - - lateinit var chapterData: ProjectParser.ProjectData.ChapterInfo - lateinit var projectData: ProjectParser.ProjectData - } - - class Manga : SManga { - override lateinit var url: String - - override lateinit var title: String - - override var artist: String? = null - - override var author: String? = null - - override var description: String? = null - - override var genre: String? = null - - override var status: Int = 0 - - override var thumbnail_url: String? = null - - override var initialized: Boolean = false - - lateinit var projectData: ProjectParser.ProjectData - } - } - - inner class ProjectParser { - - inner class ProjectData { - inner class ProjectInfo { - var np_project_id: Int = 0 - var np_name: String = "" - var np_info: String? = null - var np_view: Int = 0 - var np_no_chapter: Int = 0 - var np_created_date: Long? = null - var np_updated_date: Long? = null - var np_status: Int = 0 - var np_author: String? = null - var np_artist: String? = null - } - - inner class ChapterInfo { - var nc_chapter_id: Int = 0 - var nc_chapter_no: Float = 0f - var nc_chapter_name: String = "" - var nc_provider: String? = null - var nc_created_date: Long? = null - var nc_owner_id: Int? = null - var nc_data_file: String = "" - var legacy_data_file: Boolean = false - - fun getChapterJsonFolder(): String = "${baseFileUrl}collectManga/${info.np_project_id}/$nc_chapter_id/" - - val sChapter: NP.Chapter - get() = NP.Chapter().apply { - if (nc_chapter_no - nc_chapter_no.toInt() == 0f) setUrlWithoutDomain("${info.np_project_id}/${nc_chapter_no.toInt()}") - else setUrlWithoutDomain("${info.np_project_id}/$nc_chapter_no") - name = nc_chapter_name - if (nc_created_date != null) date_upload = nc_created_date!! - chapter_number = nc_chapter_no - scanlator = nc_provider - chapterData = this@ChapterInfo - projectData = this@ProjectData - }.also { chapterListMap[nc_chapter_no] = this } - } - - inner class ProjectCate { - var npc_id: Int = 0 - var npc_name: String = "" - var npc_name_link: String = "" - } - - val sManga: NP.Manga - get() = NP.Manga().apply { - setUrlWithoutDomain("${info.np_project_id}") - title = info.np_name - artist = info.np_artist - author = info.np_author - description = info.np_info - genre = projectCate.joinToString(", ") { it.npc_name } - status = info.np_status - thumbnail_url = getCoverUrl(this@ProjectData) - projectData = this@ProjectData - } - - fun getChapterData(chapterNo: Float): ChapterInfo? = - if (chapterListMap.contains(chapterNo)) chapterListMap[chapterNo] - else chapterList.find { it.nc_chapter_no == chapterNo } - - var info: ProjectInfo = ProjectInfo() - var chapterList: List<ChapterInfo> = emptyList() - private val chapterListMap: HashMap<Float, ProjectParser.ProjectData.ChapterInfo> = HashMap() - var projectCate: List<ProjectCate> = emptyList() - } - - private fun getProjectJsonFolder(projectID: Int): String = projectID.toDouble().let { - (it / 1000.0 - (it % 1000.0) / 1000.0).let { _tmp -> - var tmp = _tmp - - if (projectID % 1000 != 0) tmp += 1 - tmp *= 1000 - - tmp.toInt().toString().padStart(6, '0') - } - } - - private fun getProjectDataUrl(projectID: Int): String = "${baseFileUrl}collectJson/${getProjectJsonFolder(projectID)}/$projectID/${projectID}dtl.json" - - private fun getStatus(status: String) = when (status) { - "1" -> SManga.ONGOING - "2" -> SManga.COMPLETED - "3" -> SManga.LICENSED - else -> SManga.UNKNOWN - } - - fun getCoverUrl(projectData: ProjectData): String = "${baseFileUrl}collectManga/${projectData.info.np_project_id}/${projectData.info.np_project_id}_cover.jpg" - - fun getProjectData(projectID: Int): ProjectData { - return if (projectList.contains(projectID)) projectList[projectID]!! - else JSONObject(URL(getProjectDataUrl(projectID)).readText()) - .let { - ProjectData().apply { - info = it.getJSONObject("info").let { pInfo -> - ProjectInfo().apply { - np_project_id = pInfo.getString("np_project_id").toInt() - np_name = pInfo.getString("np_name") - np_info = pInfo.getString("np_info") - np_view = pInfo.getString("np_view").toInt() - np_no_chapter = pInfo.getString("np_no_chapter").toInt() - np_created_date = NPUtils.convertDateStringToEpoch(pInfo.getString("np_created_date"), "yyyy-MM-dd hh:mm:ss") - np_updated_date = NPUtils.convertDateStringToEpoch(pInfo.getString("np_updated_date"), "yyyy-MM-dd hh:mm:ss") - np_status = getStatus(pInfo.getString("np_status")) - np_author = pInfo.getString("np_author") - np_artist = pInfo.getString("np_artist") - } - } - - chapterList = it.getJSONArray("chapterList").let { chListData -> - val chList = ArrayList<ProjectData.ChapterInfo>() - - for (chIndex in 0 until chListData.length()) { - val chInfo = chListData.getJSONObject(chIndex) - - chList.add( - ChapterInfo().apply { - nc_chapter_id = chInfo.getString("nc_chapter_id").toInt() - nc_chapter_no = chInfo.getString("nc_chapter_no").toFloat() - nc_chapter_name = chInfo.getString("nc_chapter_name") - nc_provider = chInfo.getString("nc_provider") - nc_created_date = chInfo.getString("nc_created_date").let { - it.split("-").toTypedArray().apply { - this[1] = (NPUtils.monthList.indexOf(this[1].toUpperCase(Locale.ROOT)) + 1).toString().padStart(2, '0') - } - }.joinToString("-").let { NPUtils.convertDateStringToEpoch(it) } - nc_owner_id = chInfo.getString("nc_owner_id").toInt() - nc_data_file = chInfo.getString("nc_data_file").let { - if (it.isNullOrBlank()) { - legacy_data_file = true - if (nc_chapter_no - nc_chapter_no.toInt() == 0f) - nc_chapter_no.toInt().toString() - else - nc_chapter_no.toString() - } else { - it - } - } - } - ) - } - - chList - } - - projectCate = it.getJSONArray("projectCate").let { cateListData -> - val cateList = ArrayList<ProjectData.ProjectCate>() - - for (cateIndex in 0 until cateListData.length()) { - val cateInfo = cateListData.getJSONObject(cateIndex) - - if (cateInfo.getString("project_id") != "null") { - cateList.add( - ProjectCate().apply { - npc_id = cateInfo.getString("npc_id").toInt() - npc_name = cateInfo.getString("npc_name") - npc_name_link = cateInfo.getString("npc_name_link") - } - ) - } - } - - cateList - } - }.also { projectList[projectID] = it } - } - } - } - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - return if (manga.status != SManga.LICENSED) { - Observable.just( - projectParser.getProjectData(manga.url.toInt()).chapterList.map { it.sChapter } - ) - } else { - Observable.error(Exception("Licensed - No chapters to show")) - } - } - - override fun chapterListSelector(): String = throw NotImplementedError("Unused") - - override fun chapterFromElement(element: Element): SChapter = throw NotImplementedError("Unused") - - override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl) - - override fun imageUrlParse(document: Document): String = throw NotImplementedError("Unused") - - private var latestUpdatePageOffset: Int = 0 - - override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { - if (page == 1) { - latestMangaList = HashSet() - latestUpdatePageOffset = 0 - } - - return client.newCall(latestUpdatesRequest(page + latestUpdatePageOffset)) - .asObservableSuccess() - .concatMap { response -> - latestUpdatesParse(response).let { - if ((it.mangas as NPArrayList<SManga>).isListEmpty() && it.mangas.isNotEmpty()) { - latestUpdatePageOffset++ - fetchLatestUpdates(page) - } else Observable.just(it) - } - } - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangaList = document.select(latestUpdatesSelector()).filter { element -> - val dateText = element.select(".date").text().trim() - val currentDate = Calendar.getInstance(Locale("th")) - - dateText.contains(currentDate.get(Calendar.DATE).toString()) && dateText.contains(NPUtils.monthList[currentDate.get(Calendar.MONTH)]) - } - - val mangas = NPArrayList( - mangaList.map { element -> latestUpdatesFromElement(element) }.filter { manga -> - if (!latestMangaList.contains(manga.url)) { - latestMangaList.add(manga.url) - true - } else false - }, - mangaList - ) - - val hasNextPage = mangaList.isNotEmpty() - - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val projectID = NPUtils.getMangaOrChapterAlias(element.select("a").attr("href")).toInt() - return projectParser.getProjectData(projectID).sManga - } - - override fun latestUpdatesNextPageSelector(): String? = throw Exception("Unused") - - override fun latestUpdatesRequest(page: Int): Request = GET("$mangaListUrl/${page - 1}") - - override fun latestUpdatesSelector(): String = "a[href]" - - override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(projectParser.getProjectData(manga.url.toInt()).sManga) - - override fun mangaDetailsParse(document: Document): SManga = throw NotImplementedError("Unused") - - override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { - val chData = chapter.url.split("/") - val pj = projectParser.getProjectData(chData[0].toInt()) - val ch = pj.getChapterData(chData[1].toFloat())!! - val pageList: ArrayList<Page> = ArrayList() - - if (ch.legacy_data_file) { - JSONArray(URL("${legacyChapterDataUrl}${pj.info.np_project_id}/${ch.nc_data_file}").readText()).getJSONArray(3).let { pageItem -> - for (pageIndex in 0 until pageItem.length()) { - pageList.add( - pageItem.getJSONObject(pageIndex).let { pageData -> - Page( - pageData.getString("page_no").toInt() - 1, - "", - "${ch.getChapterJsonFolder()}${pageData.getString("value_url")}" - ) - } - ) - } - } - } else { - JSONObject(URL("${ch.getChapterJsonFolder()}${ch.nc_data_file}").readText()).getJSONArray("pageItem").let { pageItem -> - for (pageIndex in 0 until pageItem.length()) { - pageList.add( - pageItem.getJSONObject(pageIndex).let { pageData -> - Page( - pageData.getInt("pageNo") - 1, - "", - "${ch.getChapterJsonFolder()}${pageData.getString("fileName")}" - ) - } - ) - } - } - } - - return Observable.just(pageList) - } - - override fun pageListParse(document: Document): List<Page> = throw NotImplementedError("Unused") - - private var popularMangaPageOffset: Int = 0 - - override fun fetchPopularManga(page: Int): Observable<MangasPage> { - if (page == 1) { - popularMangaList = HashSet() - popularMangaPageOffset = 0 - } - - return client.newCall(popularMangaRequest(page + popularMangaPageOffset)) - .asObservableSuccess() - .concatMap { response -> - popularMangaParse(response).let { - if ((it.mangas as NPArrayList<SManga>).isListEmpty() && it.mangas.isNotEmpty()) { - popularMangaPageOffset++ - fetchPopularManga(page) - } else Observable.just(it) - } - } - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangaList = document.select(popularMangaSelector()) - - val mangas = NPArrayList( - mangaList.map { element -> popularMangaFromElement(element) }.filter { manga -> - if (!popularMangaList.contains(manga.url)) { - popularMangaList.add(manga.url) - true - } else false - }, - mangaList - ) - - val hasNextPage = mangaList.isNotEmpty() - - return MangasPage(mangas, hasNextPage) - } - - override fun popularMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element) - - override fun popularMangaNextPageSelector(): String? = latestUpdatesNextPageSelector() - - override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) - - override fun popularMangaSelector(): String = latestUpdatesSelector() - - override fun getFilterList(): FilterList = FilterList( - GenreFilter(), - StatusFilter() - ) - - private class GenreFilter : Filter.Group<GenreCheckbox>("Genre", NPUtils.Genre.map { genre -> GenreCheckbox(genre.first) }) - - private class GenreCheckbox(genre: String) : Filter.CheckBox(genre, false) - - private class StatusFilter : Filter.Group<StatusCheckbox>("Status", NPUtils.Status.map { status -> StatusCheckbox(status.first) }) - - private class StatusCheckbox(status: String) : Filter.CheckBox(status, false) - - override fun searchMangaFromElement(element: Element): SManga = projectParser.getProjectData(NPUtils.getMangaOrChapterAlias(element.attr("href")).toInt()).sManga - - override fun searchMangaNextPageSelector(): String? = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (page > 1) throw Error("No more page") - - var queryString = query - - val genreList: Array<String> = try { - (filters.find { filter -> filter is GenreFilter } as GenreFilter).state.filter { checkbox -> checkbox.state }.map { checkbox -> checkbox.name }.toTypedArray() - } catch (e: Exception) { - emptyArray<String>() - }.let { - when { - it.isNotEmpty() -> it - NPUtils.getValueOf(NPUtils.Genre, query) == null -> it - else -> { - queryString = "" - arrayOf(query) - } - } - } - - val statusList: Array<String> = try { - (filters.find { filter -> filter is StatusFilter } as StatusFilter).state.filter { checkbox -> checkbox.state }.map { checkbox -> checkbox.name }.toTypedArray() - } catch (e: Exception) { - emptyArray() - } - - return GET("$searchUrl?${NPUtils.getSearchQuery(queryString, genreList, statusList)}") - } - - override fun searchMangaSelector(): String = ".list_project .item .project_info a" -} diff --git a/src/tr/MangaDenizi/AndroidManifest.xml b/src/tr/MangaDenizi/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/tr/MangaDenizi/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/tr/MangaDenizi/build.gradle b/src/tr/MangaDenizi/build.gradle deleted file mode 100644 index 0faf012c9..000000000 --- a/src/tr/MangaDenizi/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MangaDenizi' - pkgNameSuffix = 'tr.mangadenizi' - extClass = '.MangaDenizi' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/tr/MangaDenizi/res/mipmap-hdpi/ic_launcher.png b/src/tr/MangaDenizi/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 412911dd1..000000000 Binary files a/src/tr/MangaDenizi/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/MangaDenizi/res/mipmap-mdpi/ic_launcher.png b/src/tr/MangaDenizi/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e1223d1be..000000000 Binary files a/src/tr/MangaDenizi/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/MangaDenizi/res/mipmap-xhdpi/ic_launcher.png b/src/tr/MangaDenizi/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 7cbfcdd5b..000000000 Binary files a/src/tr/MangaDenizi/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/MangaDenizi/res/mipmap-xxhdpi/ic_launcher.png b/src/tr/MangaDenizi/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2ae5244d8..000000000 Binary files a/src/tr/MangaDenizi/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/MangaDenizi/res/mipmap-xxxhdpi/ic_launcher.png b/src/tr/MangaDenizi/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0b07b8fef..000000000 Binary files a/src/tr/MangaDenizi/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/MangaDenizi/res/web_hi_res_512.png b/src/tr/MangaDenizi/res/web_hi_res_512.png deleted file mode 100644 index 35a3ebe62..000000000 Binary files a/src/tr/MangaDenizi/res/web_hi_res_512.png and /dev/null differ diff --git a/src/tr/MangaDenizi/src/eu/kanade/tachiyomi/extension/tr/mangadenizi/MangaDenizi.kt b/src/tr/MangaDenizi/src/eu/kanade/tachiyomi/extension/tr/mangadenizi/MangaDenizi.kt deleted file mode 100644 index 30921d97b..000000000 --- a/src/tr/MangaDenizi/src/eu/kanade/tachiyomi/extension/tr/mangadenizi/MangaDenizi.kt +++ /dev/null @@ -1,135 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.mangadenizi - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Response -import org.json.JSONObject -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MangaDenizi : ParsedHttpSource() { - override val name = "MangaDenizi" - - override val baseUrl = "https://mangadenizi.com" - - override val lang = "tr" - - override val supportsLatest = true - - override val client = network.cloudflareClient - - override fun popularMangaSelector() = "div.media-left" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga-list?page=$page", headers) - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - title = element.select("img").attr("alt") - thumbnail_url = element.select("img").attr("abs:src") - } - - override fun popularMangaNextPageSelector() = "[rel=next]" - - override fun latestUpdatesSelector() = "h3 > a" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/latest-release?page=$page", headers) - - // No thumbnail on latest releases page - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.text() - } - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()) - .distinctBy { it.text().trim() } - .map { latestUpdatesFromElement(it) } - val hasNextPage = latestUpdatesNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaSelector() = "Unused" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/search?query=$query", headers) - - override fun searchMangaNextPageSelector() = "Unused" - override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Unused") - - override fun searchMangaParse(response: Response): MangasPage { - val res = response.body()!!.string() - return getMangasPage(res) - } - - private fun getMangasPage(json: String): MangasPage { - val response = JSONObject(json) - val results = response.getJSONArray("suggestions") - val mangas = ArrayList<SManga>() - - // No thumbnail here either - for (i in 0 until results.length()) { - val obj = results.getJSONObject(i) - val manga = SManga.create() - manga.title = obj.getString("value") - manga.url = "/manga/${obj.getString("data")}" - mangas.add(manga) - } - - return MangasPage(mangas, false) - } - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - description = document.select(".well > p").text() - genre = document.select("dd > a[href*=category]").joinToString { it.text() } - status = parseStatus(document.select(".label.label-success").text()) - thumbnail_url = document.select("img.img-responsive").attr("abs:src") - } - - private fun parseStatus(status: String) = when { - status.contains("Devam Ediyor") -> SManga.ONGOING - status.contains("Tamamlandı") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "ul.chapters li" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = "${element.select("a").text()}: ${element.select("em").text()}" - date_upload = try { - dateFormat.parse(element.select("div.date-chapter-title-rtl").text().trim())?.time ?: 0 - } catch (_: Exception) { - 0 - } - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("dd MMM. yyyy", Locale.US) - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("img.img-responsive").mapIndexed { i, element -> - val url = if (element.hasAttr("data-src")) element.attr("abs:data-src") else element.attr("abs:src") - Page(i, "", url) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun getFilterList() = FilterList() -} diff --git a/src/tr/mangaship/AndroidManifest.xml b/src/tr/mangaship/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/tr/mangaship/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/tr/mangaship/build.gradle b/src/tr/mangaship/build.gradle deleted file mode 100644 index 8fae77c1b..000000000 --- a/src/tr/mangaship/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manga Ship' - pkgNameSuffix = 'tr.mangaship' - extClass = '.MangaShip' - extVersionCode = 2 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-dataimage') -} - -apply from: "$rootDir/common.gradle" - diff --git a/src/tr/mangaship/res/mipmap-hdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9dce78c31..000000000 Binary files a/src/tr/mangaship/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/mangaship/res/mipmap-mdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e50537d01..000000000 Binary files a/src/tr/mangaship/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/mangaship/res/mipmap-xhdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 395cee294..000000000 Binary files a/src/tr/mangaship/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/mangaship/res/mipmap-xxhdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index fca129e10..000000000 Binary files a/src/tr/mangaship/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/mangaship/res/mipmap-xxxhdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a92a4a389..000000000 Binary files a/src/tr/mangaship/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/mangaship/res/web_hi_res_512.png b/src/tr/mangaship/res/web_hi_res_512.png deleted file mode 100644 index 9cb5ebcbc..000000000 Binary files a/src/tr/mangaship/res/web_hi_res_512.png and /dev/null differ diff --git a/src/tr/mangaship/src/eu/kanade/tachiyomi/extension/tr/mangaship/MangaShip.kt b/src/tr/mangaship/src/eu/kanade/tachiyomi/extension/tr/mangaship/MangaShip.kt deleted file mode 100644 index 5626faaac..000000000 --- a/src/tr/mangaship/src/eu/kanade/tachiyomi/extension/tr/mangaship/MangaShip.kt +++ /dev/null @@ -1,107 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.mangaship - -import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor -import eu.kanade.tachiyomi.lib.dataimage.dataImageAsUrl -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class MangaShip : ParsedHttpSource() { - - override val name = "Manga Ship" - - override val baseUrl = "https://www.mangaship.com" - - override val lang = "tr" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor(DataImageInterceptor()) - .build() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/Tr/PopulerMangalar?page=$page", headers) - } - - override fun popularMangaSelector() = "div.movie-item-contents" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.movie-item-title a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("img").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "li.active + li a:not(.lastpage)" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/Tr/YeniMangalar?page=$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/Tr/Search?kelime=$query&tur=Manga&page=$page", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - thumbnail_url = document.select("div.dec-review-img img").attr("abs:src") - genre = document.select("div.col-md-10 li:contains(Kategori) div a").joinToString { it.text() } - author = document.select("div.col-md-10 li:contains(Yazar) div a").text() - description = document.select("div.details-dectiontion p").joinToString("\n") { it.text() } - } - } - - // Chapters - - override fun chapterListSelector() = "div.item > div" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("div.plylist-single-content > a[title]").let { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.reading-content-manga img").mapIndexed { i, img -> - Page(i, "", img.dataImageAsUrl("src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/tr/serimanga/AndroidManifest.xml b/src/tr/serimanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/tr/serimanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/tr/serimanga/build.gradle b/src/tr/serimanga/build.gradle deleted file mode 100644 index 225f95460..000000000 --- a/src/tr/serimanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'SeriManga' - pkgNameSuffix = 'tr.serimanga' - extClass = '.SeriManga' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/tr/serimanga/res/mipmap-hdpi/ic_launcher.png b/src/tr/serimanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index aacabce94..000000000 Binary files a/src/tr/serimanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/serimanga/res/mipmap-mdpi/ic_launcher.png b/src/tr/serimanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a4c90279f..000000000 Binary files a/src/tr/serimanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/serimanga/res/mipmap-xhdpi/ic_launcher.png b/src/tr/serimanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 306b873a3..000000000 Binary files a/src/tr/serimanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/serimanga/res/mipmap-xxhdpi/ic_launcher.png b/src/tr/serimanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 3d7b1ce11..000000000 Binary files a/src/tr/serimanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/serimanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/tr/serimanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0818d8e75..000000000 Binary files a/src/tr/serimanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/tr/serimanga/res/web_hi_res_512.png b/src/tr/serimanga/res/web_hi_res_512.png deleted file mode 100644 index eee3bf48c..000000000 Binary files a/src/tr/serimanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/tr/serimanga/src/eu/kanade/tachiyomi/extension/tr/serimanga/SeriManga.kt b/src/tr/serimanga/src/eu/kanade/tachiyomi/extension/tr/serimanga/SeriManga.kt deleted file mode 100644 index 2178fcd1a..000000000 --- a/src/tr/serimanga/src/eu/kanade/tachiyomi/extension/tr/serimanga/SeriManga.kt +++ /dev/null @@ -1,127 +0,0 @@ -package eu.kanade.tachiyomi.extension.tr.serimanga - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class SeriManga : ParsedHttpSource() { - override val name = "SeriManga" - - override val baseUrl = "https://serimanga.com" - - override val lang = "tr" - - override val supportsLatest = true - - override val client = network.cloudflareClient - - override fun popularMangaSelector() = "a.manga-list-bg" - - override fun popularMangaRequest(page: Int): Request { - return if (page == 1) { - GET("$baseUrl/mangalar", headers) - } else { - GET("$baseUrl/mangalar?page=$page", headers) - } - } - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.select("span.mlb-name").text() - thumbnail_url = styleToUrl(element).removeSurrounding("'") - } - - private fun styleToUrl(element: Element): String { - return element.attr("style").substringAfter("(").substringBefore(")") - } - - override fun popularMangaNextPageSelector() = "[rel=next]" - - override fun latestUpdatesSelector() = "a.sli2-img" - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?a=a&page=$page", headers) - - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - title = element.attr("title") - thumbnail_url = styleToUrl(element) - } - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/mangalar?search=$query&page=$page", headers) - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - description = document.select(".demo1").text() - genre = document.select("div.spc2rcrc-links > a").joinToString { it.text() } - status = document.select("div.is-status.is-status--green").text().let { - parseStatus(it) - } - thumbnail_url = document.select("[rel=image_src]").attr("href") - } - - private fun parseStatus(status: String) = when { - status.contains("CONTINUES") -> SManga.ONGOING - status.contains("Tamamlanmış") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = mutableListOf<SChapter>() - var document = response.asJsoup() - var continueParsing = true - - while (continueParsing) { - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - document.select(popularMangaNextPageSelector()).let { - if (it.isNotEmpty()) { - document = client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup() - } else { - continueParsing = false - } - } - } - return chapters - } - - override fun chapterListSelector() = "ul.spl-list > li" - - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = "${element.select("span").first().text()}: ${element.select("span")[1].text()}" - date_upload = dateFormat.parse(element.select("span")[2].ownText())?.time ?: 0 - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("dd MMMM yyyy", Locale("tr")) - } - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("div.reader-manga > img").mapIndexed { i, element -> - val url = if (element.hasAttr("data-src"))element.attr("data-src") else element.attr("src") - Page(i, "", url) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") - - override fun getFilterList() = FilterList() -} diff --git a/src/vi/academyvn/AndroidManifest.xml b/src/vi/academyvn/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/academyvn/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/academyvn/build.gradle b/src/vi/academyvn/build.gradle deleted file mode 100644 index fc0f9bcac..000000000 --- a/src/vi/academyvn/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HocVienTruyenTranh' - pkgNameSuffix = 'vi.academyvn' - extClass = '.HocVienTruyenTranh' - extVersionCode = 8 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/academyvn/res/mipmap-hdpi/ic_launcher.png b/src/vi/academyvn/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index aa216e032..000000000 Binary files a/src/vi/academyvn/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/academyvn/res/mipmap-mdpi/ic_launcher.png b/src/vi/academyvn/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e0a507b94..000000000 Binary files a/src/vi/academyvn/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/academyvn/res/mipmap-xhdpi/ic_launcher.png b/src/vi/academyvn/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 26c881488..000000000 Binary files a/src/vi/academyvn/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/academyvn/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/academyvn/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f96bc8c3b..000000000 Binary files a/src/vi/academyvn/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/academyvn/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/academyvn/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 95e2acbae..000000000 Binary files a/src/vi/academyvn/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/academyvn/res/web_hi_res_512.png b/src/vi/academyvn/res/web_hi_res_512.png deleted file mode 100644 index cb649f2d4..000000000 Binary files a/src/vi/academyvn/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/academyvn/src/eu/kanade/tachiyomi/extension/vi/academyvn/HocVienTruyenTranh.kt b/src/vi/academyvn/src/eu/kanade/tachiyomi/extension/vi/academyvn/HocVienTruyenTranh.kt deleted file mode 100644 index 9d2fc63bf..000000000 --- a/src/vi/academyvn/src/eu/kanade/tachiyomi/extension/vi/academyvn/HocVienTruyenTranh.kt +++ /dev/null @@ -1,210 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.academyvn - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.Calendar - -class HocVienTruyenTranh : ParsedHttpSource() { - - override val name = "HocVienTruyenTranh" - - override val baseUrl = "https://hocvientruyentranh.net" - - override val lang = "vi" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl) - - override fun popularMangaSelector() = "table.table.table-hover > tbody > tr" - - override fun latestUpdatesSelector() = "table.table.table-hover > tbody > tr" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/truyen/all?filter_type=view&page=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/truyen/all?filter_type=latest-chapter&page=$page", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - manga.thumbnail_url = it.attr("data-thumbnail") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "li > a:contains(»)" - - override fun latestUpdatesNextPageSelector(): String = "li > a:contains(»)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/searchs?")!!.newBuilder().addQueryParameter("keyword", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Type -> url.addQueryParameter("type", if (filter.state == 0) "-1" else type.indexOf(filter.state.toString()).toString()) - is GenreList -> filter.state.forEachIndexed { index, genre -> - if (genre.state != 0) url.addQueryParameter("genres[]", (index + 1).toString()) - } - is Status -> url.addQueryParameter("status", if (filter.state == 0) "-1" else status.indexOf(filter.state.toString()).toString()) - } - } - url.addQueryParameter("submit", "Tìm+kiếm") - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun searchMangaNextPageSelector() = "li > a:contains(»)" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.__info-container").first() - - val manga = SManga.create() - manga.author = infoElement.select("p:has(strong:contains(Tác giả:)) > a").first()?.text() - manga.genre = infoElement.select("p:has(strong:contains(Thể loại:)) > *:gt(0)").joinToString { it.text() } - manga.description = infoElement.select("div.__description > p").text() - manga.status = infoElement.select("p:has(strong:contains(Tình trạng:))").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = document.select("div.__left img").first()?.attr("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Đang tiến hành") -> SManga.ONGOING - status.contains("Đã hoàn thành") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.table-scroll > table.table.table-hover > tbody > tr" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.attr("title") - chapter.date_upload = element.select("td.text-center").last()?.text()?.let { parseChapterDate(it) } - ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - val dateWords: List<String> = date.split(" ") - if (dateWords.size == 3) { - val timeAgo = Integer.parseInt(dateWords[0]) - val dates: Calendar = Calendar.getInstance() - when { - dateWords[1].contains("minute") -> { - dates.add(Calendar.MINUTE, -timeAgo) - } - dateWords[1].contains("hour") -> { - dates.add(Calendar.HOUR_OF_DAY, -timeAgo) - } - dateWords[1].contains("day") -> { - dates.add(Calendar.DAY_OF_YEAR, -timeAgo) - } - dateWords[1].contains("week") -> { - dates.add(Calendar.WEEK_OF_YEAR, -timeAgo) - } - dateWords[1].contains("month") -> { - dates.add(Calendar.MONTH, -timeAgo) - } - dateWords[1].contains("year") -> { - dates.add(Calendar.YEAR, -timeAgo) - } - } - return dates.timeInMillis - } - return 0L - } - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - document.select("div.manga-container > img").forEach { - pages.add(Page(pages.size, "", it.attr("src"))) - } - return pages - } - - override fun imageUrlParse(document: Document): String = throw Exception("Not Used") - - private var type = arrayOf("Khác", "Manga", "Manhwa", "Manhua", "Tất cả") - private var status = arrayOf("Ngưng", "Đang tiến hành", "Đã hoàn thành", "Tất cả") - - private class Type : Filter.Select<String>("Type", arrayOf("Khác", "Manga", "Manhwa", "Manhua", "Tất cả")) - private class Status : Filter.Select<String>("Status", arrayOf("Ngưng", "Đang tiến hành", "Đã hoàn thành", "Tất cả")) - private class Genre(name: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres) - - override fun getFilterList() = FilterList( - Type(), - Status(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Comedy"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("One shot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Webtoon"), - Genre("Yaoi"), - Genre("Yuri"), - Genre("Hot") - ) -} diff --git a/src/vi/blogtruyen/AndroidManifest.xml b/src/vi/blogtruyen/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/blogtruyen/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/blogtruyen/build.gradle b/src/vi/blogtruyen/build.gradle deleted file mode 100644 index 1ae91da4c..000000000 --- a/src/vi/blogtruyen/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'BlogTruyen' - pkgNameSuffix = 'vi.blogtruyen' - extClass = '.BlogTruyen' - extVersionCode = 8 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/blogtruyen/res/mipmap-hdpi/ic_launcher.png b/src/vi/blogtruyen/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 08acf9eee..000000000 Binary files a/src/vi/blogtruyen/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/blogtruyen/res/mipmap-mdpi/ic_launcher.png b/src/vi/blogtruyen/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 194bacf6f..000000000 Binary files a/src/vi/blogtruyen/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/blogtruyen/res/mipmap-xhdpi/ic_launcher.png b/src/vi/blogtruyen/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 67f7bc7e5..000000000 Binary files a/src/vi/blogtruyen/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/blogtruyen/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/blogtruyen/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 14bf7024a..000000000 Binary files a/src/vi/blogtruyen/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/blogtruyen/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/blogtruyen/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 208871d0b..000000000 Binary files a/src/vi/blogtruyen/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/blogtruyen/res/web_hi_res_512.png b/src/vi/blogtruyen/res/web_hi_res_512.png deleted file mode 100644 index 7ccf314cf..000000000 Binary files a/src/vi/blogtruyen/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/blogtruyen/src/eu/kanade/tachiyomi/extension/vi/blogtruyen/BlogTruyen.kt b/src/vi/blogtruyen/src/eu/kanade/tachiyomi/extension/vi/blogtruyen/BlogTruyen.kt deleted file mode 100644 index eaab85117..000000000 --- a/src/vi/blogtruyen/src/eu/kanade/tachiyomi/extension/vi/blogtruyen/BlogTruyen.kt +++ /dev/null @@ -1,247 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.blogtruyen - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class BlogTruyen : ParsedHttpSource() { - - override val name = "BlogTruyen" - - override val baseUrl = "https://blogtruyen.vn" - - override val lang = "vi" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl) - - override fun popularMangaSelector() = "div.list span.tiptip.fs-12.ellipsis" - - override fun latestUpdatesSelector() = "section.list-mainpage.listview > div > div > div > div.fl-l" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/ajax/Search/AjaxLoadListManga?key=tatca&orderBy=3&p=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/page-$page", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val imgURL = document.select("img").map { it.attr("abs:src") } - val mangas = document.select(popularMangaSelector()).mapIndexed { index, element -> popularMangaFromElement(element, imgURL[index]) } - - val hasNextPage = popularMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - private fun popularMangaFromElement(element: Element, imgURL: String): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().trim() - manga.thumbnail_url = imgURL - } - return manga - } - - override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not Used") - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = element.select("img").first().attr("alt").toString().trim() - manga.thumbnail_url = element.select("img").first().attr("abs:src") - } - return manga - } - - override fun popularMangaNextPageSelector() = "div.paging:last-child:not(.current_page)" - - override fun latestUpdatesNextPageSelector() = "ul.pagination.paging.list-unstyled > li:nth-last-child(2) > a" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var temp = "$baseUrl/timkiem/nangcao/1/0" - val genres = mutableListOf<Int>() - val genresEx = mutableListOf<Int>() - var aut = "" - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> filter.state.forEach { - genre -> - when (genre.state) { - Filter.TriState.STATE_INCLUDE -> genres.add(genre.id) - Filter.TriState.STATE_EXCLUDE -> genresEx.add(genre.id) - } - } - is Author -> { - if (filter.state.isNotEmpty()) { - aut = filter.state - } - } - } - } - temp = if (genres.isNotEmpty()) temp + "/" + genres.joinToString(",") - else "$temp/-1" - temp = if (genresEx.isNotEmpty()) temp + "/" + genresEx.joinToString(",") - else "$temp/-1" - val url = HttpUrl.parse(temp)!!.newBuilder() - url.addQueryParameter("txt", query) - if (aut.isNotEmpty()) url.addQueryParameter("aut", aut) - url.addQueryParameter("p", page.toString()) - return GET(url.toString().replace("m.", ""), headers) - } - - override fun searchMangaSelector() = "div.list > p:has(a)" - - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a").let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - thumbnail_url = element.nextElementSibling().select("img").attr("abs:src") - } - } - - override fun searchMangaNextPageSelector() = "ul.pagination i.glyphicon.glyphicon-step-forward.red" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.description").first() - - val manga = SManga.create() - manga.author = infoElement.select("p:contains(Tác giả) > a").first()?.text() - manga.genre = infoElement.select("span.category a").joinToString { it.text() } - manga.description = document.select("div.detail > div.content").text() - manga.status = infoElement.select("p:contains(Trạng thái) > span.color-red").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = document.select("div.thumbnail > img").first()?.attr("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Đang tiến hành") -> SManga.ONGOING - status.contains("Đã hoàn thành") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.list-wrap > p" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("span > a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.attr("title").trim() - chapter.date_upload = element.select("span.publishedDate").first()?.text()?.let { - SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.ENGLISH).parse(it)?.time ?: 0 - } ?: 0 - return chapter - } - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val pageUrl = document.select("link[rel=canonical]").attr("href") - document.select("article#content > img").forEachIndexed { i, e -> - pages.add(Page(i, pageUrl, e.attr("src"))) - } - return pages - } - - override fun imageUrlParse(document: Document) = "" - - private class Status : Filter.Select<String>("Status", arrayOf("Sao cũng được", "Đang tiến hành", "Đã hoàn thành", "Tạm ngưng")) - private class Author : Filter.Text("Tác giả") - private class Genre(name: String, val id: Int) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres) - - override fun getFilterList() = FilterList( - Status(), - GenreList(getGenreList()), - Author() - ) - - private fun getGenreList() = listOf( - Genre("16+", 54), - Genre("18+", 45), - Genre("Action", 1), - Genre("Adult", 2), - Genre("Adventure", 3), - Genre("Anime", 4), - Genre("Comedy", 5), - Genre("Comic", 6), - Genre("Doujinshi", 7), - Genre("Drama", 49), - Genre("Ecchi", 48), - Genre("Even BT", 60), - Genre("Fantasy", 50), - Genre("Game", 61), - Genre("Gender Bender", 51), - Genre("Harem", 12), - Genre("Historical", 13), - Genre("Horror", 14), - Genre("Isekai/Dị Giới", 63), - Genre("Josei", 15), - Genre("Live Action", 16), - Genre("Magic", 46), - Genre("Manga", 55), - Genre("Manhua", 17), - Genre("Manhwa", 18), - Genre("Martial Arts", 19), - Genre("Mature", 20), - Genre("Mecha", 21), - Genre("Mystery", 22), - Genre("Nấu ăn", 56), - Genre("NTR", 62), - Genre("One shot", 23), - Genre("Psychological", 24), - Genre("Romance", 25), - Genre("School Life", 26), - Genre("Sci-fi", 27), - Genre("Seinen", 28), - Genre("Shoujo", 29), - Genre("Shoujo Ai", 30), - Genre("Shounen", 31), - Genre("Shounen Ai", 32), - Genre("Slice of Life", 33), - Genre("Smut", 34), - Genre("Soft Yaoi", 35), - Genre("Soft Yuri", 36), - Genre("Sports", 37), - Genre("Supernatural", 38), - Genre("Tạp chí truyện tranh", 39), - Genre("Tragedy", 40), - Genre("Trap", 58), - Genre("Trinh thám", 57), - Genre("Truyện scan", 41), - Genre("Video clip", 53), - Genre("VnComic", 42), - Genre("Webtoon", 52), - Genre("Yuri", 59) - ) -} diff --git a/src/vi/hentaivn/AndroidManifest.xml b/src/vi/hentaivn/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/hentaivn/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/hentaivn/build.gradle b/src/vi/hentaivn/build.gradle deleted file mode 100644 index 0ebce19ae..000000000 --- a/src/vi/hentaivn/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'HentaiVN' - pkgNameSuffix = 'vi.hentaivn' - extClass = '.HentaiVN' - extVersionCode = 8 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/hentaivn/res/mipmap-hdpi/ic_launcher.png b/src/vi/hentaivn/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 4fd0f1f75..000000000 Binary files a/src/vi/hentaivn/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/hentaivn/res/mipmap-mdpi/ic_launcher.png b/src/vi/hentaivn/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 49bd4a61e..000000000 Binary files a/src/vi/hentaivn/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/hentaivn/res/mipmap-xhdpi/ic_launcher.png b/src/vi/hentaivn/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2a2b5a583..000000000 Binary files a/src/vi/hentaivn/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/hentaivn/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/hentaivn/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d55ccbdc3..000000000 Binary files a/src/vi/hentaivn/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/hentaivn/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/hentaivn/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ea8ac6c88..000000000 Binary files a/src/vi/hentaivn/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/hentaivn/res/web_hi_res_512.png b/src/vi/hentaivn/res/web_hi_res_512.png deleted file mode 100644 index eef018066..000000000 Binary files a/src/vi/hentaivn/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/HentaiVN.kt b/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/HentaiVN.kt deleted file mode 100644 index 410c73cbd..000000000 --- a/src/vi/hentaivn/src/eu/kanade/tachiyomi/extension/vi/hentaivn/HentaiVN.kt +++ /dev/null @@ -1,404 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.hentaivn - -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.CookieJar -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale - -@Nsfw -class HentaiVN : ParsedHttpSource() { - - override val baseUrl = "https://hentaivn.net" - override val lang = "vi" - override val name = "HentaiVN" - override val supportsLatest = true - - private val searchUrl = "$baseUrl/forum/search-plus.php" - private val searchClient = network.cloudflareClient - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .cookieJar(CookieJar.NO_COOKIES) - .addInterceptor { chain -> - val originalRequest = chain.request() - when { - originalRequest.url().toString().startsWith(searchUrl) -> { - searchClient.newCall(originalRequest).execute() - } - else -> chain.proceed(originalRequest) - } - } - .build() - - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl) - - private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH) - - override fun chapterFromElement(element: Element): SChapter { - if (element.select("a").isEmpty()) throw Exception(element.select("h2").html()) - val chapter = SChapter.create() - element.select("a").first().let { - chapter.name = it.select("h2").text() - chapter.setUrlWithoutDomain(it.attr("href")) - } - chapter.date_upload = parseDate(element.select("td:nth-child(2)").text().trim()) - return chapter - } - - private fun parseDate(dateString: String): Long { - return try { - dateFormat.parse(dateString)?.time ?: 0L - } catch (e: ParseException) { - return 0L - } - } - - override fun chapterListSelector() = ".page-info > table.listing > tbody > tr" - - override fun imageUrlParse(document: Document) = "" - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select(".box-description a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().trim() - } - manga.thumbnail_url = element.select(".box-cover a img").attr("data-src") - return manga - } - - override fun latestUpdatesNextPageSelector() = "ul.pagination > li:contains(Next)" - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/chap-moi.html?page=$page", headers) - } - - override fun latestUpdatesSelector() = ".main > .block-left > .block-item > ul > li.item" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select(".main > .page-left > .left-info > .page-info") - val manga = SManga.create() - manga.author = infoElement.select("p:contains(Tác giả:) a").text() - manga.description = infoElement.select(":root > p:contains(Nội dung:) + p").text() - manga.genre = infoElement.select("p:contains(Thể loại:) a").joinToString { it.text() } - manga.thumbnail_url = document.select(".main > .page-right > .right-info > .page-ava > img").attr("src") - manga.status = parseStatus(infoElement.select("p:contains(Tình Trạng:) a").firstOrNull()?.text()) - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("Đang tiến hành") -> SManga.ONGOING - status.contains("Đã hoàn thành") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val pageUrl = document.select("link[rel=canonical]").attr("href") - document.select("#image > img").forEachIndexed { i, e -> - pages.add(Page(i, pageUrl, e.attr("abs:src"))) - } - return pages - } - - override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element) - - override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/tieu-diem.html?page=$page", headers) - } - - override fun popularMangaSelector() = latestUpdatesSelector() - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - if (document.select("p").toString().contains("Bạn chỉ có thể sử dụng chức năng này khi đã đăng ký thành viên")) - throw Exception("Đăng nhập qua WebView để kích hoạt tìm kiếm") - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select(".search-des > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().trim() - } - manga.thumbnail_url = element.select("div.search-img img").attr("abs:src") - return manga - } - - override fun searchMangaNextPageSelector() = "ul.pagination > li:contains(Cuối)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$searchUrl?name=$query&page=$page&dou=&char=&group=0&search=")!!.newBuilder() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is TextField -> url.addQueryParameter(filter.key, filter.state) - is GenreList -> - filter.state - .filter { it.state } - .map { it.id } - .forEach { url.addQueryParameter("tag[]", it) } - is GroupList -> { - val group = getGroupList()[filter.state] - url.addQueryParameter("group", group.id) - } - } - } - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = ".search-ul .search-li" - - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Genre(name: String, val id: String) : Filter.CheckBox(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres) - private class TransGroup(name: String, val id: String) : Filter.CheckBox(name) { - override fun toString(): String { - return name - } - } - private class GroupList(groups: Array<TransGroup>) : Filter.Select<TransGroup>("Nhóm dịch", groups) - - override fun getFilterList() = FilterList( - TextField("Doujinshi", "dou"), - TextField("Nhân vật", "char"), - GenreList(getGenreList()), - GroupList(getGroupList()) - ) - - // jQuery.makeArray($('#container > div > div > div.box-box.textbox > form > ul:nth-child(7) > li').map((i, e) => `Genre("${e.textContent}", "${e.children[0].value}")`)).join(',\n') - // https://hentaivn.net/forum/search-plus.php - private fun getGenreList() = listOf( - Genre("3D Hentai", "3"), - Genre("Action", "5"), - Genre("Adult", "116"), - Genre("Adventure", "203"), - Genre("Ahegao", "20"), - Genre("Anal", "21"), - Genre("Angel", "249"), - Genre("Ảnh động", "131"), - Genre("Animal", "127"), - Genre("Animal girl", "22"), - Genre("Artist", "115"), - Genre("BBW", "251"), - Genre("BDSM", "24"), - Genre("Bestiality", "25"), - Genre("Big Ass", "133"), - Genre("Big Boobs", "23"), - Genre("Big Penis", "32"), - Genre("Bloomers", "27"), - Genre("BlowJobs", "28"), - Genre("Body Swap", "29"), - Genre("Bodysuit", "30"), - Genre("Bondage", "254"), - Genre("Breast Sucking", "33"), - Genre("BreastJobs", "248"), - Genre("Brocon", "31"), - Genre("Brother", "242"), - Genre("Business Suit", "241"), - Genre("Catgirls", "39"), - Genre("Che ít", "101"), - Genre("Che nhiều", "129"), - Genre("Cheating", "34"), - Genre("Chikan", "35"), - Genre("Có che", "100"), - Genre("Comedy", "36"), - Genre("Comic", "120"), - Genre("Condom", "210"), - Genre("Cosplay", "38"), - Genre("Cousin", "2"), - Genre("Dark Skin", "40"), - Genre("Demon", "132"), - Genre("DemonGirl", "212"), - Genre("Devil", "104"), - Genre("DevilGirl", "105"), - Genre("Dirty", "253"), - Genre("Dirty Old Man", "41"), - Genre("Double Penetration", "42"), - Genre("Doujinshi", "44"), - Genre("Drama", "4"), - Genre("Drug", "43"), - Genre("Ecchi", "45"), - Genre("Elder Sister", "245"), - Genre("Elf", "125"), - Genre("Exhibitionism", "46"), - Genre("Fantasy", "123"), - Genre("Father", "243"), - Genre("Femdom", "47"), - Genre("Fingering", "48"), - Genre("Footjob", "108"), - Genre("Full Color", "37"), - Genre("Furry", "202"), - Genre("Futanari", "50"), - Genre("Game", "130"), - Genre("GangBang", "51"), - Genre("Garter Belts", "206"), - Genre("Gender Bender", "52"), - Genre("Ghost", "106"), - Genre("Glasses", "56"), - Genre("Group", "53"), - Genre("Guro", "55"), - Genre("Hairy", "247"), - Genre("Handjob", "57"), - Genre("Harem", "58"), - Genre("HentaiVN", "102"), - Genre("Historical", "80"), - Genre("Horror", "122"), - Genre("Housewife", "59"), - Genre("Humiliation", "60"), - Genre("Idol", "61"), - Genre("Imouto", "244"), - Genre("Incest", "62"), - Genre("Insect (Côn Trùng)", "26"), - Genre("Không che", "99"), - Genre("Kimono", "110"), - Genre("Loli", "63"), - Genre("Maids", "64"), - Genre("Manhwa", "114"), - Genre("Mature", "119"), - Genre("Miko", "124"), - Genre("Milf", "126"), - Genre("Mind Break", "121"), - Genre("Mind Control", "113"), - Genre("Monster", "66"), - Genre("Monstergirl", "67"), - Genre("Mother", "103"), - Genre("Nakadashi", "205"), - Genre("Netori", "1"), - Genre("Non-hen", "201"), - Genre("NTR", "68"), - Genre("Nurse", "69"), - Genre("Old Man", "211"), - Genre("Oneshot", "71"), - Genre("Oral", "70"), - Genre("Osananajimi", "209"), - Genre("Paizuri", "72"), - Genre("Pantyhose", "204"), - Genre("Pregnant", "73"), - Genre("Rape", "98"), - Genre("Romance", "117"), - Genre("Ryona", "207"), - Genre("Scat", "134"), - Genre("School Uniform", "74"), - Genre("SchoolGirl", "75"), - Genre("Series", "87"), - Genre("Sex Toys", "88"), - Genre("Shimapan", "246"), - Genre("Short Hentai", "118"), - Genre("Shota", "77"), - Genre("Shoujo", "76"), - Genre("Siscon", "79"), - Genre("Sister", "78"), - Genre("Slave", "82"), - Genre("Sleeping", "213"), - Genre("Small Boobs", "84"), - Genre("Sports", "83"), - Genre("Stockings", "81"), - Genre("Supernatural", "85"), - Genre("Sweating", "250"), - Genre("Swimsuit", "86"), - Genre("Teacher", "91"), - Genre("Tentacles", "89"), - Genre("Time Stop", "109"), - Genre("Tomboy", "90"), - Genre("Tracksuit", "252"), - Genre("Transformation", "256"), - Genre("Trap", "92"), - Genre("Tsundere", "111"), - Genre("Tự sướng", "65"), - Genre("Twins", "93"), - Genre("Vampire", "107"), - Genre("Vanilla", "208"), - Genre("Virgin", "95"), - Genre("X-ray", "94"), - Genre("Yandere", "112"), - Genre("Yaoi", "96"), - Genre("Yuri", "97"), - Genre("Zombie", "128") - ) - - // jQuery.makeArray($('#container > div > div > div.box-box.textbox > form > ul:nth-child(8) > li').map((i, e) => `TransGroup("${e.textContent}", "${e.children[0].value}")`)).join(',\n') - // https://hentaivn.net/forum/search-plus.php - private fun getGroupList() = arrayOf( - TransGroup("Tất cả", "0"), - TransGroup("Đang cập nhật", "1"), - TransGroup("Góc Hentai", "3"), - TransGroup("Hakihome", "4"), - TransGroup("LXERS", "5"), - TransGroup("Hentai-Homies", "6"), - TransGroup("BUZPLANET", "7"), - TransGroup("Trang Sally", "8"), - TransGroup("Loli Rules The World", "9"), - TransGroup("XXX Inc", "10"), - TransGroup("Kobato9x", "11"), - TransGroup("Blazing Soul", "12"), - TransGroup("TAYXUONG", "13"), - TransGroup("[S]ky [G]arden [G]roup", "14"), - TransGroup("Bloomer-kun", "15"), - TransGroup("DHT", "16"), - TransGroup("TruyenHen18", "17"), - TransGroup("iHentaiManga", "18"), - TransGroup("Quân cảng Kancolle X", "19"), - TransGroup("LHMANGA", "20"), - TransGroup("Ship of The Dream", "21"), - TransGroup("Fallen Angels", "22"), - TransGroup("TruyenHentai2H", "23"), - TransGroup("Lạc Thiên", "24"), - TransGroup("69HENTAIXXX", "25"), - TransGroup("DHL", "26"), - TransGroup("Hentai-AdutsManga", "27"), - TransGroup("Hatsu Kaze Desu Translator Team", "28"), - TransGroup("IHentai69", "29"), - TransGroup("Zest", "30"), - TransGroup("Demon Victory Team", "31"), - TransGroup("NTR Victory Team", "32"), - TransGroup("Rori Saikou", "33"), - TransGroup("Bullet Burn Team", "34"), - TransGroup("RE Team", "35"), - TransGroup("Rebelliones", "36"), - TransGroup("Shinto", "37"), - TransGroup("Sexual Paradise", "38"), - TransGroup("FA Dislike Team", "39"), - TransGroup("Triggered Team", "41"), - TransGroup("T.K Translation Team", "42"), - TransGroup("Mabu MG", "43"), - TransGroup("Team Zentsu", "44"), - TransGroup("Sweeter Than Salt", "46"), - TransGroup("Cà rà cà rà Cặt", "47"), - TransGroup("Paradise Of The Happiness", "48"), - TransGroup("Furry Break the 4th Wall", "49"), - TransGroup("The Ignite Team", "50"), - TransGroup("Cuồng Loli", "51"), - TransGroup("Depressed Lolicons Squad - DLS", "52"), - TransGroup("Heaven Of The Fuck", "53") - ) -} diff --git a/src/vi/iutruyentranh/AndroidManifest.xml b/src/vi/iutruyentranh/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/iutruyentranh/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/iutruyentranh/build.gradle b/src/vi/iutruyentranh/build.gradle deleted file mode 100644 index 68f606f9c..000000000 --- a/src/vi/iutruyentranh/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'IuTruyenTranh' - pkgNameSuffix = 'vi.iutruyentranh' - extClass = '.IuTruyenTranh' - extVersionCode = 5 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/iutruyentranh/res/mipmap-hdpi/ic_launcher.png b/src/vi/iutruyentranh/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index adedc4ea5..000000000 Binary files a/src/vi/iutruyentranh/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/iutruyentranh/res/mipmap-mdpi/ic_launcher.png b/src/vi/iutruyentranh/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2d0eb41f7..000000000 Binary files a/src/vi/iutruyentranh/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/iutruyentranh/res/mipmap-xhdpi/ic_launcher.png b/src/vi/iutruyentranh/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index fcbab3e69..000000000 Binary files a/src/vi/iutruyentranh/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/iutruyentranh/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/iutruyentranh/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2ad7833c7..000000000 Binary files a/src/vi/iutruyentranh/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/iutruyentranh/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/iutruyentranh/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6d5f370d3..000000000 Binary files a/src/vi/iutruyentranh/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/iutruyentranh/res/web_hi_res_512.png b/src/vi/iutruyentranh/res/web_hi_res_512.png deleted file mode 100644 index c41307e53..000000000 Binary files a/src/vi/iutruyentranh/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/iutruyentranh/src/eu/kanade/tachiyomi/extension/vi/iutruyentranh/IuTruyenTranh.kt b/src/vi/iutruyentranh/src/eu/kanade/tachiyomi/extension/vi/iutruyentranh/IuTruyenTranh.kt deleted file mode 100644 index 19ac7c5eb..000000000 --- a/src/vi/iutruyentranh/src/eu/kanade/tachiyomi/extension/vi/iutruyentranh/IuTruyenTranh.kt +++ /dev/null @@ -1,197 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.iutruyentranh - -import android.annotation.SuppressLint -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.lang.UnsupportedOperationException -import java.text.SimpleDateFormat -import java.util.Locale - -class IuTruyenTranh : ParsedHttpSource() { - - override val name = "IuTruyenTranh" - - override val baseUrl = "http://iutruyentranh.com" - - override val lang = "vi" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaSelector() = "div.media" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/genre/$page?popular", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest/$page", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h4 a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = element.select("img").attr("abs:data-original") - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "ul.pagination > li:contains(...»)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - @SuppressLint("DefaultLocale") - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search/$page?")!!.newBuilder().addQueryParameter("name", query) - val genres = mutableListOf<String>() - val genresEx = mutableListOf<String>() - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Author -> url.addQueryParameter("autart", filter.state) - is GenreList -> filter.state.forEach { genre -> - when (genre.state) { - Filter.TriState.STATE_INCLUDE -> genres.add(genre.name.toLowerCase()) - Filter.TriState.STATE_EXCLUDE -> genresEx.add(genre.name.toLowerCase()) - } - } - } - } - if (genres.isNotEmpty()) url.addQueryParameter("genres", genres.joinToString(",")) - if (genresEx.isNotEmpty()) url.addQueryParameter("genres-exclude", genresEx.joinToString(",")) - - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("section.manga article").first() - - val manga = SManga.create() - manga.author = infoElement.select("span[itemprop=author]").first()?.text() - manga.genre = infoElement.select("a[itemprop=genre]").joinToString { it.text() } - manga.description = infoElement.select("p.box.box-danger").text() - manga.status = infoElement.select("a[rel=nofollow]").last()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = infoElement.select("img[class^=thumbnail]").first()?.attr("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Đang tiến hành") -> SManga.ONGOING - status.contains("Đã hoàn thành") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "ul.list-unstyled > table > tbody > tr" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href") + "&load=all") - chapter.name = urlElement.select("b").text() - chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { - SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).parse(it)?.time ?: 0L - } ?: 0 - return chapter - } - - override fun pageListParse(document: Document): List<Page> { - return document.select("a.img-link img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:src")) - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - private class Author : Filter.Text("Tác giả") - private class Genre(name: String) : Filter.TriState(name) - private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres) - - override fun getFilterList() = FilterList( - Author(), - GenreList(getGenreList()) - ) - - private fun getGenreList() = listOf( - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Anime"), - Genre("Bishounen"), - Genre("Comedy"), - Genre("Cookin"), - Genre("Demons"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Hentai"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Live action"), - Genre("Magic"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Medical"), - Genre("Military"), - Genre("Mystery"), - Genre("One shot"), - Genre("Oneshot"), - Genre("Other"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci fi"), - Genre("Seinen"), - Genre("Shotacon"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shoujoai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Shounenai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Super power"), - Genre("Superma"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Vampire"), - Genre("Webtoon"), - Genre("Yaoi"), - Genre("Yuri") - ) -} diff --git a/src/vi/medoctruyentranh/AndroidManifest.xml b/src/vi/medoctruyentranh/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/medoctruyentranh/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/medoctruyentranh/build.gradle b/src/vi/medoctruyentranh/build.gradle deleted file mode 100644 index 62c03bbf8..000000000 --- a/src/vi/medoctruyentranh/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'MeDocTruyenTranh' - pkgNameSuffix = 'vi.medoctruyentranh' - extClass = '.MeDocTruyenTranh' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/medoctruyentranh/res/mipmap-hdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 53425cf92..000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-mdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 64fe9eb2c..000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-xhdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index acd8823e3..000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7dcae5ccc..000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/medoctruyentranh/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f478ba21c..000000000 Binary files a/src/vi/medoctruyentranh/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/res/web_hi_res_512.png b/src/vi/medoctruyentranh/res/web_hi_res_512.png deleted file mode 100644 index b02c9ab32..000000000 Binary files a/src/vi/medoctruyentranh/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt b/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt deleted file mode 100644 index f99de28cf..000000000 --- a/src/vi/medoctruyentranh/src/eu/kanade/tachiyomi/extension/vi/medoctruyentranh/MeDocTruyenTranh.kt +++ /dev/null @@ -1,181 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.medoctruyentranh - -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class MeDocTruyenTranh : ParsedHttpSource() { - - override val name = "MeDocTruyenTranh" - - override val baseUrl = "https://www.medoctruyentranh.net" - - override val lang = "vi" - - override val supportsLatest = false - - override val client = network.cloudflareClient - - override fun popularMangaSelector() = "div.classifyList a" - - override fun searchMangaSelector() = ".listCon a" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/tim-truyen/toan-bo" + if (page > 1) "/$page" else "", headers) - } - - private inline fun <reified T, R> JSONArray.mapJSONArray(transform: (Int, T) -> R): List<R> { - val list = mutableListOf<R>() - for (i in 0 until this.length()) { - list.add(transform(i, this[i] as T)) - } - return list - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - // trying to build URLs from this JSONObject could cause issues but we need it to get thumbnails - val titleCoverMap = JSONObject(document.select("script#__NEXT_DATA__").first().data()) - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("classify") - .getJSONArray("comics") - .mapJSONArray { _, jsonObject: JSONObject -> - Pair(jsonObject.getString("title"), jsonObject.getString("coverimg")) - } - .toMap() - - val mangas = document.select(popularMangaSelector()).map { - popularMangaFromElement(it).apply { - thumbnail_url = titleCoverMap[this.title] - } - } - - return MangasPage(mangas, document.select(popularMangaNextPageSelector()) != null) - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/$query", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - title = element.select("div.storytitle").text() - setUrlWithoutDomain(element.attr("href")) - } - } - - override fun popularMangaNextPageSelector() = "div.page_floor a.focus + a + a" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - val jsonData = element.ownerDocument().select("#__NEXT_DATA__").first()!!.data() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("div.storytitle").text() - - val indexOfManga = jsonData.indexOf(manga.title) - val startIndex = jsonData.indexOf("coverimg", indexOfManga) + 11 - val endIndex = jsonData.indexOf("}", startIndex) - 1 - manga.thumbnail_url = jsonData.substring(startIndex, endIndex) - return manga - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - val jsonData = JSONObject(document.select("#__NEXT_DATA__").first()!!.data()) - val mangaDetail = jsonData - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("detail") - .getJSONObject("story_item") - manga.title = mangaDetail.getString("title") - manga.author = mangaDetail.getJSONArray("author_list").getString(0) - val genres = mutableListOf<String>() - for (i in 0 until mangaDetail.getJSONArray("category_list").length()) { - genres.add(mangaDetail.getJSONArray("category_list").getString(i)) - } - manga.genre = genres.joinToString(", ") - manga.description = mangaDetail.getString("summary") - manga.status = parseStatus(mangaDetail.getString("is_updating")) - manga.thumbnail_url = mangaDetail.getString("coverimg") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("1") -> SManga.ONGOING - status.contains("0") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.chapters a" - - override fun chapterListParse(response: Response): List<SChapter> { - return JSONObject(response.asJsoup().select("script#__NEXT_DATA__").first().data()) - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("detail") - .getJSONArray("story_chapters") - .getJSONArray(0) - .mapJSONArray { _, jsonObject: JSONObject -> - SChapter.create().apply { - name = jsonObject.getString("title") - setUrlWithoutDomain("${response.request().url()}/${jsonObject.getString("chapter_index")}") - date_upload = parseChapterDate(jsonObject.getString("time")) - } - } - .reversed() - } - - private fun parseChapterDate(date: String): Long { - // 2019-05-09T07:09:58 - val dateFormat = SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss", - Locale.US - ) - return dateFormat.parse(date)?.time ?: 0L - } - - override fun pageListParse(document: Document): List<Page> { - return JSONObject(document.select("#__NEXT_DATA__").first()?.data() ?: "{}") - .getJSONObject("props") - .getJSONObject("pageProps") - .getJSONObject("initialState") - .getJSONObject("read") - .getJSONObject("detail_item") - .getJSONArray("elements") - .mapJSONArray { i, jsonObject: JSONObject -> - Page(i, "", jsonObject.getString("content")) - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("This method should not be called!") - - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("This method should not be called!") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("This method should not be called!") -} diff --git a/src/vi/ngonphong/AndroidManifest.xml b/src/vi/ngonphong/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/ngonphong/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/ngonphong/build.gradle b/src/vi/ngonphong/build.gradle deleted file mode 100644 index 7a18b6502..000000000 --- a/src/vi/ngonphong/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Ngon Phong' - pkgNameSuffix = 'vi.ngonphong' - extClass = '.NgonPhong' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/ngonphong/res/mipmap-hdpi/ic_launcher.png b/src/vi/ngonphong/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a73203efd..000000000 Binary files a/src/vi/ngonphong/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/ngonphong/res/mipmap-mdpi/ic_launcher.png b/src/vi/ngonphong/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 18fd15859..000000000 Binary files a/src/vi/ngonphong/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/ngonphong/res/mipmap-xhdpi/ic_launcher.png b/src/vi/ngonphong/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5747009a8..000000000 Binary files a/src/vi/ngonphong/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/ngonphong/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/ngonphong/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index ce75efef7..000000000 Binary files a/src/vi/ngonphong/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/ngonphong/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/ngonphong/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 34f4ec9df..000000000 Binary files a/src/vi/ngonphong/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/ngonphong/res/web_hi_res_512.png b/src/vi/ngonphong/res/web_hi_res_512.png deleted file mode 100644 index d4e9fee2e..000000000 Binary files a/src/vi/ngonphong/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/ngonphong/src/eu/kanade/tachiyomi/extension/vi/ngonphong/NgonPhong.kt b/src/vi/ngonphong/src/eu/kanade/tachiyomi/extension/vi/ngonphong/NgonPhong.kt deleted file mode 100644 index c9777068d..000000000 --- a/src/vi/ngonphong/src/eu/kanade/tachiyomi/extension/vi/ngonphong/NgonPhong.kt +++ /dev/null @@ -1,137 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.ngonphong - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class NgonPhong : ParsedHttpSource() { - - override val name = "Ngon Phong" - - override val baseUrl = "https://ngonphongcomics.com" - - override val lang = "vi" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", baseUrl) - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/danh-sach-truyen/?sort=view&trang=$page", headers) - } - - override fun popularMangaSelector() = "div.comic-item" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select(".comic-title-link > a").let { - title = it.attr("title") ?: it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("img.img-thumbnail").attr("abs:src") - } - } - - override fun popularMangaNextPageSelector() = "ul.phantrang li a[title=next]" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/danh-sach-truyen/?sort=latest&trang=$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/?s=$query", headers) - } - - override fun searchMangaSelector() = "table.comic-list-table tbody tr" - - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a").first().let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - } - } - - override fun searchMangaNextPageSelector(): String? = null - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - return SManga.create().apply { - document.select("div.comic-intro div.row").let { info -> - title = info.select("h2").text() - author = info.select("span.green").text() - genre = info.select("strong:contains(Thể loại:) ~ a").joinToString { it.text() } - status = info.select("strong:contains(Tình trạng:) + span").text().toStatus() - thumbnail_url = info.select("img.img-thumbnail").attr("abs:src") - } - description = document.select("div.comic-intro div.row + div p").text() - } - } - - private fun String.toStatus() = when { - this.contains("Đang cập nhật", ignoreCase = true) -> SManga.ONGOING - this.contains("Hoàn thành", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "table.table tbody tr" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a").let { - name = it.text() - setUrlWithoutDomain(it.attr("href")) - } - date_upload = element.select("td:nth-child(2)").text().toChapterDate() - } - } - - private fun String.toChapterDate(): Long { - return try { - SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).parse(this)?.time ?: 0L - } catch (_: Exception) { - 0L - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("script:containsData(htmlContent)").first().data().substringAfter("htmlContent=[") - .substringBefore("];").replace(Regex("""["\\]"""), "").split(",") - .mapIndexed { i, image -> Page(i, "", image) } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/vi/truyenqq/AndroidManifest.xml b/src/vi/truyenqq/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/truyenqq/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/truyenqq/build.gradle b/src/vi/truyenqq/build.gradle deleted file mode 100644 index f691867c2..000000000 --- a/src/vi/truyenqq/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'TruyenQQ' - pkgNameSuffix = 'vi.truyenqq' - extClass = '.TruyenQQ' - extVersionCode = 3 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/truyenqq/res/mipmap-hdpi/ic_launcher.png b/src/vi/truyenqq/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0efd64f57..000000000 Binary files a/src/vi/truyenqq/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyenqq/res/mipmap-mdpi/ic_launcher.png b/src/vi/truyenqq/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index fd4b4969d..000000000 Binary files a/src/vi/truyenqq/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyenqq/res/mipmap-xhdpi/ic_launcher.png b/src/vi/truyenqq/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8654721eb..000000000 Binary files a/src/vi/truyenqq/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyenqq/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/truyenqq/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index edd39f101..000000000 Binary files a/src/vi/truyenqq/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyenqq/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/truyenqq/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 47716dc06..000000000 Binary files a/src/vi/truyenqq/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyenqq/res/web_hi_res_512.png b/src/vi/truyenqq/res/web_hi_res_512.png deleted file mode 100644 index ffce08ec7..000000000 Binary files a/src/vi/truyenqq/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/truyenqq/src/eu/kanade/tachiyomi/extension/vi/truyenqq/TruyenQQ.kt b/src/vi/truyenqq/src/eu/kanade/tachiyomi/extension/vi/truyenqq/TruyenQQ.kt deleted file mode 100644 index 33c1ba88b..000000000 --- a/src/vi/truyenqq/src/eu/kanade/tachiyomi/extension/vi/truyenqq/TruyenQQ.kt +++ /dev/null @@ -1,109 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.truyenqq - -import android.net.Uri -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class TruyenQQ : ParsedHttpSource() { - override val name: String = "TruyenQQ" - override val lang: String = "vi" - override val baseUrl: String = "https://truyenqq.com" - override val supportsLatest: Boolean = true - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build()!! - - override fun headersBuilder(): Headers.Builder { - return super.headersBuilder().add("Referer", baseUrl) - } - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/top-thang/trang-$page.html", headers) - } - override fun popularMangaNextPageSelector(): String? = "a.pagination-link:contains(›)" - override fun popularMangaSelector(): String = "div.story-item" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - setUrlWithoutDomain(element.select("a").first().attr("abs:href")) - thumbnail_url = element.select("img.story-cover").attr("abs:src") - title = element.select(".title-book a").text() - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/truyen-moi-cap-nhat/trang-$page.html", headers) - } - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse("$baseUrl/tim-kiem/trang-$page.html").buildUpon() - .appendQueryParameter("q", query) - return GET(uri.toString(), headers) - - // Todo Filters - } - override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() - override fun searchMangaSelector(): String = popularMangaSelector() - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Details - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - title = document.select("h1").text() - author = document.select(".info-item:eq(1)").text().substringAfter(":").trim() - artist = author - val glist = document.select(".list01 li").map { it.text() } - genre = glist.joinToString(", ") - description = document.select(".story-detail-info").text() - thumbnail_url = document.select("div.left img").attr("src") - status = when (document.select(".info-item:eq(2)").text().substringAfter(":").trim()) { - "Đang Cập Nhật" -> SManga.ONGOING - // "" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - - // Chapters - - override fun chapterListSelector(): String = "div.works-chapter-list div.works-chapter-item" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("abs:href")) - name = element.select("a").text().trim() - date_upload = parseDate(element.select("div.text-right").text()) - chapter_number = name.substringAfter("Chương").trim().toFloat() - } - private fun parseDate(date: String): Long { - return SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L - } - - // Pages - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - document.select("img.lazy").forEachIndexed { index, element -> - add(Page(index, "", element.attr("abs:src"))) - } - } - override fun imageUrlParse(document: Document): String { - throw Exception("Not Used") - } - - // Not Used -} diff --git a/src/vi/truyentranhlh/AndroidManifest.xml b/src/vi/truyentranhlh/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/vi/truyentranhlh/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/vi/truyentranhlh/build.gradle b/src/vi/truyentranhlh/build.gradle deleted file mode 100644 index fa39761f6..000000000 --- a/src/vi/truyentranhlh/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'TruyenTranhLH' - pkgNameSuffix = 'vi.truyentranhlh' - extClass = '.TruyenTranhLH' - extVersionCode = 8 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/vi/truyentranhlh/res/mipmap-hdpi/ic_launcher.png b/src/vi/truyentranhlh/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0aeb0e0c0..000000000 Binary files a/src/vi/truyentranhlh/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranhlh/res/mipmap-mdpi/ic_launcher.png b/src/vi/truyentranhlh/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 9554776c6..000000000 Binary files a/src/vi/truyentranhlh/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranhlh/res/mipmap-xhdpi/ic_launcher.png b/src/vi/truyentranhlh/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d60f0ac46..000000000 Binary files a/src/vi/truyentranhlh/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranhlh/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/truyentranhlh/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f6319bdd2..000000000 Binary files a/src/vi/truyentranhlh/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranhlh/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/truyentranhlh/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9f80e511b..000000000 Binary files a/src/vi/truyentranhlh/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/vi/truyentranhlh/res/web_hi_res_512.png b/src/vi/truyentranhlh/res/web_hi_res_512.png deleted file mode 100644 index a5140304d..000000000 Binary files a/src/vi/truyentranhlh/res/web_hi_res_512.png and /dev/null differ diff --git a/src/vi/truyentranhlh/src/eu/kanade/tachiyomi/extension/vi/truyentranhlh/TruyenTranhLH.kt b/src/vi/truyentranhlh/src/eu/kanade/tachiyomi/extension/vi/truyentranhlh/TruyenTranhLH.kt deleted file mode 100644 index 0da71c8b3..000000000 --- a/src/vi/truyentranhlh/src/eu/kanade/tachiyomi/extension/vi/truyentranhlh/TruyenTranhLH.kt +++ /dev/null @@ -1,124 +0,0 @@ -package eu.kanade.tachiyomi.extension.vi.truyentranhlh - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class TruyenTranhLH : ParsedHttpSource() { - - override val name = "TruyenTranhLH" - - override val baseUrl = "https://truyentranhlh.net" - - override val lang = "vi" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build() - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0") - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/tim-kiem?sort=top&page=$page", headers) - } - - override fun popularMangaSelector() = "div.thumb-item-flow" - - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("div.series-title a").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) - } - thumbnail_url = element.select("div.content").attr("abs:data-bg") - } - } - - override fun popularMangaNextPageSelector() = "div.pagination_wrap a.page_num.current + a:not(.disabled)" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/tim-kiem?sort=update&page=$page", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/tim-kiem?q=$query&sort=update&page=$page", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.top-part") - return SManga.create().apply { - genre = infoElement.select("span.info-name:contains(Thể loại) + span a").joinToString { it.text() } - author = infoElement.select("span.info-name:contains(Tác giả) + span").text() - status = infoElement.select("span.info-name:contains(Tình trạng) + span").text().toStatus() - thumbnail_url = infoElement.select("div.content").attr("style") - .let { Regex("""url\("(.*)"\)""").find(it)?.groups?.get(1)?.value } - description = document.select("div.summary-content").text() - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("Đang tiến hành", ignoreCase = true) -> SManga.ONGOING - this.contains("Đã hoàn thành", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector(): String = "ul.list-chapters a" - - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.select("div.chapter-name").text() - date_upload = element.select("div.chapter-time").firstOrNull()?.text() - ?.let { SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).parse(it)?.time ?: 0L } ?: 0 - } - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - return document.select("div#chapter-content img").mapIndexed { i, img -> - Page(i, "", img.attr("abs:data-src")) - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") -} diff --git a/src/zh/bainianmanga/AndroidManifest.xml b/src/zh/bainianmanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/bainianmanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/bainianmanga/build.gradle b/src/zh/bainianmanga/build.gradle deleted file mode 100755 index 8c2b3a0f7..000000000 --- a/src/zh/bainianmanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'BainianManga' - pkgNameSuffix = 'zh.bainianmanga' - extClass = '.BainianManga' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/bainianmanga/res/mipmap-hdpi/ic_launcher.png b/src/zh/bainianmanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100755 index 43dd0a75a..000000000 Binary files a/src/zh/bainianmanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bainianmanga/res/mipmap-ldpi/ic_launcher.png b/src/zh/bainianmanga/res/mipmap-ldpi/ic_launcher.png deleted file mode 100755 index 5e51f7a05..000000000 Binary files a/src/zh/bainianmanga/res/mipmap-ldpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bainianmanga/res/mipmap-mdpi/ic_launcher.png b/src/zh/bainianmanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100755 index e8bd21e7e..000000000 Binary files a/src/zh/bainianmanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bainianmanga/res/mipmap-xhdpi/ic_launcher.png b/src/zh/bainianmanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100755 index b7abff9e2..000000000 Binary files a/src/zh/bainianmanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bainianmanga/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/bainianmanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100755 index 42d93888c..000000000 Binary files a/src/zh/bainianmanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bainianmanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/bainianmanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100755 index 5f99e9279..000000000 Binary files a/src/zh/bainianmanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bainianmanga/res/web_hi_res_512.png b/src/zh/bainianmanga/res/web_hi_res_512.png deleted file mode 100755 index ebeea2e11..000000000 Binary files a/src/zh/bainianmanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/bainianmanga/src/eu/kanade/tachiyomi/extension/zh/bainianmanga/BainianManga.kt b/src/zh/bainianmanga/src/eu/kanade/tachiyomi/extension/zh/bainianmanga/BainianManga.kt deleted file mode 100755 index b1b64b283..000000000 --- a/src/zh/bainianmanga/src/eu/kanade/tachiyomi/extension/zh/bainianmanga/BainianManga.kt +++ /dev/null @@ -1,112 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.bainianmanga - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class BainianManga : ParsedHttpSource() { - - override val name = "百年漫画" - override val baseUrl = "https://m.bnmanhua.com" - override val lang = "zh" - override val supportsLatest = true - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/page/hot/$page.html", headers) - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/page/new/$page.html", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/index.php?m=vod-search-pg-$page-wd-$query.html")?.newBuilder() - return GET(url.toString(), headers) - } - - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - - override fun popularMangaSelector() = "ul.tbox_m > li.vbox" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = popularMangaSelector() - override fun chapterListSelector() = "ul.list_block > li" - - override fun searchMangaNextPageSelector() = "a.pagelink_a" - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", baseUrl) - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.vbox_t").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title").trim() - } - manga.thumbnail_url = element.select("mip-img").attr("src") - return manga - } - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a") - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text().trim() - return chapter - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.data") - - val manga = SManga.create() - manga.description = document.select("div.tbox_js").text().trim() - manga.author = infoElement.select("p.dir").text().substring(3).trim() - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).asReversed() - } - - private val gson = Gson() - - override fun pageListParse(document: Document): List<Page> { - val html = document.html() - val baseURLRe = Regex("var z_yurl='(.*?)';") - val baseImageUrl = baseURLRe.find(html)?.groups?.get(1)?.value - - val re = Regex("var z_img='(.*?)';") - val imgCode = re.find(html)?.groups?.get(1)?.value - if (imgCode != null) { - val anotherStr = gson.fromJson<List<String>>(imgCode) - return anotherStr.mapIndexed { i, imgStr -> - Page(i, "", "$baseImageUrl$imgStr") - } - } - return listOf() - } - - override fun imageUrlParse(document: Document) = "" - - private class GenreFilter(genres: Array<String>) : Filter.Select<String>("Genre", genres) - - override fun getFilterList() = FilterList( - GenreFilter(getGenreList()) - ) - - private fun getGenreList() = arrayOf( - "All" - ) -} diff --git a/src/zh/bh3/AndroidManifest.xml b/src/zh/bh3/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/bh3/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/bh3/build.gradle b/src/zh/bh3/build.gradle deleted file mode 100644 index c66e7b954..000000000 --- a/src/zh/bh3/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'BH3' - pkgNameSuffix = 'zh.bh3' - extClass = '.BH3' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/bh3/res/mipmap-hdpi/ic_launcher.png b/src/zh/bh3/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2387dcaa6..000000000 Binary files a/src/zh/bh3/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bh3/res/mipmap-mdpi/ic_launcher.png b/src/zh/bh3/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 23cfd5271..000000000 Binary files a/src/zh/bh3/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bh3/res/mipmap-xhdpi/ic_launcher.png b/src/zh/bh3/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 14b747ff9..000000000 Binary files a/src/zh/bh3/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bh3/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/bh3/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2ab7ac082..000000000 Binary files a/src/zh/bh3/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bh3/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/bh3/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1a323a8c4..000000000 Binary files a/src/zh/bh3/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/bh3/res/web_hi_res_512.png b/src/zh/bh3/res/web_hi_res_512.png deleted file mode 100644 index 242cfc71a..000000000 Binary files a/src/zh/bh3/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/bh3/src/eu/kanade/tachiyomi/extension/zh/bh3/BH3.kt b/src/zh/bh3/src/eu/kanade/tachiyomi/extension/zh/bh3/BH3.kt deleted file mode 100644 index a4a2dd688..000000000 --- a/src/zh/bh3/src/eu/kanade/tachiyomi/extension/zh/bh3/BH3.kt +++ /dev/null @@ -1,106 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.bh3 - -import com.github.salomonbrys.kotson.float -import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class BH3 : ParsedHttpSource() { - - override val name = "《崩坏3》IP站" - override val baseUrl = "https://comic.bh3.com" - override val lang = "zh" - override val supportsLatest = false - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build()!! - - override fun popularMangaSelector() = "a[href*=book]" - override fun latestUpdatesSelector() = throw Exception("Not Used") - override fun searchMangaSelector() = throw Exception("Not Used") - override fun chapterListSelector() = throw Exception("Not Used") - - override fun popularMangaNextPageSelector() = "none" - override fun latestUpdatesNextPageSelector() = "none" - override fun searchMangaNextPageSelector() = "none" - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/book", headers) - override fun latestUpdatesRequest(page: Int) = throw Exception("Not Used") - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("No search") - - // override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - // override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "/get_chapter", headers) - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.url = "/book/" + element.select("div.container").attr("id") - manga.title = element.select("div.container-title").text().trim() - manga.thumbnail_url = element.select("img").attr("abs:src") - return manga - } - - override fun chapterFromElement(element: Element) = throw Exception("Not Used") - - override fun chapterListParse(response: Response): List<SChapter> { - val jsondata = response.body()!!.string() - val json = JsonParser().parse(jsondata).asJsonArray - val chapters = mutableListOf<SChapter>() - json.forEach { - chapters.add(createChapter(it)) - } - return chapters - } - - private fun createChapter(json: JsonElement) = SChapter.create().apply { - name = json["title"].string - url = "/book/${json["bookid"].int}/${json["chapterid"].int}" - date_upload = parseDate(json["timestamp"].string) - chapter_number = json["chapterid"].float - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).parse(date)?.time ?: 0L - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.thumbnail_url = document.select("img.cover").attr("abs:src") - manga.description = document.select("div.detail_info1").text().trim() - manga.title = document.select("div.title").text().trim() - return manga - } - - override fun pageListParse(response: Response): List<Page> = mutableListOf<Page>().apply { - val body = response.asJsoup() - body.select("img.lazy.comic_img")?.forEach { - add(Page(size, "", it.attr("data-original"))) - } - } - - override fun pageListParse(document: Document) = throw Exception("Not Used") - override fun imageUrlParse(document: Document) = throw Exception("Not Used") -} diff --git a/src/zh/comico/AndroidManifest.xml b/src/zh/comico/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/comico/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/comico/build.gradle b/src/zh/comico/build.gradle deleted file mode 100644 index 450b8cae8..000000000 --- a/src/zh/comico/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Comico' - pkgNameSuffix = 'zh.comico' - extClass = '.ComicoFactory' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/comico/res/mipmap-hdpi/ic_launcher.png b/src/zh/comico/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 032fb371e..000000000 Binary files a/src/zh/comico/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/comico/res/mipmap-mdpi/ic_launcher.png b/src/zh/comico/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3056e61ae..000000000 Binary files a/src/zh/comico/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/comico/res/mipmap-xhdpi/ic_launcher.png b/src/zh/comico/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 51dfe452b..000000000 Binary files a/src/zh/comico/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/comico/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/comico/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2891cf9a0..000000000 Binary files a/src/zh/comico/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/comico/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/comico/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index eeccd9050..000000000 Binary files a/src/zh/comico/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/comico/res/web_hi_res_512.png b/src/zh/comico/res/web_hi_res_512.png deleted file mode 100644 index 09b0f149e..000000000 Binary files a/src/zh/comico/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/comico/src/eu/kanade/tachiyomi/extension/zh/comico/Comico.kt b/src/zh/comico/src/eu/kanade/tachiyomi/extension/zh/comico/Comico.kt deleted file mode 100644 index 3ed4ae7a9..000000000 --- a/src/zh/comico/src/eu/kanade/tachiyomi/extension/zh/comico/Comico.kt +++ /dev/null @@ -1,169 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.comico - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.Gson -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -abstract class Comico( - override val name: String, - open val urlModifier: String, - override val supportsLatest: Boolean -) : ParsedHttpSource() { - - override val baseUrl = "https://www.comico.com.tw" - - override val lang = "zh" - - override val client: OkHttpClient = network.cloudflareClient - - val gson = Gson() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl$urlModifier/official/finish/?order=ALLSALES", headers) - } - - override fun popularMangaSelector() = "ul.list-article02__list li.list-article02__item a" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.url = element.attr("href").substringAfter(baseUrl + urlModifier) - manga.title = element.attr("title") - manga.thumbnail_url = element.select("img").first().attr("abs:src") - - return manga - } - - override fun popularMangaNextPageSelector() = "No next page" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/index.nhn?searchWord=$query", headers) - } - - override fun searchMangaSelector() = "div#officialList ul.list-article02__list li.list-article02__item a" - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Manga details - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl + urlModifier + manga.url, headers) - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.article-hero05") - - val manga = SManga.create() - manga.title = infoElement.select("h1").text() - manga.author = infoElement.select("p.article-hero05__author").text() - manga.description = infoElement.select("p.article-hero05__sub-description").text() - manga.genre = infoElement.select("div.article-hero05__meta a").text() - manga.status = parseStatus(infoElement.select("div.article-hero05__meta p:not(:has(a))").first().text()) - manga.thumbnail_url = infoElement.select("img").attr("src") - - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("每") -> SManga.ONGOING - status.contains("完結作品") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = throw UnsupportedOperationException("Not used") - - override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - - override fun chapterListRequest(manga: SManga): Request { - val chapterListHeaders = headersBuilder() - .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - .build() - - val body = FormBody.Builder() - .add("titleNo", manga.url.replace("/", "")) - .build() - - return POST("$baseUrl/api/getArticleList.nhn", chapterListHeaders, body) - } - - fun chapterFromJson(jsonElement: JsonElement): SChapter { - val chapter = SChapter.create() - - chapter.name = jsonElement["subtitle"].asString - chapter.setUrlWithoutDomain(jsonElement["articleDetailUrl"].asString) - chapter.date_upload = parseDate(jsonElement["date"].asString) - - return chapter - } - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = mutableListOf<SChapter>() - - gson.fromJson<JsonObject>(response.body()!!.string())["result"]["list"].asJsonArray.forEach { - if (it["freeFlg"].asString == "Y") chapters.add(chapterFromJson(it)) - } - - return chapters.reversed() - } - - private fun parseDate(date: String): Long { - return SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()).parse(date)?.time ?: 0L - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - // First image is in the body - document.select("div.comic-image img") - .map { pages.add(Page(pages.size, "", it.attr("abs:src"))) } - // If there are more images, they're in a script - document.select("script:containsData(imageData)").first().data().let { - if (it.isNotEmpty()) { - it.substringAfter("imageData:[").substringBefore("]").trim().split(",") - .forEach { img -> pages.add(Page(pages.size, "", img.replace("\'", ""))) } - } - } - - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/zh/comico/src/eu/kanade/tachiyomi/extension/zh/comico/ComicoFactory.kt b/src/zh/comico/src/eu/kanade/tachiyomi/extension/zh/comico/ComicoFactory.kt deleted file mode 100644 index 1c308c5e7..000000000 --- a/src/zh/comico/src/eu/kanade/tachiyomi/extension/zh/comico/ComicoFactory.kt +++ /dev/null @@ -1,100 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.comico - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.get -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory -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 okhttp3.FormBody -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document - -class ComicoFactory : SourceFactory { - override fun createSources(): List<Source> = listOf( - ComicoOfficial(), - ComicoChallenge() - ) -} - -class ComicoOfficial : Comico("Comico Official (Limited free chapters)", "", false) -class ComicoChallenge : Comico("Comico Challenge", "/challenge", true) { - override fun popularMangaRequest(page: Int): Request { - val body = FormBody.Builder() - .add("page", page.toString()) - .build() - - return POST("$baseUrl$urlModifier/updateList.nhn?order=new", headers, body) - } - - override fun popularMangaParse(response: Response): MangasPage { - val mangas = mutableListOf<SManga>() - val body = response.body()!!.string() - - gson.fromJson<JsonObject>(body)["result"]["list"].asJsonArray.forEach { - val manga = SManga.create() - - manga.thumbnail_url = it["img_url"].asString - manga.title = it["article_title"].asString - manga.author = it["author"].asString - manga.description = it["description"].asString - manga.url = it["article_url"].asString.substringAfter(urlModifier) - manga.status = if (it["is_end"].asString == "false") SManga.ONGOING else SManga.COMPLETED - - mangas.add(manga) - } - - val lastPage = gson.fromJson<JsonObject>(body)["result"]["totalPageCnt"].asString - val currentPage = gson.fromJson<JsonObject>(body)["result"]["currentPageNo"].asString - - return MangasPage(mangas, currentPage < lastPage) - } - - override fun latestUpdatesRequest(page: Int): Request { - val body = FormBody.Builder() - .add("page", page.toString()) - .build() - - return POST("$baseUrl$urlModifier/updateList.nhn?order=update", headers, body) - } - - override fun latestUpdatesParse(response: Response) = popularMangaParse(response) - - override fun searchMangaSelector() = "div#challengeList ul.list-article02__list li.list-article02__item a" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.article-hero03__inner") - - val manga = SManga.create() - manga.title = infoElement.select("h1").text() - manga.author = infoElement.select("p.article-hero03__author").text() - manga.description = infoElement.select("div.article-hero03__description p").text() - manga.thumbnail_url = infoElement.select("img").attr("src") - - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = mutableListOf<SChapter>() - - gson.fromJson<JsonObject>(response.body()!!.string())["result"]["list"].asJsonArray - .forEach { chapters.add(chapterFromJson(it)) } - - return chapters.reversed() - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - document.select("img.comic-image__image").forEachIndexed { i, img -> - pages.add(Page(i, "", img.attr("src"))) - } - - return pages - } -} diff --git a/src/zh/copymanga/AndroidManifest.xml b/src/zh/copymanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/copymanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/copymanga/build.gradle b/src/zh/copymanga/build.gradle deleted file mode 100644 index 3dd510c3e..000000000 --- a/src/zh/copymanga/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'CopyManga' - pkgNameSuffix = 'zh.copymanga' - extClass = '.CopyManga' - extVersionCode = 8 - libVersion = '1.2' -} -apply from: "$rootDir/common.gradle" - -dependencies { - implementation 'com.luhuiguo:chinese-utils:1.0' -} diff --git a/src/zh/copymanga/res/mipmap-hdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 444d5bee6..000000000 Binary files a/src/zh/copymanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/copymanga/res/mipmap-ldpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-ldpi/ic_launcher.png deleted file mode 100644 index 62e956ebd..000000000 Binary files a/src/zh/copymanga/res/mipmap-ldpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/copymanga/res/mipmap-mdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d693237ed..000000000 Binary files a/src/zh/copymanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/copymanga/res/mipmap-xhdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 168550c75..000000000 Binary files a/src/zh/copymanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/copymanga/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8fea89dfb..000000000 Binary files a/src/zh/copymanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/copymanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/copymanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2a381b542..000000000 Binary files a/src/zh/copymanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/copymanga/res/web_hi_res_512.png b/src/zh/copymanga/res/web_hi_res_512.png deleted file mode 100644 index d4dcfde7b..000000000 Binary files a/src/zh/copymanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyManga.kt b/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyManga.kt deleted file mode 100644 index d8ca2b0e4..000000000 --- a/src/zh/copymanga/src/eu/kanade/tachiyomi/extension/zh/copymanga/CopyManga.kt +++ /dev/null @@ -1,442 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.copymanga - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.PreferenceScreen -import com.luhuiguo.chinese.ChineseUtils -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import org.jsoup.nodes.Element -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.security.SecureRandom -import java.security.cert.X509Certificate -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec -import javax.net.ssl.SSLContext -import javax.net.ssl.X509TrustManager -import kotlin.collections.ArrayList - -class CopyManga : ConfigurableSource, HttpSource() { - - override val name = "拷贝漫画" - override val baseUrl = "https://www.copymanga.com" - override val lang = "zh" - override val supportsLatest = true - private val popularLatestPageSize = 50 // default - private val searchPageSize = 12 // default - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - private val trustManager = object : X509TrustManager { - override fun getAcceptedIssuers(): Array<X509Certificate> { - return emptyArray() - } - - override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { - } - - override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { - } - } - private val sslContext = SSLContext.getInstance("SSL").apply { - init(null, arrayOf(trustManager), SecureRandom()) - } - - override val client: OkHttpClient = super.client.newBuilder() - .sslSocketFactory(sslContext.socketFactory, trustManager) - .build() - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics?ordering=-popular&offset=${(page - 1) * popularLatestPageSize}&limit=$popularLatestPageSize", headers) - override fun popularMangaParse(response: Response): MangasPage = parseSearchMangaWithFilterOrPopularOrLatestResponse(response) - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/comics?ordering=-datetime_updated&offset=${(page - 1) * popularLatestPageSize}&limit=$popularLatestPageSize", headers) - override fun latestUpdatesParse(response: Response): MangasPage = parseSearchMangaWithFilterOrPopularOrLatestResponse(response) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - // when perform html search, sort by popular - var apiUrlString = "$baseUrl/api/kb/web/search/comics?limit=$searchPageSize&offset=${(page - 1) * searchPageSize}&platform=2&q=$query&q_type=" - var htmlUrlString = "$baseUrl/comics?offset=${(page - 1) * popularLatestPageSize}&limit=$popularLatestPageSize" - var requestUrlString: String - - val params = filters.map { - if (it is MangaFilter) { - it.toUriPart() - } else "" - }.filter { it != "" }.joinToString("&") - // perform html search only when do have filter and not search anything - if (params != "" && query == "") { - requestUrlString = htmlUrlString + "&$params" - } else { - requestUrlString = apiUrlString - } - val url = HttpUrl.parse(requestUrlString)?.newBuilder() - return GET(url.toString(), headers) - } - override fun searchMangaParse(response: Response): MangasPage { - if (response.headers("content-type").filter { it.contains("json", true) }.any()) { - // result from api request - return parseSearchMangaResponseAsJson(response) - } else { - // result from html request - return parseSearchMangaWithFilterOrPopularOrLatestResponse(response) - } - } - - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - var _title: String = document.select("div.comicParticulars-title-right > ul > li:eq(0) ").first().text() - if (preferences.getBoolean(SHOW_Simplified_Chinese_TITLE_PREF, false)) { - _title = ChineseUtils.toSimplified(_title) - } - val manga = SManga.create().apply { - title = _title - thumbnail_url = document.select("div.comicParticulars-title-left img").first().attr("data-src") - description = document.select("div.comicParticulars-synopsis p.intro").first().text().trim() - } - - val items = document.select("div.comicParticulars-title-right ul li") - if (items.size >= 7) { - manga.author = items[2].select("a").map { i -> i.text().trim() }.joinToString(", ") - manga.status = when (items[5].select("span.comicParticulars-right-txt").first().text().trim()) { - "已完結" -> SManga.COMPLETED - "連載中" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - manga.genre = items[6].select("a").map { i -> i.text().trim().trim('#') }.joinToString(", ") - } - return manga - } - - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val disposableData = document.select("div.disposableData").first().attr("disposable") - val disposablePass = document.select("div.disposablePass").first().attr("disposable") - - val chapterJsonString = decryptChapterData(disposableData, disposablePass) - // default > groups > 全部 [] - val chapterJson = JSONObject(chapterJsonString) - var chapterArray = chapterJson.optJSONObject("default")?.optJSONObject("groups")?.optJSONArray("全部") - if (chapterArray == null) { - return listOf() - } - - val retDefault = ArrayList<SChapter>(chapterArray.length()) - for (i in 0 until chapterArray.length()) { - val chapter = chapterArray.getJSONObject(i) - retDefault.add( - SChapter.create().apply { - name = chapter.getString("name") - date_upload = stringToUnixTimestamp(chapter.getString("datetime_created")) * 1000 - url = "/comic/${chapter.getString("comic_path_word")}/chapter/${chapter.getString("uuid")}" - } - ) - } - - // {others} > groups > 全部 [] - val retOthers = ArrayList<SChapter>() - for (categroy in chapterJson.keys()) { - if (categroy != "default") { - chapterArray = chapterJson.optJSONObject(categroy)?.optJSONObject("groups")?.optJSONArray("全部") - if (chapterArray == null) { - continue - } - for (i in 0 until chapterArray.length()) { - val chapter = chapterArray.getJSONObject(i) - retOthers.add( - SChapter.create().apply { - name = chapter.getString("name") - date_upload = stringToUnixTimestamp(chapter.getString("datetime_created")) * 1000 - url = "/comic/${chapter.getString("comic_path_word")}/chapter/${chapter.getString("uuid")}" - } - ) - } - } - } - - // place others to top, as other group updates not so often - retDefault.addAll(0, retOthers) - return retDefault.asReversed() - } - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - val disposableData = document.select("div.disposableData").first().attr("disposable") - val disposablePass = document.select("div.disposablePass").first().attr("disposable") - - val pageJsonString = decryptChapterData(disposableData, disposablePass) - val pageArray = JSONArray(pageJsonString) - - val ret = ArrayList<Page>(pageArray.length()) - for (i in 0 until pageArray.length()) { - ret.add(Page(i, "", pageArray.getJSONObject(i).getString("url"))) - } - - return ret - } - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", baseUrl) - .add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") - - // Unused, we can get image urls directly from the chapter page - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("This method should not be called!") - - // Copymanga has different logic in polular and search page, mix two logic in search progress for now - override fun getFilterList() = FilterList( - MangaFilter( - "题材", - "theme", - arrayOf( - Pair("全部", ""), - Pair("愛情", "aiqing"), - Pair("歡樂向", "huanlexiang"), - Pair("冒险", "maoxian"), - Pair("百合", "baihe"), - Pair("東方", "dongfang"), - Pair("奇幻", "qihuan"), - Pair("校园", "xiaoyuan"), - Pair("科幻", "kehuan"), - Pair("生活", "shenghuo"), - Pair("轻小说", "qingxiaoshuo"), - Pair("格鬥", "gedou"), - Pair("神鬼", "shengui"), - Pair("悬疑", "xuanyi"), - Pair("耽美", "danmei"), - Pair("其他", "qita"), - Pair("舰娘", "jianniang"), - Pair("职场", "zhichang"), - Pair("治愈", "zhiyu"), - Pair("萌系", "mengxi"), - Pair("四格", "sige"), - Pair("伪娘", "weiniang"), - Pair("竞技", "jingji"), - Pair("搞笑", "gaoxiao"), - Pair("長條", "changtiao"), - Pair("性转换", "xingzhuanhuan"), - Pair("侦探", "zhentan"), - Pair("节操", "jiecao"), - Pair("热血", "rexue"), - Pair("美食", "meishi"), - Pair("後宮", "hougong"), - Pair("励志", "lizhi"), - Pair("音乐舞蹈", "yinyuewudao"), - Pair("彩色", "COLOR"), - Pair("AA", "aa"), - Pair("异世界", "yishijie"), - Pair("历史", "lishi"), - Pair("战争", "zhanzheng"), - Pair("机战", "jizhan"), - Pair("C97", "comiket97"), - Pair("C96", "comiket96"), - Pair("宅系", "zhaixi"), - Pair("C98", "C98"), - Pair("C95", "comiket95"), - Pair("恐怖", "%E6%81%90%E6%80 %96"), - Pair("FATE", "fate"), - Pair("無修正", "Uncensored"), - Pair("穿越", "chuanyue"), - Pair("武侠", "wuxia"), - Pair("生存", "shengcun"), - Pair("惊悚", "jingsong"), - Pair("都市", "dushi"), - Pair("LoveLive", "loveLive"), - Pair("转生", "zhuansheng"), - Pair("重生", "chongsheng"), - Pair("仙侠", "xianxia") - ) - ), - MangaFilter( - "排序", - "ordering", - arrayOf( - Pair("最热门", "-popular"), - Pair("最冷门", "popular"), - Pair("最新", "-datetime_updated"), - Pair("最早", "datetime_updated"), - ) - ), - ) - - private class MangaFilter( - displayName: String, - searchName: String, - val vals: Array<Pair<String, String>>, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), defaultValue) { - val searchName = searchName - fun toUriPart(): String { - val selectVal = vals[state].second - return if (selectVal != "") "$searchName=$selectVal" else "" - } - } - - private fun parseSearchMangaWithFilterOrPopularOrLatestResponse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select("div.exemptComicList div.exemptComicItem").map { element -> - mangaFromPage(element) - } - - // There is always a next pager, so use itemCount to check. XD - val hasNextPage = mangas.size == popularLatestPageSize - - return MangasPage(mangas, hasNextPage) - } - - private fun parseSearchMangaResponseAsJson(response: Response): MangasPage { - val body = response.body()!!.string() - // results > comic > list [] - val res = JSONObject(body) - val comicArray = res.optJSONObject("results")?.optJSONArray("list") - if (comicArray == null) { - return MangasPage(listOf(), false) - } - - val ret = ArrayList<SManga>(comicArray.length()) - for (i in 0 until comicArray.length()) { - val obj = comicArray.getJSONObject(i) - val authorArray = obj.getJSONArray("author") - var _title: String = obj.getString("name") - if (preferences.getBoolean(SHOW_Simplified_Chinese_TITLE_PREF, false)) { - _title = ChineseUtils.toSimplified(_title) - } - ret.add( - SManga.create().apply { - title = _title - thumbnail_url = obj.getString("cover") - author = Array<String?>(authorArray.length()) { i -> authorArray.getJSONObject(i).getString("name") }.joinToString(", ") - status = SManga.UNKNOWN - url = "/comic/${obj.getString("path_word")}" - } - ) - } - - return MangasPage(ret, comicArray.length() == searchPageSize) - } - - private fun mangaFromPage(element: Element): SManga { - val manga = SManga.create() - element.select("div.exemptComicItem-img > a > img").first().let { - manga.thumbnail_url = it.attr("data-src") - } - element.select("div.exemptComicItem-txt > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - var _title: String = it.select("p").first().text().trim() - if (preferences.getBoolean(SHOW_Simplified_Chinese_TITLE_PREF, false)) { - _title = ChineseUtils.toSimplified(_title) - } - manga.title = _title - } - return manga - } - - private fun byteArrayToHexString(byteArray: ByteArray): String { - var sb = "" - for (b in byteArray) { - sb += String.format("%02x", b) - } - return sb - } - - private fun hexStringToByteArray(string: String): ByteArray { - val bytes = ByteArray(string.length / 2) - for (i in 0 until string.length / 2) { - bytes[i] = string.substring(i * 2, i * 2 + 2).toInt(16).toByte() - } - return bytes - } - - private fun stringToUnixTimestamp(string: String, pattern: String = "yyyy-MM-dd", locale: Locale = Locale.CHINA): Long { - return try { - val time = SimpleDateFormat(pattern, locale).parse(string)?.time - if (time != null) time / 1000 else Date().time / 1000 - } catch (ex: Exception) { - Date().time / 1000 - } - } - - // thanks to unpacker toolsite, http://matthewfl.com/unPacker.html - private fun decryptChapterData(disposableData: String, disposablePass: String = "hotmanga.aes.key"): String { - val prePart = disposableData.substring(0, 16) - val postPart = disposableData.substring(16, disposableData.length) - val disposablePassByteArray = disposablePass.toByteArray(Charsets.UTF_8) - val prepartByteArray = prePart.toByteArray(Charsets.UTF_8) - val dataByteArray = hexStringToByteArray(postPart) - - val secretKey = SecretKeySpec(disposablePassByteArray, "AES") - val iv = IvParameterSpec(prepartByteArray) - val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") - cipher.init(Cipher.DECRYPT_MODE, secretKey, iv) - val result = String(cipher.doFinal(dataByteArray), Charsets.UTF_8) - - return result - } - - // Change Title to Simplified Chinese For Library Gobal Search Optionally - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val zhPreference = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SHOW_Simplified_Chinese_TITLE_PREF - title = "将标题转换为简体中文" - summary = "需要重启软件以生效。已添加漫画需要迁移改变标题。" - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putBoolean(SHOW_Simplified_Chinese_TITLE_PREF, newValue as Boolean).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - screen.addPreference(zhPreference) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val zhPreference = CheckBoxPreference(screen.context).apply { - key = SHOW_Simplified_Chinese_TITLE_PREF - title = "将标题转换为简体中文" - summary = "需要重启软件以生效。已添加漫画需要迁移改变标题。" - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putBoolean(SHOW_Simplified_Chinese_TITLE_PREF, newValue as Boolean).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(zhPreference) - } - - companion object { - private const val SHOW_Simplified_Chinese_TITLE_PREF = "showSCTitle" - } -} diff --git a/src/zh/dmzj/AndroidManifest.xml b/src/zh/dmzj/AndroidManifest.xml deleted file mode 100644 index 654b80485..000000000 --- a/src/zh/dmzj/AndroidManifest.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".zh.dmzj.DmzjUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="m.dmzj.com" - android:pathPattern="/info/..*" - android:scheme="https" /> - <data - android:host="www.dmzj.com" - android:pathPattern="/info/..*" - android:scheme="https" /> - <data - android:host="manhua.dmzj.com" - android:pathPattern="/..*" - android:scheme="https" /> - <data - android:host="m.dmzj1.com" - android:pathPattern="/info/..*" - android:scheme="https" /> - <data - android:host="www.dmzj1.com" - android:pathPattern="/info/..*" - android:scheme="https" /> - <data - android:host="manhua.dmzj1.com" - android:pathPattern="/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/zh/dmzj/build.gradle b/src/zh/dmzj/build.gradle deleted file mode 100644 index 0c497cd06..000000000 --- a/src/zh/dmzj/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Dmzj' - pkgNameSuffix = 'zh.dmzj' - extClass = '.Dmzj' - extVersionCode = 16 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/dmzj/res/mipmap-hdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index aaddbe171..000000000 Binary files a/src/zh/dmzj/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-mdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d4ed1385d..000000000 Binary files a/src/zh/dmzj/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-xhdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 233aa08c7..000000000 Binary files a/src/zh/dmzj/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c12efcf74..000000000 Binary files a/src/zh/dmzj/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/dmzj/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7f7f4b514..000000000 Binary files a/src/zh/dmzj/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/dmzj/res/web_hi_res_512.png b/src/zh/dmzj/res/web_hi_res_512.png deleted file mode 100644 index 85314a5c8..000000000 Binary files a/src/zh/dmzj/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt deleted file mode 100644 index 4302c7641..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/Dmzj.kt +++ /dev/null @@ -1,584 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.net.URLEncoder -import java.util.ArrayList - -/** - * Dmzj source - */ - -class Dmzj : ConfigurableSource, HttpSource() { - override val lang = "zh" - override val supportsLatest = true - override val name = "动漫之家" - override val baseUrl = "https://m.dmzj1.com" - private val v3apiUrl = "https://v3api.dmzj1.com" - private val apiUrl = "https://api.dmzj.com" - private val oldPageListApiUrl = "https://m.dmzj.com/chapinfo" - private val imageCDNUrl = "https://images.dmzj1.com" - - private fun cleanUrl(url: String) = if (url.startsWith("//")) - "https:$url" - else url - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private val v3apiRateLimitInterceptor = SpecificHostRateLimitInterceptor( - HttpUrl.parse(v3apiUrl)!!, - preferences.getString(API_RATELIMIT_PREF, "5")!!.toInt() - ) - private val apiRateLimitInterceptor = SpecificHostRateLimitInterceptor( - HttpUrl.parse(apiUrl)!!, - preferences.getString(API_RATELIMIT_PREF, "5")!!.toInt() - ) - private val imageCDNRateLimitInterceptor = SpecificHostRateLimitInterceptor( - HttpUrl.parse(imageCDNUrl)!!, - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, "5")!!.toInt() - ) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(apiRateLimitInterceptor) - .addNetworkInterceptor(v3apiRateLimitInterceptor) - .addNetworkInterceptor(imageCDNRateLimitInterceptor) - .build() - - override fun headersBuilder() = Headers.Builder().apply { - set("Referer", "https://www.dmzj1.com/") - set( - "User-Agent", - "Mozilla/5.0 (Linux; Android 10) " + - "AppleWebKit/537.36 (KHTML, like Gecko) " + - "Chrome/88.0.4324.93 " + - "Mobile Safari/537.36 " + - "Tachiyomi/1.0" - ) - } - - // for simple searches (query only, no filters) - private fun simpleSearchJsonParse(json: String): MangasPage { - val arr = JSONArray(json) - val ret = ArrayList<SManga>(arr.length()) - for (i in 0 until arr.length()) { - val obj = arr.getJSONObject(i) - val cid = obj.getString("id") - ret.add( - SManga.create().apply { - title = obj.getString("comic_name") - thumbnail_url = cleanUrl(obj.getString("comic_cover")) - author = obj.optString("comic_author") - url = "/comic/comic_$cid.json?version=2.7.019" - } - ) - } - return MangasPage(ret, false) - } - - // for popular, latest, and filtered search - private fun mangaFromJSON(json: String): MangasPage { - val arr = JSONArray(json) - val ret = ArrayList<SManga>(arr.length()) - for (i in 0 until arr.length()) { - val obj = arr.getJSONObject(i) - val cid = obj.getString("id") - ret.add( - SManga.create().apply { - title = obj.getString("title") - thumbnail_url = obj.getString("cover") - author = obj.optString("authors") - status = when (obj.getString("status")) { - "已完结" -> SManga.COMPLETED - "连载中" -> SManga.ONGOING - else -> SManga.UNKNOWN - } - url = "/comic/comic_$cid.json?version=2.7.019" - } - ) - } - return MangasPage(ret, arr.length() != 0) - } - - override fun popularMangaRequest(page: Int) = GET("$v3apiUrl/classify/0/0/${page - 1}.json") - - override fun popularMangaParse(response: Response) = searchMangaParse(response) - - override fun latestUpdatesRequest(page: Int) = GET("$v3apiUrl/classify/0/1/${page - 1}.json") - - override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) - - private fun searchMangaById(id: String): MangasPage { - val comicNumberID = if (checkComicIdIsNumericalRegex.matches(id)) { - id - } else { - val document = client.newCall(GET("$baseUrl/info/$id.html", headers)).execute().asJsoup() - extractComicIdFromWebpageRegex.find(document.select("#Subscribe").attr("onclick"))!!.groups[1]!!.value // onclick="addSubscribe('{comicNumberID}')" - } - - val sManga = try { - val r = client.newCall(GET("$v3apiUrl/comic/comic_$comicNumberID.json", headers)).execute() - mangaDetailsParse(r) - } catch (_: Exception) { - val r = client.newCall(GET("$apiUrl/dynamic/comicinfo/$comicNumberID.json", headers)).execute() - mangaDetailsParse(r) - } - sManga.url = "$baseUrl/info/$comicNumberID.html" - - return MangasPage(listOf(sManga), false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - // ID may be numbers or Chinese pinyin - val id = query.removePrefix(PREFIX_ID_SEARCH).removeSuffix(".html") - Observable.just(searchMangaById(id)) - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query != "") { - val uri = Uri.parse("http://s.acg.dmzj1.com/comicsum/search.php").buildUpon() - uri.appendQueryParameter("s", query) - return GET(uri.toString()) - } else { - var params = filters.map { - if (it !is SortFilter && it is UriPartFilter) { - it.toUriPart() - } else "" - }.filter { it != "" }.joinToString("-") - if (params == "") { - params = "0" - } - - val order = filters.filterIsInstance<SortFilter>().joinToString("") { (it as UriPartFilter).toUriPart() } - - return GET("$v3apiUrl/classify/$params/$order/${page - 1}.json") - } - } - - override fun searchMangaParse(response: Response): MangasPage { - val body = response.body()!!.string() - - return if (body.contains("g_search_data")) { - simpleSearchJsonParse(body.substringAfter("=").trim().removeSuffix(";")) - } else { - mangaFromJSON(body) - } - } - - // Bypass mangaDetailsRequest, fetch api url directly - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - val cid = extractComicIdFromMangaUrlRegex.find(manga.url)!!.groups[1]!!.value - return try { - // Not using client.newCall().asObservableSuccess() to ensure we can catch exception here. - val response = client.newCall(GET("$v3apiUrl/comic/comic_$cid.json", headers)).execute() - val sManga = mangaDetailsParse(response).apply { initialized = true } - Observable.just(sManga) - } catch (e: Exception) { - val response = client.newCall(GET("$apiUrl/dynamic/comicinfo/$cid.json", headers)).execute() - val sManga = mangaDetailsParse(response).apply { initialized = true } - Observable.just(sManga) - } catch (e: Exception) { - Observable.error(e) - } - } - - // Workaround to allow "Open in browser" use human readable webpage url. - override fun mangaDetailsRequest(manga: SManga): Request { - val cid = extractComicIdFromMangaUrlRegex.find(manga.url)!!.groups[1]!!.value - return GET("$baseUrl/info/$cid.html") - } - - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val obj = JSONObject(response.body()!!.string()) - - if (response.request().url().toString().startsWith(v3apiUrl)) { - title = obj.getString("title") - thumbnail_url = obj.getString("cover") - var arr = obj.getJSONArray("authors") - val tmparr = ArrayList<String>(arr.length()) - for (i in 0 until arr.length()) { - tmparr.add(arr.getJSONObject(i).getString("tag_name")) - } - author = tmparr.joinToString(", ") - - arr = obj.getJSONArray("types") - tmparr.clear() - for (i in 0 until arr.length()) { - tmparr.add(arr.getJSONObject(i).getString("tag_name")) - } - genre = tmparr.joinToString(", ") - status = when (obj.getJSONArray("status").getJSONObject(0).getInt("tag_id")) { - 2310 -> SManga.COMPLETED - 2309 -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - description = obj.getString("description") - } else { - val data = obj.getJSONObject("data").getJSONObject("info") - title = data.getString("title") - thumbnail_url = data.getString("cover") - author = data.getString("authors") - genre = data.getString("types").replace("/", ", ") - status = when (data.getString("status")) { - "连载中" -> SManga.ONGOING - "已完结" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - description = data.getString("description") - } - } - - override fun chapterListRequest(manga: SManga): Request = throw UnsupportedOperationException("Not used.") - - override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { - val cid = extractComicIdFromMangaUrlRegex.find(manga.url)!!.groups[1]!!.value - return if (manga.status != SManga.LICENSED) { - try { - val response = client.newCall(GET("$v3apiUrl/comic/comic_$cid.json", headers)).execute() - val sChapter = chapterListParse(response) - Observable.just(sChapter) - } catch (e: Exception) { - val response = client.newCall(GET("$apiUrl/dynamic/comicinfo/$cid.json", headers)).execute() - val sChapter = chapterListParse(response) - Observable.just(sChapter) - } catch (e: Exception) { - Observable.error(e) - } - } else { - Observable.error(Exception("Licensed - No chapters to show")) - } - } - - override fun chapterListParse(response: Response): List<SChapter> { - val obj = JSONObject(response.body()!!.string()) - val ret = ArrayList<SChapter>() - - if (response.request().url().toString().startsWith(v3apiUrl)) { - val cid = obj.getString("id") - val chaptersList = obj.getJSONArray("chapters") - for (i in 0 until chaptersList.length()) { - val chapterObj = chaptersList.getJSONObject(i) - val chapterData = chapterObj.getJSONArray("data") - val prefix = chapterObj.getString("title") - for (j in 0 until chapterData.length()) { - val chapter = chapterData.getJSONObject(j) - ret.add( - SChapter.create().apply { - name = "$prefix: ${chapter.getString("chapter_title")}" - date_upload = chapter.getString("updatetime").toLong() * 1000 // milliseconds - url = "https://api.m.dmzj1.com/comic/chapter/$cid/${chapter.getString("chapter_id")}.html" - } - ) - } - } - } else { - // Fallback to old api - val chaptersList = obj.getJSONObject("data").getJSONArray("list") - for (i in 0 until chaptersList.length()) { - val chapter = chaptersList.getJSONObject(i) - ret.add( - SChapter.create().apply { - name = chapter.getString("chapter_name") - date_upload = chapter.getString("updatetime").toLong() * 1000 - url = "$oldPageListApiUrl/${chapter.getString("comic_id")}/${chapter.getString("id")}.html" - } - ) - } - } - return ret - } - - override fun pageListRequest(chapter: SChapter) = GET(chapter.url, headers) // Bypass base url - - override fun pageListParse(response: Response): List<Page> { - val arr = if (response.request().url().toString().startsWith(oldPageListApiUrl)) { - JSONObject(response.body()!!.string()).getJSONArray("page_url") - } else { - // some chapters are hidden and won't return a JSONObject from api.m.dmzj, have to get them through v3api (but images won't be as HQ) - try { - val obj = JSONObject(response.body()!!.string()) - obj.getJSONObject("chapter").getJSONArray("page_url") // api.m.dmzj1.com already return HD image url - } catch (_: Exception) { - // example url: http://v3api.dmzj.com/chapter/44253/101852.json - val url = response.request().url().toString() - .replace("api.m", "v3api") - .replace("comic/", "") - .replace(".html", ".json") - val obj = client.newCall(GET(url, headers)).execute().let { JSONObject(it.body()!!.string()) } - obj.getJSONArray("page_url_hd") // page_url in v3api.dmzj1.com will return compressed image, page_url_hd will return HD image url as api.m.dmzj1.com does. - } catch (_: Exception) { - // Fallback to old api - // example url: https://m.dmzj.com/chapinfo/44253/101852.html - val url = response.request().url().toString() - .replaceFirst("api.", "") - .replaceFirst(".dmzj1.", ".dmzj.") - .replaceFirst("comic/chapter", "chapinfo") - val obj = client.newCall(GET(url, headers)).execute().let { JSONObject(it.body()!!.string()) } - obj.getJSONArray("page_url") - } - } - val ret = ArrayList<Page>(arr.length()) - for (i in 0 until arr.length()) { - ret.add( - Page(i, "", arr.getString(i).replace("http:", "https:").replace("dmzj.com", "dmzj1.com")) - ) - } - return ret - } - - private fun String.encoded(): String { - return this.chunked(1) - .joinToString("") { if (it in setOf("%", " ", "+", "#")) URLEncoder.encode(it, "UTF-8") else it } - .let { if (it.endsWith(".jp")) "${it}g" else it } - } - - override fun imageRequest(page: Page): Request { - return GET(page.imageUrl!!.encoded(), headers) - } - - // Unused, we can get image urls directly from the chapter page - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("This method should not be called!") - - override fun getFilterList() = FilterList( - SortFilter(), - GenreGroup(), - StatusFilter(), - TypeFilter(), - ReaderFilter() - ) - - private class GenreGroup : UriPartFilter( - "分类", - arrayOf( - Pair("全部", ""), - Pair("冒险", "4"), - Pair("百合", "3243"), - Pair("生活", "3242"), - Pair("四格", "17"), - Pair("伪娘", "3244"), - Pair("悬疑", "3245"), - Pair("后宫", "3249"), - Pair("热血", "3248"), - Pair("耽美", "3246"), - Pair("其他", "16"), - Pair("恐怖", "14"), - Pair("科幻", "7"), - Pair("格斗", "6"), - Pair("欢乐向", "5"), - Pair("爱情", "8"), - Pair("侦探", "9"), - Pair("校园", "13"), - Pair("神鬼", "12"), - Pair("魔法", "11"), - Pair("竞技", "10"), - Pair("历史", "3250"), - Pair("战争", "3251"), - Pair("魔幻", "5806"), - Pair("扶她", "5345"), - Pair("东方", "5077"), - Pair("奇幻", "5848"), - Pair("轻小说", "6316"), - Pair("仙侠", "7900"), - Pair("搞笑", "7568"), - Pair("颜艺", "6437"), - Pair("性转换", "4518"), - Pair("高清单行", "4459"), - Pair("治愈", "3254"), - Pair("宅系", "3253"), - Pair("萌系", "3252"), - Pair("励志", "3255"), - Pair("节操", "6219"), - Pair("职场", "3328"), - Pair("西方魔幻", "3365"), - Pair("音乐舞蹈", "3326"), - Pair("机战", "3325") - ) - ) - - private class StatusFilter : UriPartFilter( - "连载状态", - arrayOf( - Pair("全部", ""), - Pair("连载", "2309"), - Pair("完结", "2310") - ) - ) - - private class TypeFilter : UriPartFilter( - "地区", - arrayOf( - Pair("全部", ""), - Pair("日本", "2304"), - Pair("韩国", "2305"), - Pair("欧美", "2306"), - Pair("港台", "2307"), - Pair("内地", "2308"), - Pair("其他", "8453") - ) - ) - - private class SortFilter : UriPartFilter( - "排序", - arrayOf( - Pair("人气", "0"), - Pair("更新", "1") - ) - ) - - private class ReaderFilter : UriPartFilter( - "读者", - arrayOf( - Pair("全部", ""), - Pair("少年", "3262"), - Pair("少女", "3263"), - Pair("青年", "3264") - ) - ) - - private open class UriPartFilter( - displayName: String, - val vals: Array<Pair<String, String>>, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), defaultValue) { - open fun toUriPart() = vals[state].second - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val apiRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = API_RATELIMIT_PREF - title = API_RATELIMIT_PREF_TITLE - summary = API_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(API_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(apiRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val apiRateLimitPreference = ListPreference(screen.context).apply { - key = API_RATELIMIT_PREF - title = API_RATELIMIT_PREF_TITLE - summary = API_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(API_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - screen.addPreference(apiRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - } - - companion object { - private const val API_RATELIMIT_PREF = "apiRatelimitPreference" - private const val API_RATELIMIT_PREF_TITLE = "主站每秒连接数限制" // "Ratelimit permits per second for main website" - private const val API_RATELIMIT_PREF_SUMMARY = "此值影响向动漫之家网站发起连接请求的数量。调低此值可能减少发生HTTP 429(连接请求过多)错误的几率,但加载速度也会变慢。需要重启软件以生效。\n当前值:%s" // "This value affects network request amount to dmzj's url. Lower this value may reduce the chance to get HTTP 429 error, but loading speed will be slower too. Tachiyomi restart required. Current value: %s" - - private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference" - private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "图片CDN每秒连接数限制" // "Ratelimit permits per second for image CDN" - private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "此值影响加载图片时发起连接请求的数量。调低此值可能减小图片加载错误的几率,但加载速度也会变慢。需要重启软件以生效。\n当前值:%s" // "This value affects network request amount for loading image. Lower this value may reduce the chance to get error when loading image, but loading speed will be slower too. Tachiyomi restart required. Current value: %s" - - private val extractComicIdFromWebpageRegex = Regex("""addSubscribe\((\d+)\)""") - private val checkComicIdIsNumericalRegex = Regex("""^\d+$""") - private val extractComicIdFromMangaUrlRegex = Regex("""(\d+)\.(json|html)""") // Get comic ID from manga.url - - private val ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray() - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/DmzjUrlActivity.kt b/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/DmzjUrlActivity.kt deleted file mode 100644 index fe988945e..000000000 --- a/src/zh/dmzj/src/eu/kanade/tachiyomi/extension/zh/dmzj/DmzjUrlActivity.kt +++ /dev/null @@ -1,44 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.dmzj - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://www.dmzj.com/info/xxx intents and redirects them to - * the main tachiyomi process. The idea is to not install the intent filter unless - * you have this extension installed, but still let the main tachiyomi app control - * things. - */ -class DmzjUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 0) { - val titleId = if (pathSegments.size > 1) { - pathSegments[1] // [m,www].dmzj.com/info/{titleId} - } else { - pathSegments[0] // manhua.dmzj.com/{titleId} - } - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${Dmzj.PREFIX_ID_SEARCH}$titleId") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("DmzjUrlActivity", e.toString()) - } - } else { - Log.e("DmzjUrlActivity", "Could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/zh/gufengmh/AndroidManifest.xml b/src/zh/gufengmh/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/gufengmh/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/gufengmh/build.gradle b/src/zh/gufengmh/build.gradle deleted file mode 100644 index 3e0cb94f9..000000000 --- a/src/zh/gufengmh/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Gufeng Manhua' - pkgNameSuffix = 'zh.gufengmh' - extClass = '.Gufengmh' - extVersionCode = 3 - libVersion = '1.2' -} - - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/gufengmh/res/mipmap-hdpi/ic_launcher.png b/src/zh/gufengmh/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0ea26e4a8..000000000 Binary files a/src/zh/gufengmh/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/gufengmh/res/mipmap-mdpi/ic_launcher.png b/src/zh/gufengmh/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 60513cee9..000000000 Binary files a/src/zh/gufengmh/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/gufengmh/res/mipmap-xhdpi/ic_launcher.png b/src/zh/gufengmh/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 43e5aacbf..000000000 Binary files a/src/zh/gufengmh/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/gufengmh/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/gufengmh/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6c012665f..000000000 Binary files a/src/zh/gufengmh/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/gufengmh/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/gufengmh/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 04a89a9f5..000000000 Binary files a/src/zh/gufengmh/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/gufengmh/res/web_hi_res_512.png b/src/zh/gufengmh/res/web_hi_res_512.png deleted file mode 100644 index 4ea4fa161..000000000 Binary files a/src/zh/gufengmh/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/gufengmh/src/eu/kanade/tachiyomi/extension/zh/gufengmh/Gufengmh.kt b/src/zh/gufengmh/src/eu/kanade/tachiyomi/extension/zh/gufengmh/Gufengmh.kt deleted file mode 100644 index ca376be19..000000000 --- a/src/zh/gufengmh/src/eu/kanade/tachiyomi/extension/zh/gufengmh/Gufengmh.kt +++ /dev/null @@ -1,432 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.gufengmh - -import android.net.Uri -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class Gufengmh : ParsedHttpSource() { - override val name: String = "古风漫画网" - override val lang: String = "zh" - override val supportsLatest: Boolean = true - override val baseUrl: String = "https://m.gufengmh8.com" - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/list/click/?page=$page", headers) - } - override fun popularMangaNextPageSelector(): String? = "li.next" - override fun popularMangaSelector(): String = "li.list-comic" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("a.txtA").text() - setUrlWithoutDomain(element.select("a.txtA").attr("abs:href")) - thumbnail_url = element.select("mip-img").attr("abs:src") - } - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/list/update/?page=$page", headers) - } - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse(baseUrl).buildUpon() - if (query.isNotBlank()) { - uri.appendPath("search") - .appendEncodedPath("") - .appendQueryParameter("keywords", query) - .appendQueryParameter("page", page.toString()) - } else { - uri.appendPath("list") - val pathBuilder = Uri.Builder() - filters.forEach { - if (it is UriFilter) - it.addToUri(pathBuilder) - } - val filterPath = pathBuilder.toString().replace("/", "-").removePrefix("-") - uri.appendEncodedPath(filterPath) - .appendEncodedPath("") - } - - return GET(uri.toString(), headers) - } - - override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() - override fun searchMangaSelector(): String = "div.itemBox, li.list-comic" - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("a.title, a.txtA").text() - setUrlWithoutDomain(element.select("a.title, a.txtA").attr("abs:href")) - thumbnail_url = element.select("mip-img").attr("abs:src") - } - - // Details - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - title = document.select("h1.title").text() - thumbnail_url = document.select("div#Cover mip-img").attr("abs:src") - author = document.select("dt:contains(作者) + dd").text() - artist = author - genre = document.select("dt:contains(类别) + dd").text() - description = document.select("p.txtDesc").text() - } - - // Chapters - - override fun chapterListSelector(): String = "div.list li" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - url = element.select("a").attr("href") - name = element.select("span").text() - } - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - // Pages - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - val script = document.select("script:containsData(chapterImages )").html() - val images = script.substringAfter("chapterImages = [\"").substringBefore("\"]").split("\",\"") - val path = script.substringAfter("chapterPath = \"").substringBefore("\";") - val server = script.substringAfter("pageImage = \"").substringBefore("/images/cover") - images.forEach { - add(Page(size, "", "$server/$path/$it")) - } - } - override fun imageUrlParse(document: Document): String = throw Exception("Not Used") - - // Filters - - override fun getFilterList(): FilterList { - return FilterList( - Filter.Header("如果使用文本搜索"), - Filter.Header("过滤器将被忽略"), - typefilter(), - regionfilter(), - genrefilter(), - letterfilter(), - statusfilter() - ) - } - - private class typefilter : UriSelectFilterPath( - "按类型", - "filtertype", - arrayOf( - Pair("", "全部"), - Pair("shaonian", "少年漫画"), - Pair("shaonv", "少女漫画"), - Pair("qingnian", "青年漫画"), - Pair("zhenrenmanhua", "真人漫画") - ) - ) - - private class regionfilter : UriSelectFilterPath( - "按地区", - "filterregion", - arrayOf( - Pair("", "全部"), - Pair("ribenmanhua", "日本漫画"), - Pair("guochanmanhua", "国产漫画"), - Pair("gangtaimanhua", "港台漫画"), - Pair("oumeimanhua", "欧美漫画"), - Pair("hanguomanhua", "韩国漫画") - ) - ) - - private class genrefilter : UriSelectFilterPath( - "按剧情", - "filtergenre", - arrayOf( - Pair("", "全部"), - Pair("maoxian", "冒险"), - Pair("mofa", "魔法"), - Pair("kehuan", "科幻"), - Pair("kongbu", "恐怖"), - Pair("lishi", "历史"), - Pair("jingji", "竞技"), - Pair("huanlexiang", "欢乐向"), - Pair("xifangmohuan", "西方魔幻"), - Pair("aiqing", "爱情"), - Pair("xuanyi", "悬疑"), - Pair("qihuan", "奇幻"), - Pair("qingxiaoshuo", "轻小说"), - Pair("sige", "四格"), - Pair("shengui", "神鬼"), - Pair("zhiyu", "治愈"), - Pair("xiaoyuan", "校园"), - Pair("weiniang", "伪娘"), - Pair("danmei", "耽美"), - Pair("hougong", "后宫"), - Pair("mohuan", "魔幻"), - Pair("wuxia", "武侠"), - Pair("zhichang", "职场"), - Pair("zhentan", "侦探"), - Pair("meishi", "美食"), - Pair("gedou", "格斗"), - Pair("lizhi", "励志"), - Pair("yinyuewudao", "音乐舞蹈"), - Pair("rexue", "热血"), - Pair("zhanzheng", "战争"), - Pair("gaoxiao", "搞笑"), - Pair("shenghuo", "生活"), - Pair("baihe", "百合"), - Pair("mengji", "萌系"), - Pair("jiecao", "节操"), - Pair("xingzhuanhuan", "性转换"), - Pair("yanyi", "颜艺"), - Pair("gufeng", "古风"), - Pair("xianxia", "仙侠"), - Pair("zhaiji", "宅系"), - Pair("juqing", "剧情"), - Pair("shenmo", "神魔"), - Pair("xuanhuan", "玄幻"), - Pair("chuanyue", "穿越"), - Pair("qita", "其他"), - Pair("huanxiang", "幻想"), - Pair("motong", "墨瞳"), - Pair("maimeng", "麦萌"), - Pair("manman", "漫漫"), - Pair("manhuadao", "漫画岛"), - Pair("tuili", "推理"), - Pair("dongfang", "东方"), - Pair("kuaikan", "快看"), - Pair("jizhan", "机战"), - Pair("gaoqingdanxing", "高清单行"), - Pair("xinzuo", "新作"), - Pair("tougao", "投稿"), - Pair("richang", "日常"), - Pair("shougong", "手工"), - Pair("yundong", "运动"), - Pair("weimei", "唯美"), - Pair("dushi", "都市"), - Pair("jingxian", "惊险"), - Pair("jiangshi", "僵尸"), - Pair("lianai", "恋爱"), - Pair("nuexin", "虐心"), - Pair("chunai", "纯爱"), - Pair("fuchou", "复仇"), - Pair("dongzuo", "动作"), - Pair("qita2", "其它"), - Pair("egao", "恶搞"), - Pair("mingxing", "明星"), - Pair("zhenhan", "震撼"), - Pair("anhei", "暗黑"), - Pair("naodong", "脑洞"), - Pair("xuexing", "血腥"), - Pair("youyaoqi", "有妖气"), - Pair("jijia", "机甲"), - Pair("qingchun", "青春"), - Pair("lingyi", "灵异"), - Pair("tongren", "同人"), - Pair("langman", "浪漫"), - Pair("quanmou", "权谋"), - Pair("shehui", "社会"), - Pair("gongdou", "宫斗"), - Pair("baoxiao", "爆笑"), - Pair("tiyu", "体育"), - Pair("lanmu", "栏目"), - Pair("caihong", "彩虹"), - Pair("zhentantuili", "侦探推理"), - Pair("shaonuaiqing", "少女爱情"), - Pair("gaoxiaoxiju", "搞笑喜剧"), - Pair("kongbulingyi", "恐怖灵异"), - Pair("kehuanmohuan", "科幻魔幻"), - Pair("jingjitiyu", "竞技体育"), - Pair("wuxiagedou", "武侠格斗"), - Pair("jianniang", "舰娘"), - Pair("danmeiBL", "耽美BL"), - Pair("xiee", "邪恶"), - Pair("zongheqita", "综合其它"), - Pair("qingnian", "青年"), - Pair("zhainan", "宅男"), - Pair("zazhi", "杂志"), - Pair("yinyue", "音乐"), - Pair("quancai", "全彩"), - Pair("heidao", "黑道"), - Pair("lianaidanmei", "恋爱耽美"), - Pair("rexuemaoxian", "热血冒险"), - Pair("funv", "腐女"), - Pair("gushi", "故事"), - Pair("shaonv", "少女"), - Pair("zongcai", "总裁"), - Pair("baoxiaoxiju", "爆笑喜剧"), - Pair("qitamanhua", "其他漫画"), - Pair("lianaishenghuo", "恋爱生活"), - Pair("kongbuxuanyi", "恐怖悬疑"), - Pair("danmeirensheng", "耽美人生"), - Pair("chongwu", "宠物"), - Pair("zhandou", "战斗"), - Pair("zhaohuanshou", "召唤兽"), - Pair("yineng", "异能"), - Pair("zhuangbi", "装逼"), - Pair("yishijie", "异世界"), - Pair("zhengju", "正剧"), - Pair("wenxin", "温馨"), - Pair("jingqi", "惊奇"), - Pair("jiakong", "架空"), - Pair("qingsong", "轻松"), - Pair("weilai", "未来"), - Pair("keji", "科技"), - Pair("shaonao", "烧脑"), - Pair("gaoxiaoegao", "搞笑恶搞"), - Pair("mhuaquan", "mhuaquan"), - Pair("shaonian", "少年"), - Pair("sigeduoge", "四格多格"), - Pair("bazong", "霸总"), - Pair("xiuzhen", "修真"), - Pair("gushimanhua", "故事漫画"), - Pair("huiben", "绘本"), - Pair("youxi", "游戏"), - Pair("zhenren", "真人"), - Pair("jingsong", "惊悚"), - Pair("manhua", "漫画"), - Pair("weizhongquan", "微众圈"), - Pair("yujie", "御姐"), - Pair("xiaoshuogaibian", "小说改编"), - Pair("luoli", "萝莉"), - Pair("1024manhua", "1024manhua"), - Pair("jiating", "家庭"), - Pair("shenhua", "神话"), - Pair("shishi", "史诗"), - Pair("moshi", "末世"), - Pair("yulequan", "娱乐圈"), - Pair("gandong", "感动"), - Pair("lunli", "伦理"), - Pair("zazhiquanben", "杂志全本"), - Pair("zhiyu2", "致郁"), - Pair("shangzhan", "商战"), - Pair("zhupu", "主仆"), - Pair("manhuaquan", "漫画圈"), - Pair("lianaijuqingmanhua", "恋爱、剧情漫画"), - Pair("hunai", "婚爱"), - Pair("haomen", "豪门"), - Pair("neihan", "内涵"), - Pair("xingzhuan", "性转"), - Pair("xiangcun", "乡村"), - Pair("gongting", "宫廷"), - Pair("duanzi", "段子"), - Pair("chunaimanhua", "纯爱漫画"), - Pair("nixi", "逆袭"), - Pair("hunyin", "婚姻"), - Pair("baihenvxing", "百合女性"), - Pair("shenghuomanhua", "生活漫画"), - Pair("ertong", "儿童"), - Pair("wudao", "舞蹈"), - Pair("tianchong", "甜宠"), - Pair("wengai", "文改"), - Pair("dujia", "独家"), - Pair("biaoqian", "标签"), - Pair("zhaifumanhua", "宅腐漫画"), - Pair("qinggan", "情感"), - Pair("mingkatong", "茗卡通"), - Pair("jiujie", "纠结"), - Pair("lianaimaoxiangaoxiao", "恋爱冒险搞笑"), - Pair("xiuzhenlianaijiakong", "修真恋爱架空"), - Pair("lianaigaoxiaohougong", "恋爱搞笑后宫"), - Pair("xuanyikongbu", "悬疑恐怖"), - Pair("lianaixiaoyuanshenghuo", "恋爱校园生活"), - Pair("xiuzhenlianaigufeng", "修真恋爱古风"), - Pair("shenghuoxuanyilingyi", "生活悬疑灵异"), - Pair("qingnianmanhua", "青年漫画"), - Pair("lishimanhua", "历史漫画"), - Pair("meishaonv", "美少女"), - Pair("shuangliu", "爽流"), - Pair("qiangwei", "蔷薇"), - Pair("gaozhishang", "高智商"), - Pair("xuanyituili", "悬疑推理"), - Pair("jizhi", "机智"), - Pair("donghua", "动画"), - Pair("rexuedongzuo", "热血动作"), - Pair("xiuji", "秀吉"), - Pair("AA", "AA"), - Pair("gaibian", "改编"), - Pair("juwei", "橘味") - ) - ) - - private class letterfilter : UriSelectFilterPath( - "按字母", - "filterletter", - arrayOf( - Pair("", "全部"), - Pair("a", "A"), - Pair("b", "B"), - Pair("c", "C"), - Pair("d", "D"), - Pair("e", "E"), - Pair("f", "F"), - Pair("g", "G"), - Pair("h", "H"), - Pair("i", "I"), - Pair("j", "J"), - Pair("k", "K"), - Pair("l", "L"), - Pair("m", "M"), - Pair("n", "N"), - Pair("o", "O"), - Pair("p", "P"), - Pair("q", "Q"), - Pair("r", "R"), - Pair("s", "S"), - Pair("t", "T"), - Pair("u", "U"), - Pair("v", "V"), - Pair("w", "W"), - Pair("x", "X"), - Pair("y", "Y"), - Pair("z", "Z"), - Pair("1", "其他") - ) - ) - - private class statusfilter : UriSelectFilterPath( - "按进度", - "filterstatus", - arrayOf( - Pair("", "全部"), - Pair("wanjie", "已完结"), - Pair("lianzai", "连载中") - ) - ) - - /** - * Class that creates a select filter. Each entry in the dropdown has a name and a display name. - * If an entry is selected it is appended as a query parameter onto the end of the URI. - * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. - */ - // vals: <name, display> - private open class UriSelectFilterPath( - displayName: String, - val uriParam: String, - val vals: Array<Pair<String, String>>, - val firstIsUnspecified: Boolean = true, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { - override fun addToUri(uri: Uri.Builder) { - if (state != 0 || !firstIsUnspecified) - uri.appendPath(vals[state].first) - } - } - - /** - * Represents a filter that is able to modify a URI. - */ - private interface UriFilter { - fun addToUri(uri: Uri.Builder) - } -} diff --git a/src/zh/hanhankuman/AndroidManifest.xml b/src/zh/hanhankuman/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/hanhankuman/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/hanhankuman/build.gradle b/src/zh/hanhankuman/build.gradle deleted file mode 100644 index 928d459a0..000000000 --- a/src/zh/hanhankuman/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Hanhankuman' - pkgNameSuffix = 'zh.hanhankuman' - extClass = '.HanhanKuman' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/hanhankuman/res/mipmap-hdpi/ic_launcher.png b/src/zh/hanhankuman/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2a71088a9..000000000 Binary files a/src/zh/hanhankuman/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/hanhankuman/res/mipmap-mdpi/ic_launcher.png b/src/zh/hanhankuman/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2ee17eedb..000000000 Binary files a/src/zh/hanhankuman/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/hanhankuman/res/mipmap-xhdpi/ic_launcher.png b/src/zh/hanhankuman/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c5e971560..000000000 Binary files a/src/zh/hanhankuman/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/hanhankuman/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/hanhankuman/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 27ded8e71..000000000 Binary files a/src/zh/hanhankuman/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/hanhankuman/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/hanhankuman/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d2c4ea147..000000000 Binary files a/src/zh/hanhankuman/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/hanhankuman/res/web_hi_res_512.png b/src/zh/hanhankuman/res/web_hi_res_512.png deleted file mode 100644 index 581d1a22c..000000000 Binary files a/src/zh/hanhankuman/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/hanhankuman/src/eu/kanade/tachiyomi/extension/zh/hanhankuman/HanhanKuman.kt b/src/zh/hanhankuman/src/eu/kanade/tachiyomi/extension/zh/hanhankuman/HanhanKuman.kt deleted file mode 100644 index b9f83edbe..000000000 --- a/src/zh/hanhankuman/src/eu/kanade/tachiyomi/extension/zh/hanhankuman/HanhanKuman.kt +++ /dev/null @@ -1,183 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.hanhankuman - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class HanhanKuman : ParsedHttpSource() { - - override val name = "汗汗酷漫" - override val baseUrl = "http://www.hhimm.com" - override val lang = "zh" - override val supportsLatest = true - - override fun popularMangaSelector() = ".cTopComicList > div.cComicItem" - override fun searchMangaSelector() = ".cComicList > li" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun chapterListSelector() = "ul.cVolUl > li" - - override fun searchMangaNextPageSelector() = "li.next" - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/top/hotrating.aspx", headers) - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/top/newrating.aspx", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/comic/?act=search&st=$query")?.newBuilder() - return GET(url.toString(), headers) - } - - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.url = element.select("a").first()!!.attr("href") - manga.title = element.select("span.cComicTitle").text().trim() - manga.author = element.select("span.cComicAuthor").first()?.text()?.trim() - manga.thumbnail_url = element.select("div.cListSlt > a > img").attr("abs:src") - manga.description = element.select(".cComicMemo").text().trim() - - return manga - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title").trim() - manga.thumbnail_url = it.select("img").attr("abs:src").trim() - } - return manga - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - element.select("a").let { - chapter.setUrlWithoutDomain(it.attr("href")) - chapter.name = it.attr("title").trim() - } - - return chapter - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.author = document.select("li:contains(作者)").text()?.substringAfterLast(":")?.trim() - manga.artist = document.select("li:contains(作者)").text()?.substringAfterLast(":")?.trim() - manga.description = document.select("li:contains(简介)").text().substringAfterLast(":").trim() - manga.thumbnail_url = document.select("img[src*=comicui]").attr("src") - manga.status = when (document.select("li:contains(状态)").text()?.substringAfterLast(":")?.trim()) { - "连载" -> SManga.ONGOING - "完结" -> SManga.COMPLETED - // "" -> SManga.LICENSED - else -> SManga.UNKNOWN - } - return manga - } - - override fun pageListParse(response: Response): List<Page> { - val url = response.request().url().url().toString() - - val re = Regex(""".*\/(.*?)\/\d+\.html\?s=(\d+)""") - - val matches = re.find(url)?.groups!! - val pathId = matches[1]!!.value - val pathS = matches[2]!!.value - - return pageListParse(response.asJsoup(), pathId, pathS) - } - - override fun pageListParse(document: Document): List<Page> = listOf() - - fun pageListParse(document: Document, id: String, s: String): List<Page> { - return document.select("#iPageHtm > a").mapIndexed { i, _ -> - Page(i, String.format("http://www.hhimm.com/%s/%d.html?s=%s&d=0", id, i + 1, s), "") - } - } - - override fun imageUrlParse(document: Document): String { - // get img key - val imgEleIds = arrayOf("img1021", "img2391", "img7652", "imgCurr") - var imgKey: String? = null - for (i in imgEleIds.indices) { - imgKey = document.select("#" + imgEleIds[i]).attr("name") - if (imgKey != "") break - } - - val servers = document.select("#hdDomain").attr("value").split("|") - - // img key decode - return if (imgKey != "") { - servers[0] + unsuan(imgKey!!) - } else "" - } - - // https://stackoverflow.com/questions/2946067/what-is-the-java-equivalent-to-javascripts-string-fromcharcode - fun fromCharCode(vararg codePoints: Int): String { - return String(codePoints, 0, codePoints.size) - } - - private fun unsuan(s: String): String { - var s = s - val sw = "44123.com|hhcool.com|hhimm.com" - val su = "www.hhimm.com" - var b = false - - for (i in 0 until sw.split("|".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray().size) { - if (su.indexOf(sw.split("|".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[i]) > -1) { - b = true - break - } - } - if (!b) - return "" - - val x = s.substring(s.length - 1) - val w = "abcdefghijklmnopqrstuvwxyz" - val xi = w.indexOf(x) + 1 - val sk = s.substring(s.length - xi - 12, s.length - xi - 1) - s = s.substring(0, s.length - xi - 12) - val k = sk.substring(0, sk.length - 1) - val f = sk.substring(sk.length - 1) - - for (i in k.indices) { - s = s.replace(k.substring(i, i + 1), i.toString()) - } - val ss = s.split(f.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - s = "" - for (i in ss.indices) { - s += fromCharCode(Integer.parseInt(ss[i])) - } - return s - } - - private class GenreFilter(genres: Array<String>) : Filter.Select<String>("Genre", genres) - - override fun getFilterList() = FilterList( - GenreFilter(getGenreList()) - ) - - private fun getGenreList() = arrayOf( - "All" - ) -} diff --git a/src/zh/jinmantiantang/AndroidManifest.xml b/src/zh/jinmantiantang/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/jinmantiantang/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/jinmantiantang/build.gradle b/src/zh/jinmantiantang/build.gradle deleted file mode 100644 index d13654f51..000000000 --- a/src/zh/jinmantiantang/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Jinmantiantang' - pkgNameSuffix = 'zh.jinmantiantang' - extClass = '.Jinmantiantang' - extVersionCode = 11 - libVersion = '1.2' - containsNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/jinmantiantang/res/mipmap-hdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d98d6fa7d..000000000 Binary files a/src/zh/jinmantiantang/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/jinmantiantang/res/mipmap-ldpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-ldpi/ic_launcher.png deleted file mode 100644 index d98d6fa7d..000000000 Binary files a/src/zh/jinmantiantang/res/mipmap-ldpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/jinmantiantang/res/mipmap-mdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d98d6fa7d..000000000 Binary files a/src/zh/jinmantiantang/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/jinmantiantang/res/mipmap-xhdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d98d6fa7d..000000000 Binary files a/src/zh/jinmantiantang/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/jinmantiantang/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d98d6fa7d..000000000 Binary files a/src/zh/jinmantiantang/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/jinmantiantang/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d98d6fa7d..000000000 Binary files a/src/zh/jinmantiantang/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/jinmantiantang/res/web_hi_res_512.png b/src/zh/jinmantiantang/res/web_hi_res_512.png deleted file mode 100644 index d98d6fa7d..000000000 Binary files a/src/zh/jinmantiantang/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt deleted file mode 100644 index 171040374..000000000 --- a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt +++ /dev/null @@ -1,449 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.jinmantiantang - -import android.app.Application -import android.content.SharedPreferences -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect -import androidx.preference.EditTextPreference -import androidx.preference.PreferenceScreen -import eu.kanade.tachiyomi.annotations.Nsfw -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.HttpUrl -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.select.Elements -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.ByteArrayOutputStream -import java.io.InputStream -import java.text.SimpleDateFormat -import java.util.Locale -import kotlin.math.floor -import android.support.v7.preference.EditTextPreference as LegacyEditTextPreference -import android.support.v7.preference.PreferenceScreen as LegacyPreferenceScreen - -@Nsfw -class Jinmantiantang : ConfigurableSource, ParsedHttpSource() { - - override val baseUrl: String = "https://18comic.bet" - override val lang: String = "zh" - override val name: String = "禁漫天堂" - override val supportsLatest: Boolean = true - - // 220980 - // 算法 html页面 1800 行左右 - // 图片开始分割的ID编号 - private val scrambleId = 220980 - - // 对只有一章的漫画进行判断条件 - private var chapterArea = "a[class=col btn btn-primary dropdown-toggle reading]" - - // 处理URL请求 - override val client: OkHttpClient = network.cloudflareClient.newBuilder().addInterceptor( - fun(chain): Response { - val url = chain.request().url().toString() - val response = chain.proceed(chain.request()) - if (!url.contains("media/photos", ignoreCase = true)) return response // 对非漫画图片连接直接放行 - if (url.substring(url.indexOf("photos/") + 7, url.lastIndexOf("/")).toInt() < scrambleId) return response // 对在漫画章节ID为220980之前的图片未进行图片分割,直接放行 -// 章节ID:220980(包含)之后的漫画(2020.10.27之后)图片进行了分割倒序处理 - val res = response.body()!!.byteStream().use { - decodeImage(it) - } - val mediaType = MediaType.parse("image/avif,image/webp,image/apng,image/*,*/*") - val outputBytes = ResponseBody.create(mediaType, res) - return response.newBuilder().body(outputBytes).build() - } - ).build() - - // 对被分割的图片进行分割,排序处理 - private fun decodeImage(img: InputStream): ByteArray { - // 使用bitmap进行图片处理 - val input = BitmapFactory.decodeStream(img) - // 漫画高度 and width - val height = input.height - val width = input.width - // 水平分割10个小图 - val rows = 10 - // 未除尽像素 - val remainder = (height % rows) - // 创建新的图片对象 - val resultBitmap = Bitmap.createBitmap(input.width, input.height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(resultBitmap) - // 分割图片 - for (x in 0 until rows) { - // 分割算法(详情见html源码页的方法"function scramble_image(img)") - var copyH = floor(height / rows.toDouble()).toInt() - var py = copyH * (x) - val y = height - (copyH * (x + 1)) - remainder - if (x == 0) { - copyH += remainder - } else { - py += remainder - } - // 要裁剪的区域 - val crop = Rect(0, y, width, y + copyH) - // 裁剪后应放置到新图片对象的区域 - val splic = Rect(0, py, width, py + copyH) - - canvas.drawBitmap(input, crop, splic, null) - } - // 创建输出流 - val output = ByteArrayOutputStream() - resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, output) - return output.toByteArray() - } - - // 点击量排序(人气) - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/albums?o=mv&page=$page", headers) - } - - override fun popularMangaNextPageSelector(): String = "a.prevnext" - override fun popularMangaSelector(): String { - val baseSelector = "div.col-xs-6.col-sm-6.col-md-4.col-lg-3.list-col div.well.well-sm" - val removedGenres = preferences.getString("BLOCK_GENRES_LIST", "")!!.substringBefore("//").trim() - // Extra selector is jquery-like selector, it uses regex to match element.text(). - // If string after 標籤 contains any word of removedGenres, the element would be ignored. - return if (removedGenres != "") - baseSelector + ":not(:matches((?i).*標籤: .*(${removedGenres.split(' ').joinToString("|")}).*))" - else - baseSelector - } - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("span.video-title").text() - setUrlWithoutDomain(element.select("a").first().attr("href")) - thumbnail_url = element.select("img").attr("data-original").split("?")[0] - author = element.select("div.title-truncate").select("a").first().text() - } - - // 最新排序 - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/albums?o=mr&page=$page", headers) - } - - override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - // 查询信息 - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var params = filters.map { - if (it is UriPartFilter) { - it.toUriPart() - } else "" - }.filter { it != "" }.joinToString("") - - val url = if (query != "" && !query.contains("-")) { - // 禁漫天堂特有搜索方式: A +B --> A and B, A B --> A or B - var newQuery = query.replace("+", "%2B").replace(" ", "+") - // remove illegal param - params = params.substringAfter("?") - if (params.contains("search_query")) { - val keyword = params.substringBefore("&").substringAfter("=") - newQuery = "$newQuery+%2B$keyword" - params = params.substringAfter("&") - } - HttpUrl.parse("$baseUrl/search/photos?search_query=$newQuery&page=$page&$params")?.newBuilder() - } else { - params = if (params == "") "/albums?" else params - if (query == "") { - HttpUrl.parse("$baseUrl$params&page=$page")?.newBuilder() - } else { - // 在搜索栏的关键词前添加-号来实现对筛选结果的过滤, 像 "-YAOI -扶他 -毛絨絨 -獵奇", 注意此时搜索功能不可用. - val removedGenres = query.split(" ").filter { it.startsWith("-") }.joinToString("+") { it.removePrefix("-") } - HttpUrl.parse("$baseUrl$params&page=$page&screen=$removedGenres")?.newBuilder() - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector() - override fun searchMangaSelector(): String = popularMangaSelector() - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // 漫画详情 - // url网址 , title标题 , artist艺术家 , author作者 , description描述 , genre类型 , thumbnail_url缩图网址 , initialized是否初始化 - // status状态 0未知,1连载,2完结,3领取牌照 - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - determineChapterInfo(document) - title = document.select("div.panel-heading").select("div.pull-left").first().text() - // keep thumbnail_url same as the one in popularMangaFromElement() - thumbnail_url = document.select("img.lazy_img.img-responsive").attr("src").split("?")[0].replace(".jpg", "_3x4.jpg") - author = selectAuthor(document) - artist = author - genre = selectDetailsStatusAndGenre(document, 0).trim().split(" ").joinToString(", ") - - // When the index passed by the "selectDetailsStatusAndGenre(document: Document, index: Int)" index is 1, - // it will definitely return a String type of 0, 1 or 2. This warning can be ignored - status = selectDetailsStatusAndGenre(document, 1).trim().toInt() - description = document.select("#intro-block .p-t-5.p-b-5").text().substringAfter("敘述:").trim() - } - - // 查询作者信息 - private fun selectAuthor(document: Document): String { - val element = document.select("div.tag-block")[9] - return if (element.select("a").size == 0) { - "未知" - } else { - element.select("a").first().text() - } - } - - // 查询漫画状态和类别信息 - private fun selectDetailsStatusAndGenre(document: Document, index: Int): String { - determineChapterInfo(document) - var status = "0" - var genre = "" - if (document.select("span[itemprop=genre] a").size == 0) { - return if (index == 1) { - status - } else { - genre - } - } - val elements: Elements = document.select("span[itemprop=genre]").first().select("a") - for (value in elements) { - when (val vote: String = value.select("a").text()) { - "連載中" -> { - status = "1" - } - "完結" -> { - status = "2" - } - else -> { - genre = "$genre$vote " - } - } - } - return if (index == 1) { - status - } else { - genre - } - } - - // 漫画章节信息 - override fun chapterListSelector(): String = chapterArea - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) - if (chapterArea == "body") { - name = "Ch. 1" - url = element.select("a[class=col btn btn-primary dropdown-toggle reading]").attr("href") - date_upload = sdf.parse(element.select("div[itemprop='datePublished']").attr("content"))?.time ?: 0 - } else { - url = element.select("a").attr("href") - name = element.select("a li").first().ownText() - date_upload = sdf.parse(element.select("a li span.hidden-xs").text().trim())?.time ?: 0 - } - } - - private fun determineChapterInfo(document: Document) { - chapterArea = if (document.select("div[id=episode-block] a li").size == 0) { - "body" - } else { - "div[id=episode-block] a[href^=/photo/]" - } - } - - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).asReversed() - } - - // 漫画图片信息 - override fun pageListParse(document: Document): List<Page> { - fun internalParse(document: Document, pages: MutableList<Page>): List<Page> { - val elements = document.select("div[style=text-align:center;][id*=0]") - for (element in elements) { - pages.apply { - if (element.select("div[style=text-align:center;][id*=0] img").attr("src").indexOf("blank.jpg") >= 0) { - add(Page(size, "", element.select("div[style=text-align:center;][id*=0] img").attr("data-original").split("\\?")[0])) - } else { - add(Page(size, "", element.select("div[style=text-align:center;][id*=0] img").attr("src").split("\\?")[0])) - } - } - } - return document.select("a.prevnext").firstOrNull() - ?.let { internalParse(client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup(), pages) } ?: pages - } - - return internalParse(document, mutableListOf()) - } - - override fun imageUrlParse(document: Document): String = throw Exception("Not Used") - - // Filters - // 按照类别信息进行检索 - - override fun getFilterList() = FilterList( - CategoryGroup(), - SortFilter(), - TimeFilter() - ) - - private class CategoryGroup : UriPartFilter( - "按类型", - arrayOf( - Pair("全部", "/albums?"), - Pair("其他", "/albums/another?"), - Pair("同人", "/albums/doujin?"), - Pair("韩漫", "/albums/hanman?"), - Pair("美漫", "/albums/meiman?"), - Pair("短篇", "/albums/short?"), - Pair("单本", "/albums/single?"), - Pair("汉化", "/albums/doujin/sub/chinese?"), - Pair("日语", "/albums/doujin/sub/japanese?"), - Pair("汉化", "/albums/doujin/sub/chinese?"), - Pair("Cosplay", "/albums/doujin/sub/cosplay?"), - Pair("CG图集", "/albums/doujin/sub/CG?"), - - Pair("P站", "/search/photos?search_query=PIXIV&"), - Pair("3D", "/search/photos?search_query=3D&"), - - Pair("剧情", "/search/photos?search_query=劇情&"), - Pair("校园", "/search/photos?search_query=校園&"), - Pair("纯爱", "/search/photos?search_query=純愛&"), - Pair("人妻", "/search/photos?search_query=人妻&"), - Pair("师生", "/search/photos?search_query=師生&"), - Pair("乱伦", "/search/photos?search_query=亂倫&"), - Pair("近亲", "/search/photos?search_query=近親&"), - Pair("百合", "/search/photos?search_query=百合&"), - Pair("男同", "/search/photos?search_query=YAOI&"), - Pair("性转换", "/search/photos?search_query=性轉換&"), - Pair("NTR", "/search/photos?search_query=NTR&"), - Pair("伪娘", "/search/photos?search_query=偽娘&"), - Pair("痴女", "/search/photos?search_query=癡女&"), - Pair("全彩", "/search/photos?search_query=全彩&"), - Pair("女性向", "/search/photos?search_query=女性向&"), - - Pair("萝莉", "/search/photos?search_query=蘿莉&"), - Pair("御姐", "/search/photos?search_query=御姐&"), - Pair("熟女", "/search/photos?search_query=熟女&"), - Pair("正太", "/search/photos?search_query=正太&"), - Pair("巨乳", "/search/photos?search_query=巨乳&"), - Pair("贫乳", "/search/photos?search_query=貧乳&"), - Pair("女王", "/search/photos?search_query=女王&"), - Pair("教师", "/search/photos?search_query=教師&"), - Pair("女仆", "/search/photos?search_query=女僕&"), - Pair("护士", "/search/photos?search_query=護士&"), - Pair("泳裝", "/search/photos?search_query=泳裝&"), - Pair("眼镜", "/search/photos?search_query=眼鏡&"), - Pair("丝袜", "/search/photos?search_query=絲襪&"), - Pair("连裤袜", "/search/photos?search_query=連褲襪&"), - Pair("制服", "/search/photos?search_query=制服&"), - Pair("兔女郎", "/search/photos?search_query=兔女郎&"), - - Pair("群交", "/search/photos?search_query=群交&"), - Pair("足交", "/search/photos?search_query=足交&"), - Pair("SM", "/search/photos?search_query=SM&"), - Pair("肛交", "/search/photos?search_query=肛交&"), - Pair("阿黑颜", "/search/photos?search_query=阿黑顏&"), - Pair("药物", "/search/photos?search_query=藥物&"), - Pair("扶他", "/search/photos?search_query=扶他&"), - Pair("调教", "/search/photos?search_query=調教&"), - Pair("野外", "/search/photos?search_query=野外&"), - Pair("露出", "/search/photos?search_query=露出&"), - Pair("催眠", "/search/photos?search_query=催眠&"), - Pair("自慰", "/search/photos?search_query=自慰&"), - Pair("触手", "/search/photos?search_query=觸手&"), - Pair("兽交", "/search/photos?search_query=獸交&"), - Pair("亚人", "/search/photos?search_query=亞人&"), - Pair("魔物", "/search/photos?search_query=魔物&"), - - Pair("CG集", "/search/photos?search_query=CG集&"), - Pair("重口", "/search/photos?search_query=重口&"), - Pair("猎奇", "/search/photos?search_query=獵奇&"), - Pair("非H", "/search/photos?search_query=非H&"), - Pair("血腥", "/search/photos?search_query=血腥&"), - Pair("暴力", "/search/photos?search_query=暴力&"), - Pair("血腥暴力", "/search/photos?search_query=血腥暴力&") - ) - ) - - private class SortFilter : UriPartFilter( - "排序", - arrayOf( - Pair("最新", "o=mr&"), - Pair("最多浏览", "o=mv&"), - Pair("最多爱心", "o=tf&"), - Pair("最多图片", "o=mp&") - ) - ) - - private class TimeFilter : UriPartFilter( - "时间", - arrayOf( - Pair("全部", "t=a"), - Pair("今天", "t=t"), - Pair("这周", "t=w"), - Pair("本月", "t=m") - ) - ) - - /** - *创建选择过滤器的类。 下拉菜单中的每个条目都有一个名称和一个显示名称。 - *如果选择了一个条目,它将作为查询参数附加到URI的末尾。 - *如果将firstIsUnspecified设置为true,则如果选择了第一个条目,则URI不会附加任何内容。 - */ - // vals: <name, display> - private open class UriPartFilter( - displayName: String, - val vals: Array<Pair<String, String>>, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), defaultValue) { - open fun toUriPart() = vals[state].second - } - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private val BLOCK_PREF_TITLE = "屏蔽词列表" - private val BLOCK_PREF_DEFAULT = "// 例如 \"YAOI cos 扶他 毛絨絨 獵奇 韩漫 韓漫\", " + - "关键词之间用空格分离, 大小写不敏感, \"//\"后的字符会被忽略" - private val BLOCK_PREF_DIALOGTITLE = "关键词列表" - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - EditTextPreference(screen.context).apply { - key = "BLOCK_GENRES_LIST" - title = BLOCK_PREF_TITLE - setDefaultValue(BLOCK_PREF_DEFAULT) - dialogTitle = BLOCK_PREF_DIALOGTITLE - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putString("BLOCK_GENRES_LIST", newValue as String).commit() - } - }.let { - screen.addPreference(it) - } - } - - override fun setupPreferenceScreen(screen: LegacyPreferenceScreen) { - LegacyEditTextPreference(screen.context).apply { - key = "BLOCK_GENRES_LIST" - title = BLOCK_PREF_TITLE - setDefaultValue(BLOCK_PREF_DEFAULT) - dialogTitle = BLOCK_PREF_DIALOGTITLE - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putString("BLOCK_GENRES_LIST", newValue as String).commit() - } - }.let { - screen.addPreference(it) - } - } -} diff --git a/src/zh/kuaikanmanhua/AndroidManifest.xml b/src/zh/kuaikanmanhua/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/kuaikanmanhua/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/kuaikanmanhua/build.gradle b/src/zh/kuaikanmanhua/build.gradle deleted file mode 100644 index b07633d9d..000000000 --- a/src/zh/kuaikanmanhua/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Kuaikanmanhua' - pkgNameSuffix = 'zh.kuaikanmanhua' - extClass = '.Kuaikanmanhua' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/kuaikanmanhua/res/mipmap-hdpi/ic_launcher.png b/src/zh/kuaikanmanhua/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 47365f396..000000000 Binary files a/src/zh/kuaikanmanhua/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/kuaikanmanhua/res/mipmap-mdpi/ic_launcher.png b/src/zh/kuaikanmanhua/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c2a64f1be..000000000 Binary files a/src/zh/kuaikanmanhua/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/kuaikanmanhua/res/mipmap-xhdpi/ic_launcher.png b/src/zh/kuaikanmanhua/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ef6a83fdf..000000000 Binary files a/src/zh/kuaikanmanhua/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/kuaikanmanhua/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/kuaikanmanhua/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d2a6b7eaa..000000000 Binary files a/src/zh/kuaikanmanhua/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/kuaikanmanhua/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/kuaikanmanhua/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6c0e6a1d2..000000000 Binary files a/src/zh/kuaikanmanhua/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/kuaikanmanhua/res/web_hi_res_512.png b/src/zh/kuaikanmanhua/res/web_hi_res_512.png deleted file mode 100644 index 34003f878..000000000 Binary files a/src/zh/kuaikanmanhua/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/kuaikanmanhua/src/eu/kanade/tachiyomi/extension/zh/kuaikanmanhua/Kuaikanmanhua.kt b/src/zh/kuaikanmanhua/src/eu/kanade/tachiyomi/extension/zh/kuaikanmanhua/Kuaikanmanhua.kt deleted file mode 100644 index 6b7bb4d1a..000000000 --- a/src/zh/kuaikanmanhua/src/eu/kanade/tachiyomi/extension/zh/kuaikanmanhua/Kuaikanmanhua.kt +++ /dev/null @@ -1,242 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.kuaikanmanhua - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class Kuaikanmanhua : ParsedHttpSource() { - - override val name = "Kuaikanmanhua" - - override val baseUrl = "https://www.kuaikanmanhua.com" - - override val lang = "zh" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val gson = Gson() - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/tag/0?state=1&page=$page", headers) - } - - override fun popularMangaParse(response: Response): MangasPage { - return parseMangaDocument(response.asJsoup()) - } - - private fun parseMangaDocument(document: Document): MangasPage { - val mangas = mutableListOf<SManga>() - - gson.fromJson<JsonArray>( - document.select("script:containsData(datalist)").first().data() - .substringAfter("dataList:").substringBefore("}],error") - ) - .forEach { mangas.add(mangaFromJson(it.asJsonObject)) } - - return MangasPage(mangas, document.select(popularMangaNextPageSelector()).isNotEmpty()) - } - - private fun mangaFromJson(jsonObject: JsonObject): SManga { - val manga = SManga.create() - - manga.url = "/web/topic/" + jsonObject["id"].asString - manga.title = jsonObject["title"].asString - manga.thumbnail_url = jsonObject["cover_image_url"].asString - - return manga - } - - override fun popularMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used") - - override fun popularMangaNextPageSelector() = "li:not(.disabled) b.right" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/tag/19?state=1&page=$page", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) - - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotEmpty()) { - GET("$baseUrl/s/result/$query", headers) - } else { - lateinit var genre: String - lateinit var status: String - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - genre = filter.toUriPart() - } - is StatusFilter -> { - status = filter.toUriPart() - } - } - } - GET("$baseUrl/tag/$genre?state=$status&page=$page", headers) - } - } - - override fun searchMangaParse(response: Response): MangasPage { - val mangas = mutableListOf<SManga>() - val document = response.asJsoup() - - document.select("script:containsData(resultList)").let { - return if (it.isNotEmpty()) { - // for search by query - gson.fromJson<JsonArray>( - it.first().data() - .substringAfter("resultList:").substringBefore(",noResult") - ) - .forEach { result -> mangas.add(searchMangaFromJson(result.asJsonObject)) } - MangasPage(mangas, document.select(searchMangaNextPageSelector()).isNotEmpty()) - } else { - // for search by genre, status - parseMangaDocument(document) - } - } - } - - private fun searchMangaFromJson(jsonObject: JsonObject): SManga { - val manga = SManga.create() - - manga.url = jsonObject["url"].asString - manga.title = jsonObject["title"].asString - manga.thumbnail_url = jsonObject["image_url"].asString - - return manga - } - - override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used") - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Details - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.TopicHeader").first() - - val manga = SManga.create() - manga.title = infoElement.select("h3").first().text() - manga.author = infoElement.select("div.nickname").text() - manga.description = infoElement.select("div.detailsBox p").text() - - return manga - } - - // Chapters & Pages - - override fun chapterListSelector() = "div.TopicItem" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - - element.select("div.title a").let { - chapter.url = it.attr("href") - chapter.name = it.text() + if (element.select("i.lockedIcon").isNotEmpty()) { " \uD83D\uDD12" } else { "" } - } - return chapter - } - - override fun pageListRequest(chapter: SChapter): Request { - if (chapter.url == "javascript:void(0);") { - throw Exception("[此章节为付费内容]") - } - return super.pageListRequest(chapter) - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - - gson.fromJson<JsonArray>( - document.select("script:containsData(comicImages)").first().data() - .substringAfter("comicImages:").substringBefore("},nextComicInfo") - ) - .forEachIndexed { i, json -> pages.add(Page(i, "", json.asJsonObject["url"].asString)) } - - return pages - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - Filter.Header("注意:不影響按標題搜索"), - StatusFilter(), - GenreFilter() - ) - - private class GenreFilter : UriPartFilter( - "题材", - arrayOf( - Pair("全部", "0"), - Pair("恋爱", "20"), - Pair("古风", "46"), - Pair("校园", "47"), - Pair("奇幻", "22"), - Pair("大女主", "77"), - Pair("治愈", "27"), - Pair("总裁", "52"), - Pair("完结", "40"), - Pair("唯美", "58"), - Pair("日漫", "57"), - Pair("韩漫", "60"), - Pair("穿越", "80"), - Pair("正能量", "54"), - Pair("灵异", "32"), - Pair("爆笑", "24"), - Pair("都市", "48"), - Pair("萌系", "62"), - Pair("玄幻", "63"), - Pair("日常", "19"), - Pair("投稿", "76") - ) - ) - - private class StatusFilter : UriPartFilter( - "类别", - arrayOf( - Pair("全部", "1"), - Pair("连载中", "2"), - Pair("已完结", "3") - ) - ) - - private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } -} diff --git a/src/zh/mangabz/AndroidManifest.xml b/src/zh/mangabz/AndroidManifest.xml deleted file mode 100644 index c72853254..000000000 --- a/src/zh/mangabz/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".zh.mangabz.MangabzUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="mangabz.com" - android:pathPattern="/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/zh/mangabz/build.gradle b/src/zh/mangabz/build.gradle deleted file mode 100644 index 0a6a5a173..000000000 --- a/src/zh/mangabz/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangabz' - pkgNameSuffix = 'zh.mangabz' - extClass = '.Mangabz' - extVersionCode = 1 - libVersion = '1.2' -} - -dependencies { - implementation project(':lib-ratelimit') - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' -} - -android { - defaultConfig { - multiDexEnabled true - } - compileOptions { - coreLibraryDesugaringEnabled true - } -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/mangabz/res/mipmap-hdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 820bc1cbe..000000000 Binary files a/src/zh/mangabz/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/mangabz/res/mipmap-mdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index da479b765..000000000 Binary files a/src/zh/mangabz/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/mangabz/res/mipmap-xhdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 07c0ff96b..000000000 Binary files a/src/zh/mangabz/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/mangabz/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7becaeb73..000000000 Binary files a/src/zh/mangabz/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/mangabz/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/mangabz/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2c61bbb7a..000000000 Binary files a/src/zh/mangabz/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/mangabz/res/web_hi_res_512.png b/src/zh/mangabz/res/web_hi_res_512.png deleted file mode 100644 index 3ee804bdc..000000000 Binary files a/src/zh/mangabz/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Mangabz.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Mangabz.kt deleted file mode 100644 index bb2f35702..000000000 --- a/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/Mangabz.kt +++ /dev/null @@ -1,461 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.mangabz - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import com.squareup.duktape.Duktape -import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.time.LocalDate -import java.time.ZoneId -import java.time.ZonedDateTime -import java.util.ArrayList - -class Mangabz : ConfigurableSource, HttpSource() { - - override val lang = "zh" - override val supportsLatest = false - override val name = "Mangabz" - override val baseUrl = "https://mangabz.com" - private val imageServer = arrayOf("https://cover.mangabz.com", "https://image.mangabz.com") - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - private val mainSiteRateLimitInterceptor = SpecificHostRateLimitInterceptor(HttpUrl.parse(baseUrl)!!, preferences.getString(MAINSITE_RATELIMIT_PREF, "5")!!.toInt()) - private val imageCDNRateLimitInterceptor1 = SpecificHostRateLimitInterceptor(HttpUrl.parse(imageServer[0])!!, preferences.getString(IMAGE_CDN_RATELIMIT_PREF, "5")!!.toInt()) - private val imageCDNRateLimitInterceptor2 = SpecificHostRateLimitInterceptor(HttpUrl.parse(imageServer[1])!!, preferences.getString(IMAGE_CDN_RATELIMIT_PREF, "5")!!.toInt()) - - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .set("Referer", "https://mangabz.com") - .set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36") - - private val showZhHantWebsite = preferences.getBoolean(SHOW_ZH_HANT_WEBSITE_PREF, false) - - override val client: OkHttpClient = network.client.newBuilder() - .addNetworkInterceptor(mainSiteRateLimitInterceptor) - .addNetworkInterceptor(imageCDNRateLimitInterceptor1) - .addNetworkInterceptor(imageCDNRateLimitInterceptor2) - .addNetworkInterceptor { chain -> - val cookies = chain.request().header("Cookie")?.replace(replaceCookiesRegex, "") ?: "" - val newReq = chain - .request() - .newBuilder() - .header("Cookie", if (showZhHantWebsite) cookies else "$cookies; mangabz_lang=2") - .build() - chain.proceed(newReq) - }.build()!! - - override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers) - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangasList = ArrayList<SManga>(0) - - // top banner - document.select("div.banner-con a").map { element -> - mangasList.add( - SManga.create().apply { - title = element.attr("title") - url = element.attr("href") - thumbnail_url = element.select("img").first().attr("src") - } - ) - } - - // ranking sidebar - document.select(".rank-list .list").map { element -> - mangasList.add( - SManga.create().apply { - title = element.select(".rank-item-title").first().text() - url = element.select("a").first().attr("href") - thumbnail_url = element.select("a img").first().attr("src") - } - ) - } - - // carousel list - document.select(".carousel-right-item").map { element -> - mangasList.add( - SManga.create().apply { - title = element.select(".carousel-right-item-title a").first().text() - url = element.select(".carousel-right-item-title a").first().attr("href") - thumbnail_url = element.select("a img").first().attr("src") - } - ) - } - - // recommend list - document.select(".index-manga-item").map { element -> - mangasList.add( - SManga.create().apply { - title = element.select(".index-manga-item-title").first().text() - url = element.select(".index-manga-item-title a").first().attr("href") - thumbnail_url = element.select("a img").first().attr("src") - } - ) - } - - return MangasPage(mangasList.distinctBy { it.url }, false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH) && query.contains(extractMangaIdRegex)) { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(GET("$baseUrl/$id", headers)) - .asObservableSuccess() - .map { response -> - val sManga = mangaDetailsParse(response) - sManga.url = "/$id" - return@map MangasPage(listOf(sManga), false) - } - } else if (query.startsWith(baseUrl) && query.contains(extractMangaIdRegex)) { - val id = extractMangaIdRegex.find(query)?.value - client.newCall(GET("$baseUrl/$id", headers)) - .asObservableSuccess() - .map { response -> - val sManga = mangaDetailsParse(response) - sManga.url = "/$id" - return@map MangasPage(listOf(sManga), false) - } - } else { - super.fetchSearchManga(page, query, filters) - } - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/search?title=$query&page=$page") - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(".mh-list .mh-item").map { element -> - SManga.create().apply { - title = element.select(".mh-item-detali h2.title a").first().text() - url = element.select(".mh-item-detali h2.title a").first().attr("href") - thumbnail_url = element.select("a img.mh-cover").first().attr("src") - } - } - val hasNextPage = document.select(".page-pagination li:contains(>)").first() != null - return MangasPage(mangas, hasNextPage) - } - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used.") - - override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used.") - - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl + manga.url, headers.newBuilder().set("Referer", baseUrl + manga.url).build()) - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - return SManga.create().apply { - title = document.select(".detail-info-title").first().text() - thumbnail_url = document.select("img.detail-info-cover").first().attr("src") - status = when (document.select("span:contains(状态)>span, span:contains(狀態)>span").first().text()) { - "连载中" -> SManga.ONGOING - "連載中" -> SManga.ONGOING - "已完结" -> SManga.COMPLETED - "已完結" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - author = document.select("span:contains(作者) a")?.first()?.text() ?: "" - genre = document.select(".item")?.first()?.text() ?: "" - description = document.select(".detail-info-content")?.first()?.text() ?: "" - } - } - - override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga) - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val latestChapter = document.select(".s a").first().attr("href") - val chapterInfo = document.select(".detail-list-form-title").first().text() - val latestUploadDate = parseDate(chapterInfo) - - return document.select("a.detail-list-form-item").map { element -> - SChapter.create().apply { - url = element.attr("href") - name = element.text() - chapter_number = chapterNumRegex.find(name)?.value?.toFloatOrNull() ?: -1F - if (url == latestChapter) { - date_upload = latestUploadDate - } - } - } - } - - private fun parseDate(string: String): Long { - val rightNow = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")) - // today - if (string.contains("今天")) { - return rightNow.toInstant().toEpochMilli() - } - // yesterday - if (string.contains("昨天")) { - return rightNow.minusDays(1).toInstant().toEpochMilli() - } - // the day before yesterday - if (string.contains("前天")) { - return rightNow.minusDays(2).toInstant().toEpochMilli() - } - // 2021-01-01 - val result1 = dateRegex1.find(string)?.value - if (result1 != null) { - return LocalDate.parse("$result1").atTime(0, 0).atZone(ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli() - } - // 1月1号 or 1月1號 -> (1, 1) - val result2 = dateRegex2.find(string)?.groupValues - if (result2 != null && result2.size > 1) { - val d = rightNow.withMonth(result2[1].toInt()).withDayOfMonth(result2[2].toInt()) - return d.toInstant().toEpochMilli() - } - return rightNow.toInstant().toEpochMilli() - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET(baseUrl + chapter.url, headers.newBuilder().set("Referer", baseUrl + chapter.url).build()) - } - - // Special thanks to Cimoc project. - // https://github.com/feilongfl/Cimoc/blob/03d378ddb5fe8684ef85cae673624afdb68fcf46/app/src/main/java/com/hiroshi/cimoc/source/MangaBZ.kt#L95 - private fun getJSVar(html: String, keyword: String, searchFor: String): String? { - val re = Regex("var\\s+$keyword\\s*=\\s*$searchFor\\s*;") - val match = re.find(html) - return match?.groups?.get(1)?.value - } - - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - val scriptTag = (document.select("head script").filter { it.data().isNotBlank() })[0].data() - val chapterUrl = response.request().url().toString() - - val mid = getJSVar(scriptTag, "MANGABZ_MID", "(\\w+)")!! - val cid = getJSVar(scriptTag, "MANGABZ_CID", "(\\w+)")!! - val sign = getJSVar(scriptTag, "MANGABZ_VIEWSIGN", """\"(\w+)\"""")!! - val pageCount = getJSVar(scriptTag, "MANGABZ_IMAGE_COUNT", "(\\d+)")!!.toInt() - val path = getJSVar(scriptTag, "MANGABZ_CURL", "\"/(\\w+)/\"")!! - - // Page list return by webpage's API maybe incomplete, so we store API url and - // chapter url in page.url to build header and fetch API when needed. - val pagesList = MutableList( - pageCount, - init = { index -> - Page( - index, - url = "$baseUrl/$path/chapterimage.ashx?cid=$cid&page=${index + 1}&key=&_cid=$cid&_mid=$mid&_sign=$sign&_dt=\n" + - chapterUrl - ) - } - ) // Fill the list at first. - - // Page 1 may return 1~2 image urls. - val apiUrlInPage1 = "$baseUrl/$path/chapterimage.ashx?cid=$cid&page=1&key=&_cid=$cid&_mid=$mid&_sign=$sign&_dt=" - val imgUrlList = fetchImageUrlListFromAPI(apiUrlInPage1, response.request().headers()) - for (i in 0 until imgUrlList.length()) { - val imgUrl = imgUrlList[i] as String - val pageNum = extractPageNumFromImageUrlRegex.find(imgUrl)!!.groups[1]!!.value.toInt() - 1 - pagesList[pageNum] = Page(pageNum, "$apiUrlInPage1\n$chapterUrl", imgUrl) - } - - return pagesList - } - - private fun fetchImageUrlListFromAPI(apiUrl: String, requestHeaders: Headers = headers): JSONArray { - val jsEvalPayload = client.newCall(GET(apiUrl, requestHeaders)).execute().body()!!.string() - val imgUrlDecode = Duktape.create().use { - it.evaluate("$jsEvalPayload; JSON.stringify(d);") as String - } - return JSONArray(imgUrlDecode) - } - - override fun fetchImageUrl(page: Page): Observable<String> { - if (page.imageUrl != null) { - return Observable.just(page.imageUrl) - } else { - val urls = page.url.split("\n") - val imgUrlList = fetchImageUrlListFromAPI(urls[0], headers.newBuilder().set("Referer", urls[1]).build()) - - for (i in 0 until imgUrlList.length()) { - val imgUrl = imgUrlList[i] as String - val pageNum = extractPageNumFromImageUrlRegex.find(imgUrl)!!.groups[1]!!.value.toInt() - 1 - if (page.index == pageNum) { - return Observable.just(imgUrl) - } - } - return Observable.error(Exception("Can't find image urls")) - } - } - - override fun imageUrlRequest(page: Page): Request = throw UnsupportedOperationException("Not used.") - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used.") - - override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headers) - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val mainSiteRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = MAINSITE_RATELIMIT_PREF - title = MAINSITE_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = MAINSITE_RATELIMIT_PREF_SUMMARY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(MAINSITE_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val zhHantPreference = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SHOW_ZH_HANT_WEBSITE_PREF - title = SHOW_ZH_HANT_WEBSITE_PREF_TITLE - summary = SHOW_ZH_HANT_WEBSITE_PREF_SUMMARY - - setDefaultValue(false) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putBoolean(SHOW_ZH_HANT_WEBSITE_PREF, newValue as Boolean).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(mainSiteRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - screen.addPreference(zhHantPreference) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val mainSiteRateLimitPreference = ListPreference(screen.context).apply { - key = MAINSITE_RATELIMIT_PREF - title = MAINSITE_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = MAINSITE_RATELIMIT_PREF_SUMMARY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(MAINSITE_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - - setDefaultValue("5") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val zhHantPreference = CheckBoxPreference(screen.context).apply { - key = SHOW_ZH_HANT_WEBSITE_PREF - title = SHOW_ZH_HANT_WEBSITE_PREF_TITLE - summary = SHOW_ZH_HANT_WEBSITE_PREF_SUMMARY - - setDefaultValue(false) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putBoolean(SHOW_ZH_HANT_WEBSITE_PREF, newValue as Boolean).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(mainSiteRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - screen.addPreference(zhHantPreference) - } - - companion object { - private const val MAINSITE_RATELIMIT_PREF = "mainSiteRatelimitPreference" - private const val MAINSITE_RATELIMIT_PREF_TITLE = "主站每秒连接数限制" // "Ratelimit permits per second for main website" - private const val MAINSITE_RATELIMIT_PREF_SUMMARY = "此值影响向网站发起连接请求的数量。调低此值可能减少发生HTTP 429(连接请求过多)错误的几率,但加载速度也会变慢。需要重启软件以生效。\n当前值:%s" // "This value affects network request amount to main website url. Lower this value may reduce the chance to get HTTP 429 error, but loading speed will be slower too. Tachiyomi restart required. Current value: %s" - - private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference" - private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "图片CDN每秒连接数限制" // "Ratelimit permits per second for image CDN" - private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "此值影响加载图片时发起连接请求的数量。调低此值可能减小IP被屏蔽的几率,但加载速度也会变慢。需要重启软件以生效。\n当前值:%s" // "This value affects network request amount for loading image. Lower this value may reduce the chance to get IP Ban, but loading speed will be slower too. Tachiyomi restart required." - - private const val SHOW_ZH_HANT_WEBSITE_PREF = "showZhHantWebsite" - private const val SHOW_ZH_HANT_WEBSITE_PREF_TITLE = "使用繁体版网站" // "Use traditional chinese version website" - private const val SHOW_ZH_HANT_WEBSITE_PREF_SUMMARY = "需要重启软件以生效。" // "You need to restart Tachiyomi" - - private val replaceCookiesRegex = Regex("""mangabz_lang=\d[;\s]*""") - private val extractMangaIdRegex = Regex("""\d+bz""") - private val chapterNumRegex = Regex("""\d+""") - private val dateRegex1 = Regex("""\d{4}-\d{1,2}-\d{1,2}""") - private val dateRegex2 = Regex("""(\d{1,2})月(\d{1,2})[号號]?""") - private val extractPageNumFromImageUrlRegex = Regex("""/(\d+)_\d+\.""") - - private val ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray() - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzUrlActivity.kt b/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzUrlActivity.kt deleted file mode 100644 index 2a53cb49e..000000000 --- a/src/zh/mangabz/src/eu/kanade/tachiyomi/extension/zh/mangabz/MangabzUrlActivity.kt +++ /dev/null @@ -1,42 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.mangabz - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://mangabz.com/XXXXbz intents and redirects them to - * the main tachiyomi process. The idea is to not install the intent filter unless - * you have this extension installed, but still let the main tachiyomi app control - * things. - */ -class MangabzUrlActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 0) { - val titleId = pathSegments[0] - - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${Mangabz.PREFIX_ID_SEARCH}$titleId") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("MangabzUrlActivity", e.toString()) - } - } else { - Log.e("MangabzUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/zh/manhuadb/AndroidManifest.xml b/src/zh/manhuadb/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/manhuadb/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/manhuadb/build.gradle b/src/zh/manhuadb/build.gradle deleted file mode 100644 index 0a13115b9..000000000 --- a/src/zh/manhuadb/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ManhuaDB' - pkgNameSuffix = 'zh.manhuadb' - extClass = '.ManhuaDB' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/manhuadb/res/mipmap-hdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 21adf7123..000000000 Binary files a/src/zh/manhuadb/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/mipmap-mdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7d44fcd5e..000000000 Binary files a/src/zh/manhuadb/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/mipmap-xhdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 41a59e542..000000000 Binary files a/src/zh/manhuadb/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 138efc194..000000000 Binary files a/src/zh/manhuadb/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/web_hi_res_512.png b/src/zh/manhuadb/res/web_hi_res_512.png deleted file mode 100644 index 3edcd969b..000000000 Binary files a/src/zh/manhuadb/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/manhuadb/src/eu/kanade/tachiyomi/extension/zh/manhuadb/ManhuaDB.kt b/src/zh/manhuadb/src/eu/kanade/tachiyomi/extension/zh/manhuadb/ManhuaDB.kt deleted file mode 100644 index 340f8cf7b..000000000 --- a/src/zh/manhuadb/src/eu/kanade/tachiyomi/extension/zh/manhuadb/ManhuaDB.kt +++ /dev/null @@ -1,111 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuadb - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.regex.Pattern - -class ManhuaDB : ParsedHttpSource() { - - override val baseUrl = "https://www.manhuadb.com" - - override val lang = "zh" - - override val name = "漫画DB" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = - super.headersBuilder().add("Referer", "https://www.manhuadb.com") - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.attr("title") - url = element.attr("href") - } - - /** - * Rewrite the method to ensure consistency with previous format orders - */ - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "#comic-book-list > div > ol > li > a" - - override fun imageUrlParse(document: Document): String { - return document.select("div.text-center > img.img-fluid").attr("abs:src") - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - title = document.select("h1.comic-title").text() - thumbnail_url = document.select("td.comic-cover > img").attr("abs:src") - author = document.select("a.comic-creator").text() - description = document.select("p.comic_story").text() - status = when (document.select("td > a.comic-pub-state").text()) { - "连载中" -> SManga.ONGOING - "已完结" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - genre = document.select("ul.tags > li a").joinToString { it.text() } - } - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val pageStr = document.select("ol.breadcrumb > li:eq(2)").text() - val pageNumMatcher = Pattern.compile("共\\s*(\\d+)").matcher(pageStr) - if (pageNumMatcher.find()) { - val page = Integer.parseInt(pageNumMatcher.group(1)!!) - var path = document.select("ol.breadcrumb > li:eq(2) > a").attr("href") - path = path.substring(1, path.length - 5) - for (i in 0 until page) - pages.add(Page(i, "$baseUrl/${path}_p${i + 1}.html")) - } - return pages - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h2").first().let { - manga.setUrlWithoutDomain(it.select("a").first().attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = element.select("a > img").attr("abs:src") - return manga - } - - override fun popularMangaNextPageSelector() = "a:contains(下页):not(.disabled)" - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manhua/list-page-$page.html") - - override fun popularMangaSelector() = "div.comic-book-unit" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.attr("title") - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.select("img").attr("abs:src") - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&p=$page", headers) - } - - override fun searchMangaSelector() = "a.d-block" -} diff --git a/src/zh/manhuadui/AndroidManifest.xml b/src/zh/manhuadui/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/manhuadui/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/manhuadui/build.gradle b/src/zh/manhuadui/build.gradle deleted file mode 100644 index 3322a9c4a..000000000 --- a/src/zh/manhuadui/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manhuadui' - pkgNameSuffix = 'zh.manhuadui' - extClass = '.Manhuadui' - extVersionCode = 14 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/manhuadui/res/mipmap-hdpi/ic_launcher.png b/src/zh/manhuadui/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index beab61e29..000000000 Binary files a/src/zh/manhuadui/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadui/res/mipmap-ldpi/ic_launcher.png b/src/zh/manhuadui/res/mipmap-ldpi/ic_launcher.png deleted file mode 100644 index 1427b9a3c..000000000 Binary files a/src/zh/manhuadui/res/mipmap-ldpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadui/res/mipmap-mdpi/ic_launcher.png b/src/zh/manhuadui/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 176fbcf7a..000000000 Binary files a/src/zh/manhuadui/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadui/res/mipmap-xhdpi/ic_launcher.png b/src/zh/manhuadui/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 5ee44751b..000000000 Binary files a/src/zh/manhuadui/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadui/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/manhuadui/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e746e2e81..000000000 Binary files a/src/zh/manhuadui/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadui/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/manhuadui/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2ee8be13b..000000000 Binary files a/src/zh/manhuadui/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadui/res/web_hi_res_512.png b/src/zh/manhuadui/res/web_hi_res_512.png deleted file mode 100644 index 691f39d2e..000000000 Binary files a/src/zh/manhuadui/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/manhuadui/src/eu/kanade/tachiyomi/extension/zh/manhuadui/Manhuadui.kt b/src/zh/manhuadui/src/eu/kanade/tachiyomi/extension/zh/manhuadui/Manhuadui.kt deleted file mode 100644 index 09e94914a..000000000 --- a/src/zh/manhuadui/src/eu/kanade/tachiyomi/extension/zh/manhuadui/Manhuadui.kt +++ /dev/null @@ -1,193 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuadui - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class Manhuadui : ParsedHttpSource() { - - override val name = "漫画堆" - override val baseUrl = "https://ykmh.com" - override val lang = "zh" - override val supportsLatest = true - private val imageServer = arrayOf("https://pic.w1fl.com", "https://mhcdn.manhuazj.com", "https://res.333dm.com", "https://res02.333dm.com") - - override val client: OkHttpClient = super.client.newBuilder() - .followRedirects(true) - .build() - - override fun popularMangaSelector() = "li.list-comic" - override fun searchMangaSelector() = popularMangaSelector() - override fun latestUpdatesSelector() = popularMangaSelector() - override fun chapterListSelector() = "ul[id^=chapter-list] > li a" - - override fun searchMangaNextPageSelector() = "li.next" - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/list_$page/", headers) - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/update/$page/", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query != "") { - val url = HttpUrl.parse("$baseUrl/search/?keywords=$query")?.newBuilder() - GET(url.toString(), headers) - } else { - val params = filters.map { - if (it is UriPartFilter) { - it.toUriPart() - } else "" - }.filter { it != "" }.joinToString("-") - val url = HttpUrl.parse("$baseUrl/list/$params/$page/")?.newBuilder() - GET(url.toString(), headers) - } - } - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.comic_img").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.select("img").attr("alt").trim() - manga.thumbnail_url = if (it.select("img").attr("src").trim().indexOf("http") == -1) - "https:${it.select("img").attr("src").trim()}" - else it.select("img").attr("src").trim() - } - manga.author = element.select("span.comic_list_det > p").first()?.text()?.substring(3) - return manga - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - val els = element.select("a.image-link") - if (els.size == 0) { - element.select("li.list-comic").first().let { - manga.setUrlWithoutDomain(it.select("a").attr("href")) - manga.title = it.select("span").attr("title").trim() - manga.thumbnail_url = it.select("a > img").attr("src").trim() - manga.author = it.select("span > p").first().text().split(":")[1].trim() - } - } else { - element.select("a.image-link").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title").trim() - manga.thumbnail_url = it.select("img").attr("src").trim() - } - manga.author = element.select("p.auth").text().trim() - } - return manga - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.attr("href")) - chapter.name = element.select("span:nth-child(2)").text().trim() - return chapter - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.description = document.select("p.comic_deCon_d").text().trim() - manga.thumbnail_url = document.select("div.comic_i_img > img").attr("src") - return manga - } - - override fun chapterListRequest(manga: SManga) = GET(baseUrl.replace("www", "m") + manga.url) - override fun chapterListParse(response: Response): List<SChapter> { - return super.chapterListParse(response).asReversed() - } - - private val chapterImagesRegex = Regex("""var chapterImages =\s*\["(.*?)"\];""") - private val imgCodeCleanupRegex = Regex("""[\[\]"\\]""") - - override fun pageListParse(document: Document): List<Page> { - val html = document.html() - val imgCodeStr = chapterImagesRegex.find(html)?.groups?.get(1)?.value ?: throw Exception("imgCodeStr not found") - val imgCode = imgCodeStr - .replace(imgCodeCleanupRegex, "") - .replace("%", "%25") - return imgCode.split(",").mapIndexed { i, imgStr -> - if (imgStr.startsWith("http://images.dmzj.com")) { - Page(i, "", "https://img01.eshanyao.com/showImage.php?url=$imgStr") - } else { - Page(i, "", if (imgStr.indexOf("http") == -1) "${imageServer[0]}/$imgStr" else imgStr) - } - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList( - CategoryGroup(), - RegionGroup(), - GenreGroup(), - ProgressGroup() - ) - - private class CategoryGroup : UriPartFilter( - "按类型", - arrayOf( - Pair("全部", ""), - Pair("儿童漫画", "ertong"), - Pair("少年漫画", "shaonian"), - Pair("少女漫画", "shaonv"), - Pair("青年漫画", "qingnian") - ) - ) - - private class ProgressGroup : UriPartFilter( - "按进度", - arrayOf( - Pair("全部", ""), - Pair("已完结", "wanjie"), - Pair("连载中", "lianzai") - ) - ) - - private class RegionGroup : UriPartFilter( - "按地区", - arrayOf( - Pair("全部", ""), - Pair("日本", "riben"), - Pair("大陆", "dalu"), - Pair("香港", "hongkong"), - Pair("台湾", "taiwan"), - Pair("欧美", "oumei"), - Pair("韩国", "hanguo"), - Pair("其他", "qita") - ) - ) - - private class GenreGroup : UriPartFilter( - "按剧情", - arrayOf( - Pair("全部", ""), - Pair("热血", "rexue"), - Pair("冒险", "maoxian"), - Pair("玄幻", "xuanhuan"), - Pair("搞笑", "gaoxiao"), - Pair("恋爱", "lianai"), - Pair("宠物", "chongwu"), - Pair("新作", "xinzuo") - ) - ) - - private open class UriPartFilter( - displayName: String, - val vals: Array<Pair<String, String>>, - defaultValue: Int = 0 - ) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), defaultValue) { - open fun toUriPart() = vals[state].second - } -} diff --git a/src/zh/manhuagui/AndroidManifest.xml b/src/zh/manhuagui/AndroidManifest.xml deleted file mode 100644 index 43b623083..000000000 --- a/src/zh/manhuagui/AndroidManifest.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="eu.kanade.tachiyomi.extension"> - - <application> - <activity - android:name=".zh.manhuagui.ManhuaguiUrlActivity" - android:excludeFromRecents="true" - android:theme="@android:style/Theme.NoDisplay"> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - - <data - android:host="manhuagui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - <data - android:host="m.manhuagui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - <data - android:host="www.manhuagui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - <data - android:host="tw.manhuagui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - <data - android:host="mhgui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - <data - android:host="m.mhgui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - <data - android:host="www.mhgui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - <data - android:host="tw.mhgui.com" - android:pathPattern="/comic/..*" - android:scheme="https" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/src/zh/manhuagui/build.gradle b/src/zh/manhuagui/build.gradle deleted file mode 100644 index fa2bcb5ff..000000000 --- a/src/zh/manhuagui/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ManHuaGui' - pkgNameSuffix = 'zh.manhuagui' - extClass = '.Manhuagui' - extVersionCode = 8 - libVersion = '1.2' -} - -dependencies { - compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" - compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" - implementation project(':lib-ratelimit') -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/manhuagui/res/mipmap-hdpi/ic_launcher.png b/src/zh/manhuagui/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8ac7cf084..000000000 Binary files a/src/zh/manhuagui/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuagui/res/mipmap-mdpi/ic_launcher.png b/src/zh/manhuagui/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index de142e385..000000000 Binary files a/src/zh/manhuagui/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuagui/res/mipmap-xhdpi/ic_launcher.png b/src/zh/manhuagui/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 833f72b2b..000000000 Binary files a/src/zh/manhuagui/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuagui/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/manhuagui/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f68b0077e..000000000 Binary files a/src/zh/manhuagui/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuagui/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/manhuagui/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9728b739c..000000000 Binary files a/src/zh/manhuagui/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuagui/res/web_hi_res_512.png b/src/zh/manhuagui/res/web_hi_res_512.png deleted file mode 100644 index f506b05bb..000000000 Binary files a/src/zh/manhuagui/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Comic.kt b/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Comic.kt deleted file mode 100644 index 63718c67f..000000000 --- a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Comic.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuagui - -data class Comic( - val bid: Int? = 0, - val block_cc: String? = "", - val bname: String? = "", - val bpic: String? = "", - val cid: Int? = 0, - val cname: String? = "", - val files: List<String?>? = listOf(), - val finished: Boolean? = false, - val len: Int? = 0, - val nextId: Int? = 0, - val path: String? = "", - val prevId: Int? = 0, - val sl: Sl? = Sl(), - val status: Int? = 0 -) diff --git a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Manhuagui.kt b/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Manhuagui.kt deleted file mode 100644 index ccc29667c..000000000 --- a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Manhuagui.kt +++ /dev/null @@ -1,784 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuagui - -import android.app.Application -import android.content.SharedPreferences -import android.support.v7.preference.CheckBoxPreference -import android.support.v7.preference.ListPreference -import android.support.v7.preference.PreferenceScreen -import com.google.gson.Gson -import com.squareup.duktape.Duktape -import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okhttp3.Call -import okhttp3.Callback -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.IOException -import java.text.SimpleDateFormat -import java.util.Locale - -class Manhuagui : ConfigurableSource, ParsedHttpSource() { - - private val preferences: SharedPreferences by lazy { - Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) - } - - override val name = "漫画柜" - - private val baseHost = if (preferences.getBoolean(USE_MIRROR_URL_PREF, false)) { - "mhgui.com" - } else { - "manhuagui.com" - } - - override val baseUrl = - if (preferences.getBoolean(SHOW_ZH_HANT_WEBSITE_PREF, false)) - "https://tw.$baseHost" - else - "https://www.$baseHost" - override val lang = "zh" - override val supportsLatest = true - - private val imageServer = arrayOf("https://i.hamreus.com", "https://cf.hamreus.com") - private val mobileWebsiteUrl = "https://m.$baseHost" - private val gson = Gson() - private val baseHttpUrl: HttpUrl = HttpUrl.parse(baseUrl)!! - - // Add rate limit to fix manga thumbnail load failure - private val mainSiteRateLimitInterceptor = SpecificHostRateLimitInterceptor(baseHttpUrl, preferences.getString(MAINSITE_RATELIMIT_PREF, "2")!!.toInt()) - private val imageCDNRateLimitInterceptor1 = SpecificHostRateLimitInterceptor(HttpUrl.parse(imageServer[0])!!, preferences.getString(IMAGE_CDN_RATELIMIT_PREF, "4")!!.toInt()) - private val imageCDNRateLimitInterceptor2 = SpecificHostRateLimitInterceptor(HttpUrl.parse(imageServer[1])!!, preferences.getString(IMAGE_CDN_RATELIMIT_PREF, "4")!!.toInt()) - - override val client: OkHttpClient = - if (getShowR18()) - network.client.newBuilder() - .addNetworkInterceptor(mainSiteRateLimitInterceptor) - .addNetworkInterceptor(imageCDNRateLimitInterceptor1) - .addNetworkInterceptor(imageCDNRateLimitInterceptor2) - .addNetworkInterceptor(AddCookieHeaderInterceptor(baseHttpUrl.host()!!)) - .build() - else - network.client.newBuilder() - .addNetworkInterceptor(mainSiteRateLimitInterceptor) - .addNetworkInterceptor(imageCDNRateLimitInterceptor1) - .addNetworkInterceptor(imageCDNRateLimitInterceptor2) - .build() - - // Add R18 verification cookie - class AddCookieHeaderInterceptor(private val baseHost: String) : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - if (chain.request().url().host() == baseHost) { - val originalCookies = chain.request().header("Cookie") ?: "" - if (originalCookies != "" && !originalCookies.contains("isAdult=1")) { - return chain.proceed( - chain.request().newBuilder() - .header("Cookie", "$originalCookies; isAdult=1") - .build() - ) - } - } - return chain.proceed(chain.request()) - } - } - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/list/view_p$page.html", headers) - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/list/update_p$page.html", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query != "") { - // Normal search - return GET("$baseUrl/s/${query}_p$page.html", headers) - } else { - // Filters search - val params = filters.map { - if (it !is SortFilter && it is UriPartFilter) { - it.toUriPart() - } else "" - }.filter { it != "" }.joinToString("_") - - val sortOrder = filters.filterIsInstance<SortFilter>() - .joinToString("") { - (it as UriPartFilter).toUriPart() - } - - // Example: https://www.manhuagui.com/list/japan_maoxian_qingnian_2020_b/update_p1.html - // /$params /$sortOrder $page - var url = "$baseUrl/list" - if (params != "") { - url += "/$params" - } - if (sortOrder == "") { - url += "/index_p$page.html" - } else { - url += "/${sortOrder}_p$page.html" - } - return GET(url, headers) - } - } - - // Return mobile webpage url to "Open in browser" and "Share manga". - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(mobileWebsiteUrl + manga.url) - } - - // Bypass mangaDetailsRequest - override fun fetchMangaDetails(manga: SManga): Observable<SManga> { - val call = client.newCall(GET(baseUrl + manga.url, headers)) - val bid = Regex("""\d+""").find(manga.url)?.value - if (bid != null) { - // Send a get request to https://www.manhuagui.com/tools/vote.ashx?act=get&bid=$bid - // and a post request to https://www.manhuagui.com/tools/submit_ajax.ashx?action=user_check_login - // to simulate what web page javascript do and get "country" cookie. - // Send requests using coroutine in another (IO) thread. - GlobalScope.launch { - withContext(Dispatchers.IO) { - // Delay 1 second to wait main manga details request complete - delay(1000L) - client.newCall( - POST( - "$baseUrl/tools/submit_ajax.ashx?action=user_check_login", - headersBuilder() - .set("Referer", manga.url) - .set("X-Requested-With", "XMLHttpRequest") - .build() - ) - ).enqueue( - object : Callback { - override fun onFailure(call: Call, e: IOException) = e.printStackTrace() - override fun onResponse(call: Call, response: Response) = response.close() - } - ) - - client.newCall( - GET( - "$baseUrl/tools/vote.ashx?act=get&bid=$bid", - headersBuilder() - .set("Referer", manga.url) - .set("X-Requested-With", "XMLHttpRequest").build() - ) - ).enqueue( - object : Callback { - override fun onFailure(call: Call, e: IOException) = e.printStackTrace() - override fun onResponse(call: Call, response: Response) = response.close() - } - ) - } - } - } - return call - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - // For ManhuaguiUrlActivity - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/comic/$id", headers) - - private fun searchMangaByIdParse(response: Response, id: String): MangasPage { - val sManga = mangaDetailsParse(response) - sManga.url = "/comic/$id/" - return MangasPage(listOf(sManga), false) - } - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(searchMangaByIdRequest(id)) - .asObservableSuccess() - .map { response -> searchMangaByIdParse(response, id) } - } else { - super.fetchSearchManga(page, query, filters) - } - } - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - if (response.request().url().encodedPath().startsWith("/s/")) { - // Normal search - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - val hasNextPage = searchMangaNextPageSelector().let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } else { - // Filters search - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - val hasNextPage = document.select(popularMangaNextPageSelector()).first() != null - return MangasPage(mangas, hasNextPage) - } - } - - override fun popularMangaSelector() = "ul#contList > li" - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = "div.book-result > ul > li" - override fun chapterListSelector() = "ul > li > a.status0" - - override fun searchMangaNextPageSelector() = "span.current + a" // "a.prev" contain 2~4 elements: first, previous, next and last page, "span.current + a" is a better choice. - override fun popularMangaNextPageSelector() = searchMangaNextPageSelector() - override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector() - - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .set("Referer", baseUrl) - .set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36") - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.bcover").first().let { - manga.url = it.attr("href") - manga.title = it.attr("title").trim() - - // Fix thumbnail lazy load - val thumbnailElement = it.select("img").first() - manga.thumbnail_url = if (thumbnailElement.hasAttr("src")) - thumbnailElement.attr("abs:src") - else - thumbnailElement.attr("abs:data-src") - } - return manga - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - element.select("div.book-detail").first().let { - manga.url = it.select("dl > dt > a").first().attr("href") - manga.title = it.select("dl > dt > a").first().attr("title").trim() - manga.thumbnail_url = element.select("div.book-cover > a.bcover > img").first().attr("abs:src") - } - - return manga - } - - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used.") - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - - // Try to get R18 manga hidden chapter list - val hiddenEncryptedChapterList = document.select("#__VIEWSTATE").first() - if (hiddenEncryptedChapterList != null) { - if (getShowR18()) { - // Hidden chapter list is LZString encoded - val decodedHiddenChapterList = Duktape.create().use { - it.evaluate( - jsDecodeFunc + - """LZString.decompressFromBase64('${hiddenEncryptedChapterList.`val`()}');""" - ) as String - } - val hiddenChapterList = Jsoup.parse(decodedHiddenChapterList, response.request().url().toString()) - if (hiddenChapterList != null) { - // Replace R18 warning with actual chapter list - document.select("#erroraudit_show").first().replaceWith(hiddenChapterList) - // Remove hidden chapter list element - document.select("#__VIEWSTATE").first().remove() - } - } else { - // "You need to enable R18 switch and restart Tachiyomi to read this manga" - error("您需要打开R18作品显示开关并重启软件才能阅读此作品") - } - } - val chapterList = document.select("ul > li > a.status0") - val latestChapterHref = document.select("div.book-detail > ul.detail-list > li.status > span > a.blue").first()?.attr("href") - val chNumRegex = Regex("""\d+""") - chapterList.forEach { - val currentChapter = SChapter.create() - currentChapter.url = it.attr("href") - currentChapter.name = it?.attr("title")?.trim() ?: it.select("span").first().ownText() - currentChapter.chapter_number = chNumRegex.find(currentChapter.name)?.value?.toFloatOrNull() ?: -1F - - // Manhuagui only provide upload date for latest chapter - if (currentChapter.url == latestChapterHref) { - currentChapter.date_upload = parseDate(document.select("div.book-detail > ul.detail-list > li.status > span > span.red").last()) - } - chapters.add(currentChapter) - } - - return chapters.sortedByDescending { it.chapter_number } - } - - private fun parseDate(element: Element): Long = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).parse(element.text())?.time ?: 0 - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - /** - * When searching manga from intent filter, sometimes will cause the error below and manga don't appear in search result: - * eu.kanade.tachiyomi.debug E/GlobalSearchPresenter$search: kotlin.UninitializedPropertyAccessException: lateinit property title has not been initialized - * at eu.kanade.tachiyomi.source.model.SMangaImpl.getTitle(SMangaImpl.kt:7) - * at eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter.networkToLocalManga(GlobalSearchPresenter.kt:259) - * at eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter$search$1$4.call(GlobalSearchPresenter.kt:172) - * at eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter$search$1$4.call(GlobalSearchPresenter.kt:34) - * Parse manga.title here can solve it. - */ - manga.title = document.select("div.book-title > h1:nth-child(1)").text().trim() - manga.description = document.select("div#intro-all").text().trim() - manga.thumbnail_url = document.select("p.hcover > img").attr("abs:src") - manga.author = document.select("span:contains(漫画作者) > a , span:contains(漫畫作者) > a").text().trim().replace(" ", ", ") - manga.genre = document.select("span:contains(漫画剧情) > a , span:contains(漫畫劇情) > a").text().trim().replace(" ", ", ") - manga.status = when (document.select("div.book-detail > ul.detail-list > li.status > span > span").first().text()) { - "连载中" -> SManga.ONGOING - "已完结" -> SManga.COMPLETED - "連載中" -> SManga.ONGOING - "已完結" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - return manga - } - - private val jsDecodeFunc = - """ - var LZString=(function(){var f=String.fromCharCode;var keyStrBase64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var baseReverseDic={};function getBaseValue(alphabet,character){if(!baseReverseDic[alphabet]){baseReverseDic[alphabet]={};for(var i=0;i<alphabet.length;i++){baseReverseDic[alphabet][alphabet.charAt(i)]=i}}return baseReverseDic[alphabet][character]}var LZString={decompressFromBase64:function(input){if(input==null)return"";if(input=="")return null;return LZString._0(input.length,32,function(index){return getBaseValue(keyStrBase64,input.charAt(index))})},_0:function(length,resetValue,getNextValue){var dictionary=[],next,enlargeIn=4,dictSize=4,numBits=3,entry="",result=[],i,w,bits,resb,maxpower,power,c,data={val:getNextValue(0),position:resetValue,index:1};for(i=0;i<3;i+=1){dictionary[i]=i}bits=0;maxpower=Math.pow(2,2);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}switch(next=bits){case 0:bits=0;maxpower=Math.pow(2,8);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}c=f(bits);break;case 1:bits=0;maxpower=Math.pow(2,16);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}c=f(bits);break;case 2:return""}dictionary[3]=c;w=c;result.push(c);while(true){if(data.index>length){return""}bits=0;maxpower=Math.pow(2,numBits);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}switch(c=bits){case 0:bits=0;maxpower=Math.pow(2,8);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}dictionary[dictSize++]=f(bits);c=dictSize-1;enlargeIn--;break;case 1:bits=0;maxpower=Math.pow(2,16);power=1;while(power!=maxpower){resb=data.val&data.position;data.position>>=1;if(data.position==0){data.position=resetValue;data.val=getNextValue(data.index++)}bits|=(resb>0?1:0)*power;power<<=1}dictionary[dictSize++]=f(bits);c=dictSize-1;enlargeIn--;break;case 2:return result.join('')}if(enlargeIn==0){enlargeIn=Math.pow(2,numBits);numBits++}if(dictionary[c]){entry=dictionary[c]}else{if(c===dictSize){entry=w+w.charAt(0)}else{return null}}result.push(entry);dictionary[dictSize++]=w+entry.charAt(0);enlargeIn--;w=entry;if(enlargeIn==0){enlargeIn=Math.pow(2,numBits);numBits++}}}};return LZString})();String.prototype.splic=function(f){return LZString.decompressFromBase64(this).split(f)}; - """ - - // Page list is javascript eval encoded and LZString encoded, these website: - // http://www.oicqzone.com/tool/eval/ , https://www.w3xue.com/tools/jseval/ , - // https://www.w3cschool.cn/tools/index?name=evalencode can try to decode javascript eval encoded content, - // jsDecodeFunc's LZString.decompressFromBase64() can decode LZString. - - // These "\" can't be remove: "\}", more info in pull request 3926. - @Suppress("RegExpRedundantEscape") - private val re = Regex("""window\[".*?"\](\(.*\)\s*\{[\s\S]+\}\s*\(.*\))""") - - @Suppress("RegExpRedundantEscape") - private val re2 = Regex("""\{.*\}""") - - override fun pageListParse(document: Document): List<Page> { - // R18 warning element (#erroraudit_show) is remove by web page javascript, so here the warning element - // will always exist if this manga is R18 limited whether R18 verification cookies has been sent or not. - // But it will not interfere parse mechanism below. - if (document.select("#erroraudit_show").first() != null && !getShowR18()) - error("R18作品显示开关未开启或未生效") // "R18 setting didn't enabled or became effective" - - val html = document.html() - val imgCode = re.find(html)?.groups?.get(1)?.value - val imgDecode = Duktape.create().use { - it.evaluate(jsDecodeFunc + imgCode) as String - } - - val imgJsonStr = re2.find(imgDecode)?.groups?.get(0)?.value - val imageJson: Comic = gson.fromJson(imgJsonStr, Comic::class.java) - - return imageJson.files!!.mapIndexed { i, imgStr -> - val imgurl = "${imageServer[0]}${imageJson.path}$imgStr?cid=${imageJson.cid}&md5=${imageJson.sl?.md5}" - Page(i, "", imgurl) - } - } - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val mainSiteRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = MAINSITE_RATELIMIT_PREF - title = MAINSITE_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = MAINSITE_RATELIMIT_PREF_SUMMARY - - setDefaultValue("2") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(MAINSITE_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - - setDefaultValue("4") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - // Simplified/Traditional Chinese version website switch - val zhHantPreference = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SHOW_ZH_HANT_WEBSITE_PREF - title = SHOW_ZH_HANT_WEBSITE_PREF_TITLE - summary = SHOW_ZH_HANT_WEBSITE_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putBoolean(SHOW_ZH_HANT_WEBSITE_PREF, newValue as Boolean).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - // R18+ switch - val r18Preference = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SHOW_R18_PREF - title = SHOW_R18_PREF_TITLE - summary = SHOW_R18_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val newSetting = preferences.edit().putBoolean(SHOW_R18_PREF, newValue as Boolean).commit() - newSetting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val mirrorURLPreference = androidx.preference.CheckBoxPreference(screen.context).apply { - key = USE_MIRROR_URL_PREF - title = USE_MIRROR_URL_PREF_TITLE - summary = USE_MIRROR_URL_PREF_SUMMARY - - setDefaultValue(false) - setOnPreferenceChangeListener { _, newValue -> - try { - val newSetting = preferences.edit().putBoolean(USE_MIRROR_URL_PREF, newValue as Boolean).commit() - newSetting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(mainSiteRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - screen.addPreference(zhHantPreference) - screen.addPreference(r18Preference) - screen.addPreference(mirrorURLPreference) - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val mainSiteRateLimitPreference = ListPreference(screen.context).apply { - key = MAINSITE_RATELIMIT_PREF - title = MAINSITE_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = MAINSITE_RATELIMIT_PREF_SUMMARY - - setDefaultValue("2") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(MAINSITE_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - - setDefaultValue("4") - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val zhHantPreference = CheckBoxPreference(screen.context).apply { - key = SHOW_ZH_HANT_WEBSITE_PREF - title = SHOW_ZH_HANT_WEBSITE_PREF_TITLE - summary = SHOW_ZH_HANT_WEBSITE_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putBoolean(SHOW_ZH_HANT_WEBSITE_PREF, newValue as Boolean).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val r18Preference = CheckBoxPreference(screen.context).apply { - key = SHOW_R18_PREF - title = SHOW_R18_PREF_TITLE - summary = SHOW_R18_PREF_SUMMARY - - setOnPreferenceChangeListener { _, newValue -> - try { - val newSetting = preferences.edit().putBoolean(SHOW_R18_PREF, newValue as Boolean).commit() - newSetting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val mirrorURLPreference = CheckBoxPreference(screen.context).apply { - key = USE_MIRROR_URL_PREF - title = USE_MIRROR_URL_PREF_TITLE - summary = USE_MIRROR_URL_PREF_SUMMARY - - setDefaultValue(false) - setOnPreferenceChangeListener { _, newValue -> - try { - val newSetting = preferences.edit().putBoolean(USE_MIRROR_URL_PREF, newValue as Boolean).commit() - newSetting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(mainSiteRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - screen.addPreference(zhHantPreference) - screen.addPreference(r18Preference) - screen.addPreference(mirrorURLPreference) - } - - private fun getShowR18(): Boolean = preferences.getBoolean(SHOW_R18_PREF, false) - - private open class UriPartFilter( - displayName: String, - val pair: Array<Pair<String, String>>, - defaultState: Int = 0 - ) : Filter.Select<String>(displayName, pair.map { it.first }.toTypedArray(), defaultState) { - open fun toUriPart() = pair[state].second - } - - override fun getFilterList() = FilterList( - SortFilter(), - LocaleFilter(), - GenreFilter(), - ReaderFilter(), - PublishDateFilter(), - FirstLetterFilter(), - StatusFilter() - ) - - private class SortFilter : UriPartFilter( - "排序方式", - arrayOf( - Pair("人气最旺", "view"), // Same to popularMangaRequest() - Pair("最新发布", ""), // Publish date - Pair("最新更新", "update"), - Pair("评分最高", "rate") - ) - ) - - private class LocaleFilter : UriPartFilter( - "按地区", - arrayOf( - Pair("全部", ""), // all - Pair("日本", "japan"), - Pair("港台", "hongkong"), - Pair("其它", "other"), - Pair("欧美", "europe"), - Pair("内地", "china"), - Pair("韩国", "korea") - ) - ) - - private class GenreFilter : UriPartFilter( - "按剧情", - arrayOf( - Pair("全部", ""), - Pair("热血", "rexue"), - Pair("冒险", "maoxian"), - Pair("魔幻", "mohuan"), - Pair("神鬼", "shengui"), - Pair("搞笑", "gaoxiao"), - Pair("萌系", "mengxi"), - Pair("爱情", "aiqing"), - Pair("科幻", "kehuan"), - Pair("魔法", "mofa"), - Pair("格斗", "gedou"), - Pair("武侠", "wuxia"), - Pair("机战", "jizhan"), - Pair("战争", "zhanzheng"), - Pair("竞技", "jingji"), - Pair("体育", "tiyu"), - Pair("校园", "xiaoyuan"), - Pair("生活", "shenghuo"), - Pair("励志", "lizhi"), - Pair("历史", "lishi"), - Pair("伪娘", "weiniang"), - Pair("宅男", "zhainan"), - Pair("腐女", "funv"), - Pair("耽美", "danmei"), - Pair("百合", "baihe"), - Pair("后宫", "hougong"), - Pair("治愈", "zhiyu"), - Pair("美食", "meishi"), - Pair("推理", "tuili"), - Pair("悬疑", "xuanyi"), - Pair("恐怖", "kongbu"), - Pair("四格", "sige"), - Pair("职场", "zhichang"), - Pair("侦探", "zhentan"), - Pair("社会", "shehui"), - Pair("音乐", "yinyue"), - Pair("舞蹈", "wudao"), - Pair("杂志", "zazhi"), - Pair("黑道", "heidao") - ) - ) - - private class ReaderFilter : UriPartFilter( - "按受众", - arrayOf( - Pair("全部", ""), - Pair("少女", "shaonv"), - Pair("少年", "shaonian"), - Pair("青年", "qingnian"), - Pair("儿童", "ertong"), - Pair("通用", "tongyong"), - ) - ) - - private class PublishDateFilter : UriPartFilter( - "按年份", - arrayOf( - Pair("全部", ""), - Pair("2020年", "2020"), - Pair("2019年", "2019"), - Pair("2018年", "2018"), - Pair("2017年", "2017"), - Pair("2016年", "2016"), - Pair("2015年", "2015"), - Pair("2014年", "2014"), - Pair("2013年", "2013"), - Pair("2012年", "2012"), - Pair("2011年", "2011"), - Pair("2010年", "2010"), - Pair("00年代", "200x"), - Pair("90年代", "199x"), - Pair("80年代", "198x"), - Pair("更早", "197x"), - ) - ) - - private class FirstLetterFilter : UriPartFilter( - "按字母", - arrayOf( - Pair("全部", ""), - Pair("A", "a"), - Pair("B", "b"), - Pair("C", "c"), - Pair("D", "d"), - Pair("E", "e"), - Pair("F", "f"), - Pair("G", "g"), - Pair("H", "h"), - Pair("I", "i"), - Pair("J", "j"), - Pair("K", "k"), - Pair("L", "l"), - Pair("M", "m"), - Pair("N", "n"), - Pair("O", "o"), - Pair("P", "p"), - Pair("Q", "q"), - Pair("R", "r"), - Pair("S", "s"), - Pair("T", "t"), - Pair("U", "u"), - Pair("V", "v"), - Pair("W", "w"), - Pair("X", "x"), - Pair("Y", "y"), - Pair("Z", "z"), - Pair("0-9", "0-9") - ) - ) - - private class StatusFilter : UriPartFilter( - "按进度", - arrayOf( - Pair("全部", ""), - Pair("连载", "lianzai"), - Pair("完结", "wanjie"), - ) - ) - - companion object { - private const val SHOW_R18_PREF = "showR18Default" - private const val SHOW_R18_PREF_TITLE = "显示R18作品" // "Show R18 contents" - private const val SHOW_R18_PREF_SUMMARY = "请确认您的IP不在漫画柜的屏蔽列表内,例如中国大陆IP。需要重启软件以生效。\n开启后如需关闭,需要到Tachiyomi高级设置内清除Cookies后才能生效。" // "Please make sure your IP is not in Manhuagui's ban list, e.g., China mainland IP. Tachiyomi restart required. If you want to close this switch after enabled it, you need to clear cookies in Tachiyomi advanced setting too. - - private const val SHOW_ZH_HANT_WEBSITE_PREF = "showZhHantWebsite" - private const val SHOW_ZH_HANT_WEBSITE_PREF_TITLE = "使用繁体版网站" // "Use traditional chinese version website" - private const val SHOW_ZH_HANT_WEBSITE_PREF_SUMMARY = "需要重启软件以生效。" // "You need to restart Tachiyomi" - - private const val USE_MIRROR_URL_PREF = "useMirrorWebsitePreference" - private const val USE_MIRROR_URL_PREF_TITLE = "使用镜像网址" - private const val USE_MIRROR_URL_PREF_SUMMARY = "使用镜像网址: mhgui.com,部分漫画可能无法观看。" // "Use mirror url. Some manga may be hidden." - - private const val MAINSITE_RATELIMIT_PREF = "mainSiteRatelimitPreference" - private const val MAINSITE_RATELIMIT_PREF_TITLE = "主站每秒连接数限制" // "Ratelimit permits per second for main website" - private const val MAINSITE_RATELIMIT_PREF_SUMMARY = "此值影响更新书架时发起连接请求的数量。调低此值可能减小IP被屏蔽的几率,但加载速度也会变慢。需要重启软件以生效。\n当前值:%s" // "This value affects network request amount for updating library. Lower this value may reduce the chance to get IP Ban, but loading speed will be slower too. Tachiyomi restart required." - - private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference" - private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "图片CDN每秒连接数限制" // "Ratelimit permits per second for image CDN" - private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "此值影响加载图片时发起连接请求的数量。调低此值可能减小IP被屏蔽的几率,但加载速度也会变慢。需要重启软件以生效。\n当前值:%s" // "This value affects network request amount for loading image. Lower this value may reduce the chance to get IP Ban, but loading speed will be slower too. Tachiyomi restart required." - - private val ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray() - const val PREFIX_ID_SEARCH = "id:" - } -} diff --git a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/ManhuaguiUrlActivity.kt b/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/ManhuaguiUrlActivity.kt deleted file mode 100644 index 53bf2e74a..000000000 --- a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/ManhuaguiUrlActivity.kt +++ /dev/null @@ -1,41 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuagui - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://www.manhuagui.com/comic/xxx intents and redirects them to - * the main tachiyomi process. The idea is to not install the intent filter unless - * you have this extension installed, but still let the main tachiyomi app control - * things. - */ -class ManhuaguiUrlActivity : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size > 1) { - val titleid = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${Manhuagui.PREFIX_ID_SEARCH}$titleid") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("ManhuaguiUrlActivity", e.toString()) - } - } else { - Log.e("ManhuaguiUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Sl.kt b/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Sl.kt deleted file mode 100644 index 60c67d5f8..000000000 --- a/src/zh/manhuagui/src/eu/kanade/tachiyomi/extension/zh/manhuagui/Sl.kt +++ /dev/null @@ -1,5 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuagui - -data class Sl( - val md5: String? = "" -) diff --git a/src/zh/manhuaren/AndroidManifest.xml b/src/zh/manhuaren/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/manhuaren/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/manhuaren/build.gradle b/src/zh/manhuaren/build.gradle deleted file mode 100644 index 44ad6272c..000000000 --- a/src/zh/manhuaren/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Manhuaren' - pkgNameSuffix = 'zh.manhuaren' - extClass = '.Manhuaren' - extVersionCode = 7 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/manhuaren/res/mipmap-hdpi/ic_launcher.png b/src/zh/manhuaren/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index ab01d0392..000000000 Binary files a/src/zh/manhuaren/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuaren/res/mipmap-mdpi/ic_launcher.png b/src/zh/manhuaren/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c3160fd3d..000000000 Binary files a/src/zh/manhuaren/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuaren/res/mipmap-xhdpi/ic_launcher.png b/src/zh/manhuaren/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8eb837787..000000000 Binary files a/src/zh/manhuaren/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuaren/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/manhuaren/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index dee5710ef..000000000 Binary files a/src/zh/manhuaren/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuaren/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/manhuaren/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3a9f4d884..000000000 Binary files a/src/zh/manhuaren/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuaren/res/web_hi_res_512.png b/src/zh/manhuaren/res/web_hi_res_512.png deleted file mode 100644 index 3a9f4d884..000000000 Binary files a/src/zh/manhuaren/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt b/src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt deleted file mode 100644 index 683e20712..000000000 --- a/src/zh/manhuaren/src/eu/kanade/tachiyomi/extension/zh/manhuaren/Manhuaren.kt +++ /dev/null @@ -1,369 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuaren - -import android.text.format.DateFormat -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import okhttp3.CacheControl -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject -import java.net.URLEncoder -import java.security.MessageDigest -import java.text.SimpleDateFormat -import java.util.ArrayList -import java.util.Date -import java.util.Locale -import java.util.concurrent.TimeUnit.MINUTES - -class Manhuaren : HttpSource() { - override val lang = "zh" - override val supportsLatest = true - override val name = "漫画人" - override val baseUrl = "http://mangaapi.manhuaren.com" - - private val pageSize = 20 - private val baseHttpUrl = HttpUrl.parse(baseUrl)!! - - private val c = "4e0a48e1c0b54041bce9c8f0e036124d" - private val cacheControl: CacheControl by lazy { CacheControl.Builder().maxAge(10, MINUTES).build() } - - private fun generateGSNHash(url: HttpUrl): String { - var s = c + "GET" - url.queryParameterNames().toSortedSet().forEach { - if (it != "gsn") { - s += it - s += urlEncode(url.queryParameterValues(it)[0]) - } - } - s += c - return hashString("MD5", s) - } - - private fun myGet(url: HttpUrl): Request { - val now = DateFormat.format("yyyy-MM-dd+HH:mm:ss", Date()).toString() - val realUrl = url.newBuilder() - .setQueryParameter("gsm", "md5") - .setQueryParameter("gft", "json") - .setQueryParameter("gts", now) - .setQueryParameter("gak", "android_manhuaren2") - .setQueryParameter("gat", "") - .setQueryParameter("gaui", "191909801") - .setQueryParameter("gui", "191909801") - .setQueryParameter("gut", "0") - return Request.Builder() - .url(realUrl.setQueryParameter("gsn", generateGSNHash(realUrl.build())).build()) - .headers(headers) - .cacheControl(cacheControl) - .build() - } - - override fun headersBuilder() = Headers.Builder().apply { - add("X-Yq-Yqci", "{\"le\": \"zh\"}") - add("User-Agent", "okhttp/3.11.0") - add("Referer", "http://www.dm5.com/dm5api/") - add("clubReferer", "http://mangaapi.manhuaren.com/") - } - - private fun hashString(type: String, input: String): String { - val hexChars = "0123456789abcdef" - val bytes = MessageDigest - .getInstance(type) - .digest(input.toByteArray()) - val result = StringBuilder(bytes.size * 2) - - bytes.forEach { - val i = it.toInt() - result.append(hexChars[i shr 4 and 0x0f]) - result.append(hexChars[i and 0x0f]) - } - - return result.toString() - } - - private fun urlEncode(str: String?): String { - return URLEncoder.encode(str, "UTF-8") - .replace("+", "%20") - .replace("%7E", "~") - .replace("*", "%2A") - } - - private fun mangasFromJSONArray(arr: JSONArray): MangasPage { - val ret = ArrayList<SManga>(arr.length()) - for (i in 0 until arr.length()) { - val obj = arr.getJSONObject(i) - val id = obj.getInt("mangaId") - ret.add( - SManga.create().apply { - title = obj.getString("mangaName") - thumbnail_url = obj.getString("mangaCoverimageUrl") - author = obj.optString("mangaAuthor") - status = when (obj.getInt("mangaIsOver")) { - 1 -> SManga.COMPLETED - 0 -> SManga.ONGOING - else -> SManga.UNKNOWN - } - url = "/v1/manga/getDetail?mangaId=$id" - } - ) - } - return MangasPage(ret, arr.length() != 0) - } - - private fun mangasPageParse(response: Response): MangasPage { - val res = response.body()!!.string() - val arr = JSONObject(res).getJSONObject("response").getJSONArray("mangas") - return mangasFromJSONArray(arr) - } - - override fun popularMangaRequest(page: Int): Request { - val url = baseHttpUrl.newBuilder() - .addQueryParameter("subCategoryType", "0") - .addQueryParameter("subCategoryId", "0") - .addQueryParameter("start", (pageSize * (page - 1)).toString()) - .addQueryParameter("limit", pageSize.toString()) - .addQueryParameter("sort", "0") - .addPathSegments("/v2/manga/getCategoryMangas") - .build() - return myGet(url) - } - - override fun latestUpdatesRequest(page: Int): Request { - val url = baseHttpUrl.newBuilder() - .addQueryParameter("subCategoryType", "0") - .addQueryParameter("subCategoryId", "0") - .addQueryParameter("start", (pageSize * (page - 1)).toString()) - .addQueryParameter("limit", pageSize.toString()) - .addQueryParameter("sort", "1") - .addPathSegments("/v2/manga/getCategoryMangas") - .build() - return myGet(url) - } - - override fun popularMangaParse(response: Response): MangasPage { - return mangasPageParse(response) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - return mangasPageParse(response) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - var url = baseHttpUrl.newBuilder() - .addQueryParameter("start", (pageSize * (page - 1)).toString()) - .addQueryParameter("limit", pageSize.toString()) - if (query != "") { - url = url.addQueryParameter("keywords", query) - .addPathSegments("/v1/search/getSearchManga") - return myGet(url.build()) - } - filters.forEach { filter -> - when (filter) { - is SortFilter -> url = url.setQueryParameter("sort", filter.getId()) - is CategoryFilter -> { - url = url.setQueryParameter("subCategoryId", filter.getId()) - .setQueryParameter("subCategoryType", filter.getType()) - } - } - } - url = url.addPathSegments("/v2/manga/getCategoryMangas") - return myGet(url.build()) - } - - override fun searchMangaParse(response: Response): MangasPage { - val res = response.body()!!.string() - val obj = JSONObject(res).getJSONObject("response") - if (obj.has("result")) { - return mangasFromJSONArray(obj.getJSONArray("result")) - } - return mangasFromJSONArray(obj.getJSONArray("mangas")) - } - - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val res = response.body()!!.string() - val obj = JSONObject(res).getJSONObject("response") - title = obj.getString("mangaName") - thumbnail_url = "" - obj.optString("mangaCoverimageUrl").let { - if (it != "") { thumbnail_url = it } - } - if (thumbnail_url == "" || thumbnail_url == "http://mhfm5.tel.cdndm5.com/tag/category/nopic.jpg") { - obj.optString("mangaPicimageUrl").let { - if (it != "") { thumbnail_url = it } - } - } - if (thumbnail_url == "") { - obj.optString("shareIcon").let { - if (it != "") { thumbnail_url = it } - } - } - - val arr = obj.getJSONArray("mangaAuthors") - val tmparr = ArrayList<String>(arr.length()) - for (i in 0 until arr.length()) { - tmparr.add(arr.getString(i)) - } - author = tmparr.joinToString(", ") - - genre = obj.getString("mangaTheme").replace(" ", ", ") - - status = when (obj.getInt("mangaIsOver")) { - 1 -> SManga.COMPLETED - 0 -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - description = obj.getString("mangaIntro") - } - - override fun mangaDetailsRequest(manga: SManga): Request { - return myGet(HttpUrl.parse(baseUrl + manga.url)!!) - } - - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - - private fun getChapterName(type: String, name: String, title: String): String { - return (if (type == "mangaEpisode") "[番外] " else "") + name + (if (title == "") "" else ": $title") - } - - private fun chaptersFromJSONArray(type: String, arr: JSONArray): List<SChapter> { - val ret = ArrayList<SChapter>() - for (i in 0 until arr.length()) { - val obj = arr.getJSONObject(i) - ret.add( - SChapter.create().apply { - val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) - name = if (obj.getInt("isMustPay") == 1) { "(锁) " } else { "" } + getChapterName(type, obj.getString("sectionName"), obj.getString("sectionTitle")) - date_upload = dateFormat.parse(obj.getString("releaseTime"))?.time ?: 0L - chapter_number = obj.getInt("sectionSort").toFloat() - url = "/v1/manga/getRead?mangaSectionId=${obj.getInt("sectionId")}" - } - ) - } - return ret - } - - override fun chapterListParse(response: Response): List<SChapter> { - val res = response.body()!!.string() - val obj = JSONObject(res).getJSONObject("response") - val ret = ArrayList<SChapter>() - listOf("mangaEpisode", "mangaWords", "mangaRolls").forEach { - if (obj.has(it)) { - ret.addAll(chaptersFromJSONArray(it, obj.getJSONArray(it))) - } - } - return ret - } - - override fun pageListParse(response: Response): List<Page> { - val res = response.body()!!.string() - val obj = JSONObject(res).getJSONObject("response") - val ret = ArrayList<Page>() - val host = obj.getJSONArray("hostList").getString(0) - val arr = obj.getJSONArray("mangaSectionImages") - val query = obj.getString("query") - for (i in 0 until arr.length()) { - ret.add(Page(i, "$host${arr.getString(i)}$query", "$host${arr.getString(i)}$query")) - } - return ret - } - - override fun pageListRequest(chapter: SChapter): Request { - val url = HttpUrl.parse(baseUrl + chapter.url)!!.newBuilder() - .addQueryParameter("netType", "4") - .addQueryParameter("loadreal", "1") - .addQueryParameter("imageQuality", "2") - .build() - return myGet(url) - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!") - - override fun getFilterList() = FilterList( - SortFilter( - "状态", - arrayOf( - Pair("热门", "0"), - Pair("更新", "1"), - Pair("新作", "2"), - Pair("完结", "3") - ) - ), - CategoryFilter( - "分类", - arrayOf( - Category("全部", "0", "0"), - Category("热血", "0", "31"), - Category("恋爱", "0", "26"), - Category("校园", "0", "1"), - Category("百合", "0", "3"), - Category("耽美", "0", "27"), - Category("伪娘", "0", "5"), - Category("冒险", "0", "2"), - Category("职场", "0", "6"), - Category("后宫", "0", "8"), - Category("治愈", "0", "9"), - Category("科幻", "0", "25"), - Category("励志", "0", "10"), - Category("生活", "0", "11"), - Category("战争", "0", "12"), - Category("悬疑", "0", "17"), - Category("推理", "0", "33"), - Category("搞笑", "0", "37"), - Category("奇幻", "0", "14"), - Category("魔法", "0", "15"), - Category("恐怖", "0", "29"), - Category("神鬼", "0", "20"), - Category("萌系", "0", "21"), - Category("历史", "0", "4"), - Category("美食", "0", "7"), - Category("同人", "0", "30"), - Category("运动", "0", "34"), - Category("绅士", "0", "36"), - Category("机甲", "0", "40"), - Category("限制级", "0", "61"), - Category("少年向", "1", "1"), - Category("少女向", "1", "2"), - Category("青年向", "1", "3"), - Category("港台", "2", "35"), - Category("日韩", "2", "36"), - Category("大陆", "2", "37"), - Category("欧美", "2", "52") - ) - ) - ) - - private data class Category(val name: String, val type: String, val id: String) - - private class SortFilter( - name: String, - val vals: Array<Pair<String, String>>, - state: Int = 0 - ) : Filter.Select<String>( - name, - vals.map { it.first }.toTypedArray(), - state - ) { - fun getId() = vals[state].second - } - - private class CategoryFilter( - name: String, - val vals: Array<Category>, - state: Int = 0 - ) : Filter.Select<String>( - name, - vals.map { it.name }.toTypedArray(), - state - ) { - fun getId() = vals[state].id - fun getType() = vals[state].type - } -} diff --git a/src/zh/onemanhua/AndroidManifest.xml b/src/zh/onemanhua/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/onemanhua/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/onemanhua/build.gradle b/src/zh/onemanhua/build.gradle deleted file mode 100644 index d72ba634d..000000000 --- a/src/zh/onemanhua/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'CoCoManhua (OhManhua)' - pkgNameSuffix = 'zh.onemanhua' - extClass = '.Onemanhua' - extVersionCode = 7 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/onemanhua/res/mipmap-hdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 31a389a2e..000000000 Binary files a/src/zh/onemanhua/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/onemanhua/res/mipmap-mdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 71aa63a34..000000000 Binary files a/src/zh/onemanhua/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/onemanhua/res/mipmap-xhdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ea719834d..000000000 Binary files a/src/zh/onemanhua/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/onemanhua/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 10db12f6a..000000000 Binary files a/src/zh/onemanhua/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/onemanhua/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0b2ce14c7..000000000 Binary files a/src/zh/onemanhua/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/onemanhua/res/web_hi_res_512.png b/src/zh/onemanhua/res/web_hi_res_512.png deleted file mode 100644 index 461735204..000000000 Binary files a/src/zh/onemanhua/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/Onemanhua.kt b/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/Onemanhua.kt deleted file mode 100644 index 5e3d5b876..000000000 --- a/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/Onemanhua.kt +++ /dev/null @@ -1,315 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.onemanhua - -import android.annotation.SuppressLint -import android.net.Uri -import android.util.Base64 -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.regex.Pattern -import javax.crypto.Cipher -import javax.crypto.spec.SecretKeySpec - -// Originally, the site was called One漫画. The name has been changing every once in awhile -class Onemanhua : ParsedHttpSource() { - override val id = 8252565807829914103 // name used to be "One漫画" - override val lang = "zh" - override val supportsLatest = true - override val name = "COCO漫画 (OH漫画)" - override val baseUrl = "https://www.cocomanhua.com/" - - // Prepend with new decrypt keys (latest keys should appear at the start of the array) - private var decryptKey1Arr = arrayOf("fw122587mkertyui", "fw12558899ertyui") - private var decryptKey2Arr = arrayOf("fw125gjdi9ertyui") - - // Common - private var commonSelector = "li.fed-list-item" - private var commonNextPageSelector = "a:contains(下页):not(.fed-btns-disad)" - private fun commonMangaFromElement(element: Element): SManga { - val picElement = element.select("a.fed-list-pics").first() - val manga = SManga.create().apply { - title = element.select("a.fed-list-title").first().text() - thumbnail_url = picElement.attr("data-original") - } - - manga.setUrlWithoutDomain(picElement.attr("href")) - - return manga - } - - // Popular Manga - override fun popularMangaRequest(page: Int) = GET("$baseUrl/show?orderBy=dailyCount&page=$page", headers) - override fun popularMangaNextPageSelector() = commonNextPageSelector - override fun popularMangaSelector() = commonSelector - override fun popularMangaFromElement(element: Element) = commonMangaFromElement(element) - - // Latest Updates - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/show?orderBy=update&page=$page", headers) - override fun latestUpdatesNextPageSelector() = commonNextPageSelector - override fun latestUpdatesSelector() = commonSelector - override fun latestUpdatesFromElement(element: Element) = commonMangaFromElement(element) - - // Filter - private class StatusFilter : Filter.TriState("已完结") - private class SortFilter : Filter.Select<String>("排序", arrayOf("更新日", "收录日", "日点击", "月点击"), 2) - override fun getFilterList() = FilterList( - SortFilter(), - StatusFilter() - ) - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank()) { - GET("$baseUrl/search?searchString=$query&page=$page", headers) - } else { - val url = HttpUrl.parse("$baseUrl/show")!!.newBuilder() - url.addQueryParameter("page", page.toString()) - - filters.forEach { filter -> - when (filter) { - is StatusFilter -> { - if (!filter.isIgnored()) { - url.addQueryParameter("status", arrayOf("0", "2", "1")[filter.state]) - } - } - is SortFilter -> { - url.addQueryParameter("orderBy", arrayOf("update", "create", "dailyCount", "weeklyCount", "monthlyCount")[filter.state]) - } - } - } - GET(url.toString(), headers) - } - } - override fun searchMangaNextPageSelector() = commonNextPageSelector - override fun searchMangaSelector() = "dl.fed-deta-info, $commonSelector" - override fun searchMangaFromElement(element: Element): SManga { - if (element.tagName() == "li") { - return commonMangaFromElement(element) - } - - val picElement = element.select("a.fed-list-pics").first() - val manga = SManga.create().apply { - title = element.select("h1.fed-part-eone a").first().text() - thumbnail_url = picElement.attr("data-original") - } - - manga.setUrlWithoutDomain(picElement.attr("href")) - - return manga - } - - override fun mangaDetailsParse(document: Document): SManga { - val picElement = document.select("a.fed-list-pics").first() - val detailElements = document.select("ul.fed-part-rows li.fed-col-xs12") - return SManga.create().apply { - title = document.select("h1.fed-part-eone").first().text().trim() - thumbnail_url = picElement.attr("data-original") - - status = when ( - detailElements.firstOrNull { - it.children().firstOrNull { - it2 -> - it2.hasClass("fed-text-muted") && it2.ownText() == "状态" - } != null - }?.select("a")?.first()?.text() - ) { - "连载中" -> SManga.ONGOING - "已完结" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - author = detailElements.firstOrNull { - it.children().firstOrNull { - it2 -> - it2.hasClass("fed-text-muted") && it2.ownText() == "作者" - } != null - }?.select("a")?.first()?.text() - - genre = detailElements.firstOrNull { - it.children().firstOrNull { - it2 -> - it2.hasClass("fed-text-muted") && it2.ownText() == "类别" - } != null - }?.select("a")?.joinToString { it.text() } - - description = document.select("ul.fed-part-rows li.fed-col-xs12.fed-show-md-block .fed-part-esan") - .firstOrNull()?.text()?.trim() - } - } - - override fun chapterListSelector(): String = "div:not(.fed-hidden) > div.all_data_list > ul.fed-part-rows a" - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create().apply { - name = element.attr("title") - } - chapter.setUrlWithoutDomain(element.attr("href")) - return chapter - } - - override fun imageUrlParse(document: Document) = "" - - override fun pageListParse(document: Document): List<Page> { - // 1. get C_DATA from HTML - val encodedData = getEncodedMangaData(document) - // 2. decrypt C_DATA - val decryptedData = decodeAndDecrypt("encodedData", encodedData, decryptKey1Arr) - - // 3. Extract values from C_DATA to formulate page urls - val imgType = regexExtractStringValue( - decryptedData, - "img_type:\"(.+?)\"", - "Unable to match for img_type" - ) - - return if (imgType.isEmpty()) { - processPagesFromExternal(decryptedData) - } else { - processPagesFromInternal(decryptedData) - } - } - - private fun processPagesFromExternal(decryptedData: String): List<Page> { - val encodedUrlsDirect = regexExtractStringValue( - decryptedData, - "urls__direct:\"(.+?)\"", - "Unable to match for urls__direct" - ) - - val decodedUrlsDirect = String(Base64.decode(encodedUrlsDirect, Base64.NO_WRAP)) - val pageUrlArr = decodedUrlsDirect.split("|SEPARATER|") - - if (pageUrlArr.isNotEmpty()) { - throw Error("Here ${pageUrlArr[0]} and2 $decodedUrlsDirect") - } - - return mutableListOf<Page>().apply { - for (i in pageUrlArr.indices) { - add(Page(i + 1, "", pageUrlArr[i])) - } - } - } - - private fun processPagesFromInternal(decryptedData: String): List<Page> { - val imageServerDomain = regexExtractStringValue( - decryptedData, - "domain:\"(.+?)\"", - "Unable to match for imageServerDomain" - ) - val startImg = regexExtractStringValue( - decryptedData, - "startimg:([0-9]+?),", - "Unable to match for startimg" - ).let { Integer.parseInt(it) } - - // Decode and decrypt relative path - val encodedRelativePath = regexExtractStringValue( - decryptedData, - "enc_code2:\"(.+?)\"", - "Unable to match for enc_code2" - ) - val decryptedRelativePath = decodeAndDecrypt("encodedRelativePath", encodedRelativePath, decryptKey2Arr) - - // Decode and decrypt total pages - val encodedTotalPages = regexExtractStringValue( - decryptedData, - "enc_code1:\"(.+?)\"", - "Unable to match for enc_code1" - ) - val decryptedTotalPages = Integer.parseInt(decodeAndDecrypt("encodedTotalPages", encodedTotalPages, decryptKey1Arr)) - - return mutableListOf<Page>().apply { - for (i in startImg..decryptedTotalPages) { - add(Page(i, "", "https://$imageServerDomain/comic/${encodeUri(decryptedRelativePath)}${"%04d".format(i)}.jpg")) - } - } - } - - private fun getEncodedMangaData(document: Document): String { - val scriptElements = document.getElementsByTag("script") - val pattern = Pattern.compile("C_DATA=\'(.+?)\'") - for (element in scriptElements) { - if (element.data().contains("C_DATA")) { - val matcher = pattern.matcher(element.data()) - if (matcher.find()) { - val data = matcher.group(1) - if (data != null) { - return data - } - } - } - } - - throw Error("Unable to match for C_DATA") - } - - private fun decodeAndDecrypt(decodeName: String, value: String, keyArr: Array<String>): String { - val decodedValue = String(Base64.decode(value, Base64.NO_WRAP)) - for (key in keyArr) { - try { - return decryptAES(decodedValue, key) - } catch (ex: Exception) { - if (ex.toString() != "Decryption failed") { - throw ex - } - } - } - - throw Exception("Decryption failed ($decodeName exhausted keys)") - } - - @SuppressLint("GetInstance") - private fun decryptAES(value: String, key: String): String { - val secretKey = SecretKeySpec(key.toByteArray(), "AES") - val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding") - - return try { - cipher.init(Cipher.DECRYPT_MODE, secretKey) - - val code = Base64.decode(value, Base64.NO_WRAP) - - String(cipher.doFinal(code)) - } catch (_: Exception) { - throw Exception("Decryption failed") - } - } - - private fun regexExtractStringValue(mangaData: String, regex: String, messageIfError: String): String { - val pattern = Pattern.compile(regex) - val matcher = pattern.matcher(mangaData) - if (matcher.find()) { - return matcher.group(1) ?: throw Exception(messageIfError) - } - - throw Error(messageIfError) - } - - /* - private fun regexExtractIntValue(mangaData: String, regex: String, messageIfError: String): Int { - return regexExtractStringValue(mangaData, regex, messageIfError).let { Integer.parseInt(it) } - } - */ - - /* - private fun encodeUriComponent(str: String): String { - return URLEncoder.encode(str, "UTF-8") - .replace("+", "%20") - .replace("%7E", "~") - .replace("*", "%2A") - } - */ - - private fun encodeUri(str: String): String { - // https://stackoverflow.com/questions/31511922/is-uri-encode-in-android-equivalent-to-encodeuricomponent-in-javascript - val whitelistChar = "@#&=*+-_.,:!?()/~'%" - return Uri.encode(str, whitelistChar) - } -} diff --git a/src/zh/pufei/AndroidManifest.xml b/src/zh/pufei/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/pufei/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/pufei/build.gradle b/src/zh/pufei/build.gradle deleted file mode 100644 index 28be8d2dc..000000000 --- a/src/zh/pufei/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Pufei' - pkgNameSuffix = 'zh.pufei' - extClass = '.Pufei' - extVersionCode = 6 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/pufei/res/mipmap-hdpi/ic_launcher.png b/src/zh/pufei/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9cb551747..000000000 Binary files a/src/zh/pufei/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/pufei/res/mipmap-mdpi/ic_launcher.png b/src/zh/pufei/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4f034cafc..000000000 Binary files a/src/zh/pufei/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/pufei/res/mipmap-xhdpi/ic_launcher.png b/src/zh/pufei/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c93dd7b6b..000000000 Binary files a/src/zh/pufei/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/pufei/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/pufei/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 207599b9c..000000000 Binary files a/src/zh/pufei/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/pufei/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/pufei/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 3abc24da8..000000000 Binary files a/src/zh/pufei/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/pufei/res/web_hi_res_512.png b/src/zh/pufei/res/web_hi_res_512.png deleted file mode 100644 index 0cfdf91ca..000000000 Binary files a/src/zh/pufei/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/pufei/src/eu/kanade/tachiyomi/extension/zh/pufei/Pufei.kt b/src/zh/pufei/src/eu/kanade/tachiyomi/extension/zh/pufei/Pufei.kt deleted file mode 100644 index 71c07cf85..000000000 --- a/src/zh/pufei/src/eu/kanade/tachiyomi/extension/zh/pufei/Pufei.kt +++ /dev/null @@ -1,213 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.pufei - -// temp patch: -// https://github.com/tachiyomiorg/tachiyomi/pull/2031 - -import android.util.Base64 -import com.squareup.duktape.Duktape -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.Filter -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.online.ParsedHttpSource -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -fun asJsoup(response: Response, html: String? = null): Document { - return Jsoup.parse(html ?: bodyWithAutoCharset(response), response.request().url().toString()) -} - -fun bodyWithAutoCharset(response: Response, _charset: String? = null): String { - val htmlBytes: ByteArray = response.body()!!.bytes() - var c = _charset - - if (c == null) { - val regexPat = Regex("""charset=(\w+)""") - val match = regexPat.find(String(htmlBytes)) - c = match?.groups?.get(1)?.value - } - - return String(htmlBytes, charset(c ?: "utf8")) -} - -// patch finish - -fun ByteArray.toHexString() = joinToString("%") { "%02x".format(it) } - -class Pufei : ParsedHttpSource() { - - override val name = "扑飞漫画" - override val baseUrl = "http://m.pufei8.com" - override val lang = "zh" - override val supportsLatest = true - val imageServer = "http://res.img.youzipi.net/" - - override val client: OkHttpClient - get() = network.client.newBuilder() - .addNetworkInterceptor(rewriteOctetStream) - .build() - - private val rewriteOctetStream: Interceptor = Interceptor { chain -> - val originalResponse: Response = chain.proceed(chain.request()) - if (originalResponse.headers("Content-Type").contains("application/octet-stream") && originalResponse.request().url().toString().contains(".jpg")) { - val orgBody = originalResponse.body()!!.bytes() - val newBody = ResponseBody.create(MediaType.parse("image/jpeg"), orgBody) - originalResponse.newBuilder() - .body(newBody) - .build() - } else originalResponse - } - - override fun popularMangaSelector() = "ul#detail li" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun headersBuilder() = super.headersBuilder() - .add("Referer", baseUrl) - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/manhua/paihang.html", headers) - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/manhua/update.html", headers) - - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.select("h3").text().trim() - manga.thumbnail_url = it.select("div.thumb img").attr("data-src") - } - return manga - } - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element) - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - - override fun popularMangaNextPageSelector() = null - - override fun latestUpdatesNextPageSelector() = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.book-detail div.cont-list") - - val manga = SManga.create() - manga.description = infoElement.select("div#bookIntro > p").text().trim() - manga.thumbnail_url = infoElement.select("div.thumb > img").first()?.attr("src") - manga.author = infoElement.select(":nth-child(4) dd").first()?.text() - return manga - } - - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaSelector() = "ul#detail > li" - - private fun encodeGBK(str: String) = "%" + str.toByteArray(charset("gb2312")).toHexString() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/e/search/?searchget=1&tbname=mh&show=title,player,playadmin,bieming,pinyin,playadmin&tempid=4&keyboard=" + encodeGBK(query))?.newBuilder() - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage { -// val document = response.asJsoup() - val document = asJsoup(response) - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - return MangasPage(mangas, false) - } - - override fun chapterListSelector() = "div.chapter-list > ul > li" - - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a") - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text().trim() - return chapter - } - - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - - override fun pageListParse(document: Document): List<Page> { - val html = document.html() - val re = Regex("cp=\"(.*?)\"") - val imgbase64 = re.find(html)?.groups?.get(1)?.value - val imgCode = String(Base64.decode(imgbase64, Base64.DEFAULT)) - val imgArrStr = Duktape.create().use { - it.evaluate("$imgCode.join('|')") as String - } - val hasHost = imgArrStr.startsWith("http") - return imgArrStr.split('|').mapIndexed { i, imgStr -> - Page(i, "", if (hasHost) imgStr else imageServer + imgStr) - } - } - - override fun imageUrlParse(document: Document) = "" - - private class GenreFilter(genres: Array<String>) : Filter.Select<String>("Genre", genres) - - override fun getFilterList() = FilterList( - GenreFilter(getGenreList()) - ) - - private fun getGenreList() = arrayOf( - "All" - ) - - // temp patch - override fun latestUpdatesParse(response: Response): MangasPage { - val document = asJsoup(response) - - val mangas = document.select(latestUpdatesSelector()).map { element -> - latestUpdatesFromElement(element) - } - - return MangasPage(mangas, false) - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = asJsoup(response) - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - - return MangasPage(mangas, false) - } - - override fun mangaDetailsParse(response: Response): SManga { - return mangaDetailsParse(asJsoup(response)) - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = asJsoup(response) - return document.select(chapterListSelector()).map { chapterFromElement(it) } - } - - override fun pageListParse(response: Response): List<Page> { - return pageListParse(asJsoup(response)) - } - - override fun imageUrlParse(response: Response): String { - return imageUrlParse(asJsoup(response)) - } - // patch finish -} diff --git a/src/zh/qimiaomh/AndroidManifest.xml b/src/zh/qimiaomh/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/qimiaomh/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/qimiaomh/build.gradle b/src/zh/qimiaomh/build.gradle deleted file mode 100644 index 5e9983ee8..000000000 --- a/src/zh/qimiaomh/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Qimiaomh' - pkgNameSuffix = 'zh.qimiaomh' - extClass = '.Qimiaomh' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/qimiaomh/res/mipmap-hdpi/ic_launcher.png b/src/zh/qimiaomh/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 311360bd9..000000000 Binary files a/src/zh/qimiaomh/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qimiaomh/res/mipmap-mdpi/ic_launcher.png b/src/zh/qimiaomh/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index f8abbb4d3..000000000 Binary files a/src/zh/qimiaomh/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qimiaomh/res/mipmap-xhdpi/ic_launcher.png b/src/zh/qimiaomh/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2414eb656..000000000 Binary files a/src/zh/qimiaomh/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qimiaomh/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/qimiaomh/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 83f93e28b..000000000 Binary files a/src/zh/qimiaomh/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qimiaomh/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/qimiaomh/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e319801bf..000000000 Binary files a/src/zh/qimiaomh/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qimiaomh/res/web_hi_res_512.png b/src/zh/qimiaomh/res/web_hi_res_512.png deleted file mode 100644 index 82db736ec..000000000 Binary files a/src/zh/qimiaomh/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/qimiaomh/src/eu/kanade/tachiyomi/extension/zh/qimiaomh/Qimiaomh.kt b/src/zh/qimiaomh/src/eu/kanade/tachiyomi/extension/zh/qimiaomh/Qimiaomh.kt deleted file mode 100644 index 0f57a3808..000000000 --- a/src/zh/qimiaomh/src/eu/kanade/tachiyomi/extension/zh/qimiaomh/Qimiaomh.kt +++ /dev/null @@ -1,108 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.qimiaomh - -import com.github.salomonbrys.kotson.string -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.Random -import java.util.concurrent.TimeUnit - -class Qimiaomh : ParsedHttpSource() { - override val name: String = "奇妙漫画" - override val lang: String = "zh" - override val baseUrl: String = "https://www.qimiaomh.com" - override val supportsLatest: Boolean = true - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build()!! - - // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/list-1------hits--$page.html", headers) - } - override fun popularMangaNextPageSelector(): String? = "a:contains(下一页)" - override fun popularMangaSelector(): String = "div.classification" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - url = element.select("a").first().attr("href") - thumbnail_url = element.select("img.lazyload").attr("abs:data-src") - title = element.select("a").first().text() - } - - // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/list-1------updatetime--$page.html", headers) - } - override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector() - override fun latestUpdatesSelector(): String = popularMangaSelector() - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - throw Exception("不管用 (T_T)") - // Todo Filters - } - override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() - override fun searchMangaSelector(): String = popularMangaSelector() - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Details - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - title = document.select("h1.title").text() - author = document.select("p.author").first().ownText() - artist = author - val glist = document.select("span.labelBox a").map { it.text() } - genre = glist.joinToString(", ") - description = document.select("p#worksDesc").text().trim() - thumbnail_url = document.select("div.ctdbLeft img").attr("src") - status = when (document.select("a.status").text().substringAfter(":").trim()) { - "连载中" -> SManga.ONGOING - "完结" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - - // Chapters - - override fun chapterListSelector(): String = "div.comic-content-list ul.comic-content-c" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - url = element.select("a").first().attr("href") - name = element.select("li.tit").text().trim() - } - private fun parseDate(date: String): Long { - return SimpleDateFormat("dd/MM/yyyy", Locale.US).parse(date)?.time ?: 0L - } - - // Pages - - override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { - val script = document.select("script:containsData(var did =)").html() - val did = script.substringAfter("var did = ").substringBefore(";") - val sid = script.substringAfter("var sid = ").substringBefore(";") - val url = "$baseUrl/Action/Play/AjaxLoadImgUrl?did=$did&sid=$sid&tmp=${Random().nextFloat()}" - val body = client.newCall(GET(url, headers)).execute().body()!!.string() - val json = JsonParser().parse(body).asJsonObject - val images = json["listImg"].asJsonArray - images.forEachIndexed { index, jsonElement -> - add(Page(index, "", jsonElement.string)) - } - } - override fun imageUrlParse(document: Document): String { - throw Exception("Not Used") - } - - // Not Used -} diff --git a/src/zh/qiximh/AndroidManifest.xml b/src/zh/qiximh/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/qiximh/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/qiximh/build.gradle b/src/zh/qiximh/build.gradle deleted file mode 100644 index fedd38c25..000000000 --- a/src/zh/qiximh/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'QiXiManhua' - pkgNameSuffix = 'zh.qiximh' - extClass = '.Qiximh' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/qiximh/res/mipmap-hdpi/ic_launcher.png b/src/zh/qiximh/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 17d338bed..000000000 Binary files a/src/zh/qiximh/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qiximh/res/mipmap-mdpi/ic_launcher.png b/src/zh/qiximh/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index b923d4207..000000000 Binary files a/src/zh/qiximh/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qiximh/res/mipmap-xhdpi/ic_launcher.png b/src/zh/qiximh/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ca51735a7..000000000 Binary files a/src/zh/qiximh/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qiximh/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/qiximh/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e8236d68b..000000000 Binary files a/src/zh/qiximh/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qiximh/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/qiximh/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 9ac2b6f17..000000000 Binary files a/src/zh/qiximh/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/qiximh/res/web_hi_res_512.png b/src/zh/qiximh/res/web_hi_res_512.png deleted file mode 100644 index 284edbd3e..000000000 Binary files a/src/zh/qiximh/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/qiximh/src/eu/kanade/tachiyomi/extension/zh/qiximh/Qiximh.kt b/src/zh/qiximh/src/eu/kanade/tachiyomi/extension/zh/qiximh/Qiximh.kt deleted file mode 100644 index a1435c390..000000000 --- a/src/zh/qiximh/src/eu/kanade/tachiyomi/extension/zh/qiximh/Qiximh.kt +++ /dev/null @@ -1,306 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.qiximh - -import com.squareup.duktape.Duktape -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.model.Filter -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.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.Request -import okhttp3.Response -import org.json.JSONArray -import org.json.JSONObject - -class Qiximh : HttpSource() { - override val lang = "zh" - override val supportsLatest = true - override val name = "七夕漫画" - override val baseUrl = "http://qiximh1.com" - // This is hard limit by API - val maxPage = 5 - - // Used in Rank API - private enum class RANKTYPE(val rankVal: Int) { - DAILY_HOT(1), - WEEKLY_HOT(2), - MONTHLY_HOT(3), - OVERALL_HOT(4), - LATEST(5), - NEW(6), - } - - // Used in Sort API (although it looks like genre) - private enum class SORTTYPE(val sortVal: Int) { - ADVENTURE(1), - ACTION(2), - MAGIC_SCIFI(3), - THRILLER(4), - ROMANCE(5), - SLICE_OF_LIFE(6), - // These are not accurate, hence not included - // HIGH_QUALITY(11), - // ON_GOING(12), - // COMPLETED(13) - } - - private open class PairIntFilter(displayName: String, val vals: Array<Pair<String, Int?>>) : - Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { - fun getVal() = vals[state].second - } - - // Override - private fun FormBody.value(name: String): String { - return (0 until size()) - .first { name(it) == name } - .let { value(it) } - } - - private fun commonRankDataRequest(page: Int, rankTypeVal: Int): Request { - return POST( - "$baseUrl/rankdata.php", - headers, - FormBody.Builder() - .add("page_num", page.toString()) - .add("type", rankTypeVal.toString()) - .build() - ) - } - - private fun commonSortDataRequest(page: Int, sortTypeVal: Int): Request { - return POST( - "$baseUrl/sortdata.php", - headers, - FormBody.Builder() - .add("page_num", page.toString()) - .add("type", sortTypeVal.toString()) - .build() - ) - } - - private fun commonDataProcess(origRequest: Request, responseBody: String): MangasPage { - val jsonData = JSONArray(responseBody) - - val mangaArr = mutableListOf<SManga>() - - for (i in 0 until jsonData.length()) { - val targetObj = jsonData.getJSONObject(i) - mangaArr.add( - SManga.create().apply { - title = targetObj.get("name") as String - status = SManga.UNKNOWN - thumbnail_url = targetObj.get("imgurl") as String - url = "$baseUrl/${targetObj.get("id")}/" - } - ) - } - - val requestBody = origRequest.body() as FormBody - val currentPage: Int = requestBody.value("page_num").toInt() - val hasNextPage = currentPage < maxPage - - return MangasPage(mangaArr, hasNextPage) - } - - private fun commonRankDataParse(response: Response): MangasPage { - return commonDataProcess(response.request(), response.body()!!.string()) - } - - // Popular Manga - override fun popularMangaRequest(page: Int) = commonRankDataRequest(page, RANKTYPE.DAILY_HOT.rankVal) - override fun popularMangaParse(response: Response) = commonRankDataParse(response) - - // Latest Updates - override fun latestUpdatesRequest(page: Int) = commonRankDataRequest(page, RANKTYPE.LATEST.rankVal) - override fun latestUpdatesParse(response: Response) = commonRankDataParse(response) - - // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (query.isNotBlank()) { - return POST( - "$baseUrl/search.php", - headers, - FormBody.Builder() - .add("keyword", query) - .build() - ) - } else { - filters.forEach { filter -> - when (filter) { - is RankFilter -> { - val filterVal = filter.getVal() - if (filterVal != null) { - return commonRankDataRequest(page, filterVal) - } - } - is SortFilter -> { - val filterVal = filter.getVal() - if (filterVal != null) { - return commonSortDataRequest(page, filterVal) - } - } - } - } - - // Default if no filter set - return commonRankDataRequest(page, RANKTYPE.DAILY_HOT.rankVal) - } - } - override fun searchMangaParse(response: Response): MangasPage { - val responseBody = response.body() - val mangaArr = mutableListOf<SManga>() - - if (responseBody != null) { - val responseString = responseBody.string() - if (!responseString.isNullOrEmpty()) { - if (responseString.startsWith("[")) { - // This is to process filter - return commonDataProcess(response.request(), responseString) - } else { - val jsonData = JSONObject(responseString) - if (jsonData.get("msg") == "success") { - val jsonArr = jsonData.getJSONArray("search_data") - - for (i in 0 until jsonArr.length()) { - val targetObj = jsonArr.getJSONObject(i) - mangaArr.add( - SManga.create().apply { - title = targetObj.get("name") as String - thumbnail_url = targetObj.get("imgs") as String - url = "$baseUrl/${targetObj.get("id")}/" - } - ) - } - } - } - } - } - - // Search does not have pagination - return MangasPage(mangaArr, false) - } - - // Filter - private class RankFilter : PairIntFilter( - "排行榜", - arrayOf( - Pair("全部", null), - Pair("日热门榜", RANKTYPE.DAILY_HOT.rankVal), - Pair("周热门榜", RANKTYPE.WEEKLY_HOT.rankVal), - Pair("月热门榜", RANKTYPE.MONTHLY_HOT.rankVal), - Pair("总热门榜", RANKTYPE.OVERALL_HOT.rankVal), - Pair("最近更新", RANKTYPE.LATEST.rankVal), - Pair("新漫入库", RANKTYPE.NEW.rankVal), - ) - ) - - private class SortFilter : PairIntFilter( - "分类", - arrayOf( - Pair("全部", null), - Pair("冒险热血", SORTTYPE.ADVENTURE.sortVal), - Pair("武侠格斗", SORTTYPE.ACTION.sortVal), - Pair("玄幻科幻", SORTTYPE.MAGIC_SCIFI.sortVal), - Pair("侦探推理", SORTTYPE.THRILLER.sortVal), - Pair("耽美爱情", SORTTYPE.ROMANCE.sortVal), - Pair("生活漫画", SORTTYPE.SLICE_OF_LIFE.sortVal), - ) - ) - - override fun getFilterList() = FilterList( - Filter.Header("注意: 文本搜索,排行榜和分类筛选,不可同时使用"), - Filter.Separator(), - RankFilter(), - SortFilter() - ) - - // Manga Details - override fun mangaDetailsRequest(manga: SManga) = GET(manga.url, headers) - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - return SManga.create().apply { - title = document.select("h1.name").text() - - author = document.select(".author_name").text() - - description = arrayOf( - document.select("span.looking_chapter").text(), - document.select(".bold_fortime").text(), - document.select(".details").first().ownText(), - ).filter(String::isNotBlank).joinToString("\n") - - genre = arrayOf( - document.select(".comic_hot span:last-child").text(), - *(document.select(".tags.tags_last").text().split("|").toTypedArray()) - ).filter(String::isNotBlank).joinToString() - } - } - - // Chapter - override fun chapterListRequest(manga: SManga) = GET(manga.url, headers) - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - - // API does not allow retrieve full chapter list, hence the need to parse the chapters from both HTML and API - val htmlChapters = document.select(".catalog_list.row_catalog_list a").map { - SChapter.create().apply { - name = it.text() - url = "$baseUrl${it.attr("href")}" - } - } - - val mangaUrl = response.request().url().toString() - - val request = POST( - "$baseUrl/bookchapter/", - headers, - FormBody.Builder() - .add("id", mangaUrl.split("/").toTypedArray().filter(String::isNotBlank).last()) - .add("id2", "1") - .build() - ) - - val inlineResponse = client.newCall(request).execute() - val jsonData = JSONArray(inlineResponse.body()!!.string()) - - val chapterArr = mutableListOf<SChapter>() - chapterArr.addAll(htmlChapters) - - for (i in 0 until jsonData.length()) { - val targetObj = jsonData.getJSONObject(i) - chapterArr.add( - SChapter.create().apply { - name = targetObj.get("chaptername") as String - url = "$mangaUrl${targetObj.get("chapterid")}.html" - } - ) - } - - return chapterArr - } - - // Page - override fun pageListRequest(chapter: SChapter) = GET(chapter.url, headers) - override fun pageListParse(response: Response): List<Page> { - val document = response.asJsoup() - - // Special thanks to author who created Mangahere.kt - val duktape = Duktape.create() - - val script = document.select("script:containsData(function(p,a,c,k,e,d))").html().removePrefix("eval") - val deobfuscatedScript = duktape.evaluate(script).toString() - val urls = deobfuscatedScript.substringAfter("newImgs=[\"").substringBefore("\"]").split("\",\"") - duktape.close() - - return urls.mapIndexed { index, s -> Page(index, "", s) } - } - - // Unused - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Unused") -} diff --git a/src/zh/tohomh123/AndroidManifest.xml b/src/zh/tohomh123/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/tohomh123/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/tohomh123/build.gradle b/src/zh/tohomh123/build.gradle deleted file mode 100644 index 69da140f2..000000000 --- a/src/zh/tohomh123/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Tohomh123' - pkgNameSuffix = 'zh.tohomh123' - extClass = '.Tohomh' - extVersionCode = 2 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/tohomh123/res/mipmap-hdpi/ic_launcher.png b/src/zh/tohomh123/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index b44315a63..000000000 Binary files a/src/zh/tohomh123/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/tohomh123/res/mipmap-mdpi/ic_launcher.png b/src/zh/tohomh123/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8bad12d98..000000000 Binary files a/src/zh/tohomh123/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/tohomh123/res/mipmap-xhdpi/ic_launcher.png b/src/zh/tohomh123/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index c6422128a..000000000 Binary files a/src/zh/tohomh123/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/tohomh123/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/tohomh123/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 1a3a3282a..000000000 Binary files a/src/zh/tohomh123/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/tohomh123/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/tohomh123/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 37d0074e6..000000000 Binary files a/src/zh/tohomh123/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/tohomh123/res/web_hi_res_512.png b/src/zh/tohomh123/res/web_hi_res_512.png deleted file mode 100644 index aa1b2edf7..000000000 Binary files a/src/zh/tohomh123/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/tohomh123/src/eu/kanade/tachiyomi/extension/zh/tohomh123/Tohomh.kt b/src/zh/tohomh123/src/eu/kanade/tachiyomi/extension/zh/tohomh123/Tohomh.kt deleted file mode 100644 index 5b5f8f5d1..000000000 --- a/src/zh/tohomh123/src/eu/kanade/tachiyomi/extension/zh/tohomh123/Tohomh.kt +++ /dev/null @@ -1,154 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.tohomh123 - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.JsonObject -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Tohomh : ParsedHttpSource() { - - override val name = "Tohomh123" - - override val baseUrl = "https://www.tohomh123.com" - - override val lang = "zh" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/f-1-------hits--$page.html", headers) - } - - override fun popularMangaSelector() = "div.mh-item" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h2 a").let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - manga.thumbnail_url = element.select("p").attr("style").substringAfter("(").substringBefore(")") - return manga - } - - override fun popularMangaNextPageSelector() = "div.page-pagination li a:contains(>)" - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/f-1------updatetime--$page.html", headers) - } - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/action/Search?keyword=$query&page=$page", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - // Manga summary page - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.banner_detail_form").first() - - val manga = SManga.create() - manga.title = infoElement.select("h1").first().text() - manga.author = infoElement.select("div.info p.subtitle").text().substringAfter(":").trim() - val status = infoElement.select("div.banner_detail_form div.info span.block:contains(状态) span").text() - manga.status = parseStatus(status) - manga.description = infoElement.select("div.info p.content").text() - manga.thumbnail_url = infoElement.select("div.banner_detail_form div.cover img").first().attr("src") - return manga - } - - private fun parseStatus(status: String?) = when { - status == null -> SManga.UNKNOWN - status.contains("连载中") -> SManga.ONGOING - status.contains("完结") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListSelector() = "ul#detail-list-select-1 li a" - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } - // Add date for most recent chapter - document.select("div.banner_detail_form div.info span:contains(更新时间)").text() - .substringAfter(":").trim().let { chapters[0].date_upload = parseChapterDate(it) } - return chapters - } - - override fun chapterFromElement(element: Element): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(element.attr("href")) - chapter.name = element.ownText() - return chapter - } - - companion object { - val dateFormat by lazy { - SimpleDateFormat("yyyy-MM-dd", Locale.CHINA) - } - } - - private fun parseChapterDate(string: String): Long { - return dateFormat.parse(string)?.time ?: 0L - } - - // Pages - - override fun pageListParse(document: Document): List<Page> { - val pages = mutableListOf<Page>() - val script = document.select("script:containsData(imgDomain)").first().data() - val did = script.substringAfter("did=").substringBefore(";") - val sid = script.substringAfter("sid=").substringBefore(";") - val lastPage = script.substringAfter("pcount =").substringBefore(";").trim().toInt() - - for (i in 1..lastPage) { - pages.add(Page(i, "$baseUrl/action/play/read?did=$did&sid=$sid&iid=$i", "")) - } - return pages - } - - private val gson = Gson() - - override fun imageUrlParse(response: Response): String { - return gson.fromJson<JsonObject>(response.body()!!.string())["Code"].asString - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") - - override fun getFilterList() = FilterList() -} diff --git a/src/zh/wnacg/AndroidManifest.xml b/src/zh/wnacg/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/wnacg/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/wnacg/build.gradle b/src/zh/wnacg/build.gradle deleted file mode 100644 index c3e160437..000000000 --- a/src/zh/wnacg/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'WNACG' - pkgNameSuffix = 'zh.wnacg' - extClass = '.wnacg' - extVersionCode = 4 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/wnacg/res/mipmap-hdpi/ic_launcher.png b/src/zh/wnacg/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 6adcd3bcd..000000000 Binary files a/src/zh/wnacg/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wnacg/res/mipmap-mdpi/ic_launcher.png b/src/zh/wnacg/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 1cd5dd28f..000000000 Binary files a/src/zh/wnacg/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wnacg/res/mipmap-xhdpi/ic_launcher.png b/src/zh/wnacg/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 1bb2f6d7e..000000000 Binary files a/src/zh/wnacg/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wnacg/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/wnacg/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 563a78d49..000000000 Binary files a/src/zh/wnacg/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wnacg/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/wnacg/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 524e914fe..000000000 Binary files a/src/zh/wnacg/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wnacg/res/web_hi_res_512.png b/src/zh/wnacg/res/web_hi_res_512.png deleted file mode 100644 index 8ecdaf3d1..000000000 Binary files a/src/zh/wnacg/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/wnacg/src/eu/kanade/tachiyomi/extension/zh/wnacg/wnacg.kt b/src/zh/wnacg/src/eu/kanade/tachiyomi/extension/zh/wnacg/wnacg.kt deleted file mode 100644 index d3ad9feb8..000000000 --- a/src/zh/wnacg/src/eu/kanade/tachiyomi/extension/zh/wnacg/wnacg.kt +++ /dev/null @@ -1,105 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.wnacg - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class wnacg : ParsedHttpSource() { - override val name = "紳士漫畫" - override val baseUrl = "https://www.wnacg.org" - override val lang = "zh" - override val supportsLatest = false - - override fun popularMangaSelector() = "div.pic_box" - override fun latestUpdatesSelector() = throw Exception("Not used") - override fun searchMangaSelector() = popularMangaSelector() - override fun chapterListSelector() = "div.f_left > a" - - override fun popularMangaNextPageSelector() = "a:containsOwn(後頁)" - override fun latestUpdatesNextPageSelector() = throw Exception("Not used") - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/albums-index-page-$page.html", headers) - } - - override fun latestUpdatesRequest(page: Int) = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/albums-index-page-$page-sname-$query.html", headers) - } - - override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, headers) - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - - override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers) - override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .set("referer", baseUrl) - .set("sec-fetch-mode", "no-cors") - .set("sec-fetch-site", "cross-site") - .set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36") - - override fun popularMangaFromElement(element: Element) = mangaFromElement(element) - override fun latestUpdatesFromElement(element: Element) = throw Exception("Not used") - override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - - private fun mangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.setUrlWithoutDomain(element.select("a").first().attr("href")) - manga.title = element.select("a").attr("title").trim() - manga.thumbnail_url = "https://" + element.select("img").attr("src").replace("//", "") - // maybe the local cache cause the old source (url) can not be update. but the image can be update on detailpage. - // ps. new machine can be load img normal. - - return manga - } - - override fun chapterListParse(response: Response): List<SChapter> { - val document = response.asJsoup() - val chapters = mutableListOf<SChapter>() - // create one chapter since it is single books - chapters.add(createChapter("1", document.baseUri())) - return chapters - } - - private fun createChapter(pageNumber: String, mangaUrl: String): SChapter { - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(mangaUrl) - chapter.name = "Ch. $pageNumber" - return chapter - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.title = document.select("h2")?.text()?.trim() ?: "Unknown" - manga.artist = document.select("div.uwuinfo p")?.first()?.text()?.trim() ?: "Unknown" - manga.author = document.select("div.uwuinfo p")?.first()?.text()?.trim() ?: "Unknown" - manga.thumbnail_url = "https://" + document.select("div.uwthumb img").first().attr("src").replace("//", "") - return manga - } - - override fun pageListParse(document: Document): List<Page> { - val regex = "\\/\\/\\S*(jpg|png)".toRegex() - val slideaid = client.newCall(GET(baseUrl + document.select("a.btn:containsOwn(下拉閱讀)").attr("href"), headers)).execute().asJsoup() - val galleryaid = client.newCall(GET(baseUrl + slideaid.select("script[src$=html]").attr("src"), headers)).execute().asJsoup().toString() - val matchresult = regex.findAll(galleryaid).map { it.value }.toList() - val pages = mutableListOf<Page>() - for (i in matchresult.indices) { - pages.add(Page(i, "", "https:" + matchresult[i])) - } - return pages - } - - override fun chapterFromElement(element: Element) = throw Exception("Not used") - override fun imageUrlRequest(page: Page) = throw Exception("Not used") - override fun imageUrlParse(document: Document) = throw Exception("Not used") -} diff --git a/src/zh/wuqimanga/AndroidManifest.xml b/src/zh/wuqimanga/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/wuqimanga/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest package="eu.kanade.tachiyomi.extension" /> diff --git a/src/zh/wuqimanga/build.gradle b/src/zh/wuqimanga/build.gradle deleted file mode 100644 index 9af77bcdb..000000000 --- a/src/zh/wuqimanga/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'WuqiManga' - pkgNameSuffix = 'zh.wuqimanga' - extClass = '.WuqiManga' - extVersionCode = 1 - libVersion = '1.2' -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/wuqimanga/res/mipmap-hdpi/ic_launcher.png b/src/zh/wuqimanga/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 90438cd32..000000000 Binary files a/src/zh/wuqimanga/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wuqimanga/res/mipmap-mdpi/ic_launcher.png b/src/zh/wuqimanga/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 8363d3ff0..000000000 Binary files a/src/zh/wuqimanga/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wuqimanga/res/mipmap-xhdpi/ic_launcher.png b/src/zh/wuqimanga/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 9d08867db..000000000 Binary files a/src/zh/wuqimanga/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wuqimanga/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/wuqimanga/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e5401866f..000000000 Binary files a/src/zh/wuqimanga/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wuqimanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/wuqimanga/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a3fe98e29..000000000 Binary files a/src/zh/wuqimanga/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/wuqimanga/res/web_hi_res_512.png b/src/zh/wuqimanga/res/web_hi_res_512.png deleted file mode 100644 index 975429ec4..000000000 Binary files a/src/zh/wuqimanga/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/wuqimanga/src/eu/kanade/tachiyomi/extension/zh/wuqimanga/Comic.kt b/src/zh/wuqimanga/src/eu/kanade/tachiyomi/extension/zh/wuqimanga/Comic.kt deleted file mode 100644 index 688f1884a..000000000 --- a/src/zh/wuqimanga/src/eu/kanade/tachiyomi/extension/zh/wuqimanga/Comic.kt +++ /dev/null @@ -1,6 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.wuqimanga - -class Comic { - - val fs: List<String?>? = listOf() -} diff --git a/src/zh/wuqimanga/src/eu/kanade/tachiyomi/extension/zh/wuqimanga/WuqiManga.kt b/src/zh/wuqimanga/src/eu/kanade/tachiyomi/extension/zh/wuqimanga/WuqiManga.kt deleted file mode 100644 index 1f988d401..000000000 --- a/src/zh/wuqimanga/src/eu/kanade/tachiyomi/extension/zh/wuqimanga/WuqiManga.kt +++ /dev/null @@ -1,159 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.wuqimanga - -import com.google.gson.Gson -import com.squareup.duktape.Duktape -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -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.online.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class WuqiManga : ParsedHttpSource() { - - override val name = "57漫画" - override val baseUrl = "http://www.wuqimh.com" - override val lang = "zh" - override val supportsLatest = false - private val imageServer = "http://images.lancaier.com" - - override fun latestUpdatesRequest(page: Int) = throw Exception("Not used") - override fun latestUpdatesNextPageSelector() = throw Exception("Not used") - override fun latestUpdatesSelector() = throw Exception("Not used") - override fun latestUpdatesFromElement(element: Element) = throw Exception("Not used") - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/area-日本-order-hits", headers) - override fun popularMangaSelector() = "ul#contList > li" - override fun popularMangaNextPageSelector(): String? = null - - override fun popularMangaFromElement(element: Element): SManga { - val coverEl = element.select("a img").first() - val cover = if (coverEl.hasAttr("data-src")) { - coverEl.attr("data-src") - } else { - coverEl.attr("src") - } - val title = element.select("a").attr("title") - val url = element.select("a").attr("href") - - val manga = SManga.create() - - manga.thumbnail_url = cover - manga.title = title - manga.url = url - - return manga - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/q_$query-p-$page", headers) - } - - override fun searchMangaNextPageSelector() = "div.book-result > div > span > a.prev" - override fun searchMangaSelector() = "div.book-result li.cf" - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("div.book-detail").first().let { - val titleEl = it.select("dl > dt > a") - manga.setUrlWithoutDomain(titleEl.attr("href")) - manga.title = titleEl.attr("title").trim() - manga.description = it.select("dd.intro").text() - val status = it.select("dd.tags.status") - manga.status = if (status.select("span.red").first().text().contains("连载中")) { - SManga.ONGOING - } else { - SManga.COMPLETED - } - for (el in it.select("dd.tags")) { - if (el.select("span strong").text().contains("作者")) { - manga.author = el.select("span a").text() - } - } - } - manga.thumbnail_url = element.select("a.bcover > img").attr("src") - return manga - } - - override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/${manga.url}", headers) - override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) - override fun chapterListSelector() = throw Exception("Not used") - - override fun pageListRequest(chapter: SChapter) = GET("$baseUrl/${chapter.url}", headers) - - override fun headersBuilder() = Headers.Builder().add("Referer", "$baseUrl/") - .set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36") - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a") - - val chapter = SChapter.create() - chapter.url = urlElement.attr("href") - chapter.name = urlElement.attr("alt").trim() - return chapter - } - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.description = "" - manga.title = document.select(".book-title h1").text().trim() - manga.thumbnail_url = document.select(".hcover img").attr("src") - for (element in document.select("ul.detail-list li span")) { - if (element.select("strong").text().contains("漫画作者")) { - manga.author = element.select("a").text() - break - } - } - return manga - } - - override fun imageRequest(page: Page): Request { - return GET(page.imageUrl.toString(), headers) - } - - override fun imageUrlRequest(page: Page): Request { - return GET(page.imageUrl.toString(), headers) - } - - override fun imageUrlParse(document: Document): String = "" - - override fun chapterListParse(response: Response): List<SChapter> { - val chapters = mutableListOf<SChapter>() - response.asJsoup().select("div.chapter div.chapter-list>ul").asReversed().forEach { - it.select("li a").forEach { - chapters.add( - SChapter.create().apply { - url = it.attr("href") - name = it.attr("title") - } - ) - } - } - return chapters - } - - private val gson = Gson() - - override fun pageListParse(document: Document): List<Page> { - val html = document.html() - val packed = Regex("eval(.*?)\\n").find(html)?.groups?.get(1)?.value - val result = Duktape.create().use { - it.evaluate(packed) as String - } - val re2 = Regex("""\{.*\}""") - val imgJsonStr = re2.find(result)?.groups?.get(0)?.value - val imageJson: Comic = gson.fromJson(imgJsonStr, Comic::class.java) - - return imageJson.fs!!.mapIndexed { i, imgStr -> - val imgurl = "$imageServer$imgStr" - Page(i, "", imgurl) - } - } - - override fun getFilterList() = FilterList() -}