[New] ReadM (#2542)
* Upload ReadM * Add Icons * Update ReadM.kt - Extend default time out - Simplify pageListParse
This commit is contained in:
		
							parent
							
								
									9c0b1f252d
								
							
						
					
					
						commit
						18206ed744
					
				
							
								
								
									
										12
									
								
								src/en/readm/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/en/readm/build.gradle
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					apply plugin: 'com.android.application'
 | 
				
			||||||
 | 
					apply plugin: 'kotlin-android'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ext {
 | 
				
			||||||
 | 
					    appName = 'Tachiyomi: ReadM'
 | 
				
			||||||
 | 
					    pkgNameSuffix = 'en.readm'
 | 
				
			||||||
 | 
					    extClass = '.ReadM'
 | 
				
			||||||
 | 
					    extVersionCode = 1
 | 
				
			||||||
 | 
					    libVersion = '1.2'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apply from: "$rootDir/common.gradle"
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 4.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 5.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 11 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/en/readm/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/en/readm/res/web_hi_res_512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/en/readm/res/web_hi_res_512.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 54 KiB  | 
							
								
								
									
										226
									
								
								src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								src/en/readm/src/eu/kanade/tachiyomi/extension/en/readm/ReadM.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,226 @@
 | 
				
			|||||||
 | 
					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.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.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("data-src")
 | 
				
			||||||
 | 
					        element.select("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("manga-name", query)
 | 
				
			||||||
 | 
					        filters.forEach { filter ->
 | 
				
			||||||
 | 
					            when (filter) {
 | 
				
			||||||
 | 
					                is TypeFilter -> formBody.add("type", typeArray[filter.state].second)
 | 
				
			||||||
 | 
					                is AuthorName -> formBody.add("author-name", filter.state)
 | 
				
			||||||
 | 
					                is ArtistName -> formBody.add("artist-name", filter.state)
 | 
				
			||||||
 | 
					                is StatusFilter -> formBody.add("status", statusArray[filter.state].second)
 | 
				
			||||||
 | 
					                is GenreFilter -> filter.state.forEach { genre ->
 | 
				
			||||||
 | 
					                    if (genre.isExcluded()) formBody.add("exclude[]", genre.id)
 | 
				
			||||||
 | 
					                    if (genre.isIncluded()) formBody.add("include[]", genre.id)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (filters.isEmpty()) {
 | 
				
			||||||
 | 
					            formBody
 | 
				
			||||||
 | 
					                .add("type", "all")
 | 
				
			||||||
 | 
					                .add("status", "both")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val searchHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest").build()
 | 
				
			||||||
 | 
					        return POST("$baseUrl/service/advanced_search", searchHeaders, formBody.build())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun searchMangaNextPageSelector(): String? = null
 | 
				
			||||||
 | 
					    override fun searchMangaSelector(): String = "div.poster-with-subject"
 | 
				
			||||||
 | 
					    override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //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() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //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:data-src"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //Filters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override fun getFilterList(): FilterList = FilterList(
 | 
				
			||||||
 | 
					        TypeFilter(typeArray),
 | 
				
			||||||
 | 
					        AuthorName(),
 | 
				
			||||||
 | 
					        ArtistName(),
 | 
				
			||||||
 | 
					        StatusFilter(statusArray),
 | 
				
			||||||
 | 
					        GenreFilter(genreArray())
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private class TypeFilter(values: Array<Pair<String, String>>) : Filter.Select<String>("Type", values.map { it.first }.toTypedArray())
 | 
				
			||||||
 | 
					    private class AuthorName : Filter.Text("Author Name")
 | 
				
			||||||
 | 
					    private class ArtistName : Filter.Text("Artist Name")
 | 
				
			||||||
 | 
					    private class StatusFilter(values: Array<Pair<String, String>>) : Filter.Select<String>("Status", values.map { it.first }.toTypedArray())
 | 
				
			||||||
 | 
					    private class GenreFilter(state: List<Tag>) : Filter.Group<Tag>("Genres", state)
 | 
				
			||||||
 | 
					    private class Tag(name: String, val id: String) : Filter.TriState(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val typeArray = arrayOf(
 | 
				
			||||||
 | 
					        Pair("All", "all"),
 | 
				
			||||||
 | 
					        Pair("Japanese Manga", "japanese"),
 | 
				
			||||||
 | 
					        Pair("Korean Manhwa", "korean"),
 | 
				
			||||||
 | 
					        Pair("Chinese Manhua", "chinese")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val statusArray = arrayOf(
 | 
				
			||||||
 | 
					        Pair("Both", "both"),
 | 
				
			||||||
 | 
					        Pair("Ongoing", "ongoing"),
 | 
				
			||||||
 | 
					        Pair("Completed", "completed")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun genreArray() = listOf(
 | 
				
			||||||
 | 
					        Tag("Action", "1"),
 | 
				
			||||||
 | 
					        Tag("Adventure", "23"),
 | 
				
			||||||
 | 
					        Tag("Comedy", "12"),
 | 
				
			||||||
 | 
					        Tag("Doujinshi", "26"),
 | 
				
			||||||
 | 
					        Tag("Drama", "9"),
 | 
				
			||||||
 | 
					        Tag("Ecchi", "2"),
 | 
				
			||||||
 | 
					        Tag("Fantasy", "3"),
 | 
				
			||||||
 | 
					        Tag("Gender Bender", "30"),
 | 
				
			||||||
 | 
					        Tag("Harem", "4"),
 | 
				
			||||||
 | 
					        Tag("Historical", "36"),
 | 
				
			||||||
 | 
					        Tag("Horror", "34"),
 | 
				
			||||||
 | 
					        Tag("Josei", "17"),
 | 
				
			||||||
 | 
					        Tag("Lolicon", "39"),
 | 
				
			||||||
 | 
					        Tag("Manga", "5"),
 | 
				
			||||||
 | 
					        Tag("Manhua", "31"),
 | 
				
			||||||
 | 
					        Tag("Manhwa", "32"),
 | 
				
			||||||
 | 
					        Tag("Martial Arts", "22"),
 | 
				
			||||||
 | 
					        Tag("Mecha", "33"),
 | 
				
			||||||
 | 
					        Tag("Mystery", "13"),
 | 
				
			||||||
 | 
					        Tag("None", "41"),
 | 
				
			||||||
 | 
					        Tag("One shot", "16"),
 | 
				
			||||||
 | 
					        Tag("Psychological", "14"),
 | 
				
			||||||
 | 
					        Tag("Romance", "6"),
 | 
				
			||||||
 | 
					        Tag("School Life", "10"),
 | 
				
			||||||
 | 
					        Tag("Sci fi", "19"),
 | 
				
			||||||
 | 
					        Tag("Sci-fi", "40"),
 | 
				
			||||||
 | 
					        Tag("Seinen", "24"),
 | 
				
			||||||
 | 
					        Tag("Shotacon", "38"),
 | 
				
			||||||
 | 
					        Tag("Shoujo", "8"),
 | 
				
			||||||
 | 
					        Tag("Shoujo Ai", "37"),
 | 
				
			||||||
 | 
					        Tag("Shounen", "7"),
 | 
				
			||||||
 | 
					        Tag("Shounen Ai", "35"),
 | 
				
			||||||
 | 
					        Tag("Slice of Life", "21"),
 | 
				
			||||||
 | 
					        Tag("Sports", "29"),
 | 
				
			||||||
 | 
					        Tag("Supernatural", "11"),
 | 
				
			||||||
 | 
					        Tag("Tragedy", "15"),
 | 
				
			||||||
 | 
					        Tag("Uncategorized", "43"),
 | 
				
			||||||
 | 
					        Tag("Yaoi", "28"),
 | 
				
			||||||
 | 
					        Tag("Yuri", "20")
 | 
				
			||||||
 | 
					    ).sortedWith(compareBy { it.name })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user