add NineHentai very basic features (#829)
* add NineHentai very basic features * icons
This commit is contained in:
		
							parent
							
								
									b3167bc1a9
								
							
						
					
					
						commit
						27419ab711
					
				@ -5,7 +5,7 @@ buildscript {
 | 
			
		||||
        google()
 | 
			
		||||
    }
 | 
			
		||||
    dependencies {
 | 
			
		||||
        classpath 'com.android.tools.build:gradle:3.3.0'
 | 
			
		||||
        classpath 'com.android.tools.build:gradle:3.3.1'
 | 
			
		||||
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								src/all/ninehentai/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/all/ninehentai/build.gradle
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    appName = 'Tachiyomi: NineHentai'
 | 
			
		||||
    pkgNameSuffix = 'all.ninehentai'
 | 
			
		||||
    extClass = '.NineHentai'
 | 
			
		||||
    extVersionCode = 1
 | 
			
		||||
    libVersion = '1.2'
 | 
			
		||||
}
 | 
			
		||||
dependencies {
 | 
			
		||||
    compileOnly 'com.google.code.gson:gson:2.8.2'
 | 
			
		||||
    compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
 | 
			
		||||
    compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440'
 | 
			
		||||
}
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 5.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 9.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/all/ninehentai/res/web_hi_res_512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/all/ninehentai/res/web_hi_res_512.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 64 KiB  | 
@ -0,0 +1,5 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.ninehentai
 | 
			
		||||
 | 
			
		||||
data class id(
 | 
			
		||||
    val id : Int
 | 
			
		||||
)
 | 
			
		||||
@ -0,0 +1,223 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.ninehentai
 | 
			
		||||
 | 
			
		||||
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.JsonElement
 | 
			
		||||
import com.google.gson.JsonParser
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
import eu.kanade.tachiyomi.network.asObservableSuccess
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.*
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
 | 
			
		||||
import okhttp3.*
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import java.net.URLEncoder
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
open class NineHentai() : ParsedHttpSource() {
 | 
			
		||||
    final override val baseUrl = "https://9hentai.com"
 | 
			
		||||
    override val name = "NineHentai"
 | 
			
		||||
 | 
			
		||||
    override val lang = "en"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client: OkHttpClient = network.cloudflareClient
 | 
			
		||||
 | 
			
		||||
    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 ->
 | 
			
		||||
                    popularMangaParse(response)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val list = getMangaList(response)
 | 
			
		||||
        return MangasPage(list, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return client.newCall(latestUpdatesRequest(page))
 | 
			
		||||
                .asObservableSuccess()
 | 
			
		||||
                .map { response ->
 | 
			
		||||
                    latestUpdatesParse(response)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
 | 
			
		||||
 | 
			
		||||
    private fun getMangaList(response: Response): List<SManga> {
 | 
			
		||||
        val jsonData = response.body()!!.string()
 | 
			
		||||
        val jsonObject = JsonParser().parse(jsonData).asJsonObject
 | 
			
		||||
        val results = jsonObject.getAsJsonArray("results")
 | 
			
		||||
        return parseSearch(results.toList())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseSearch(jsonArray: List<JsonElement>): List<SManga> {
 | 
			
		||||
        val mutableList = mutableListOf<SManga>()
 | 
			
		||||
        jsonArray.forEach { json ->
 | 
			
		||||
            val manga = SManga.create()
 | 
			
		||||
            val id = json["id"].string
 | 
			
		||||
            manga.url = "$baseUrl/g/$id"
 | 
			
		||||
            manga.title = json["title"].string
 | 
			
		||||
            manga.thumbnail_url = json["image_server"].string + id + "/" + "cover.jpg"
 | 
			
		||||
            mutableList.add(manga)
 | 
			
		||||
        }
 | 
			
		||||
        return mutableList
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
 | 
			
		||||
        return client.newCall(mangaDetailsRequest(manga))
 | 
			
		||||
                .asObservableSuccess()
 | 
			
		||||
                .map { response ->
 | 
			
		||||
                    chapterListParse(response)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getChapter(response: Response): SChapter {
 | 
			
		||||
        val jsonData = response.body()!!.string()
 | 
			
		||||
        val jsonObject = JsonParser().parse(jsonData).asJsonObject
 | 
			
		||||
        val jsonArray = jsonObject.getAsJsonObject("results")
 | 
			
		||||
 | 
			
		||||
        val sChapter = SChapter.create()
 | 
			
		||||
 | 
			
		||||
        jsonArray.let { json ->
 | 
			
		||||
            val id = json["id"].string
 | 
			
		||||
            sChapter.url = "$baseUrl/g/$id"
 | 
			
		||||
            sChapter.name = "chapter"
 | 
			
		||||
            //api doesnt return date so setting to current date for now
 | 
			
		||||
            sChapter.date_upload = Date().time
 | 
			
		||||
        }
 | 
			
		||||
        return sChapter
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        return POST(baseUrl + SEARCH_URL, headers, buildRequestBody(query, page))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(response: Response): SManga {
 | 
			
		||||
        val jsonData = response.body()!!.string()
 | 
			
		||||
        val jsonObject = JsonParser().parse(jsonData).asJsonObject
 | 
			
		||||
        val results = jsonObject.getAsJsonObject("results")
 | 
			
		||||
        return parseSearch(listOf(results))[0]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> = listOf(getChapter(response))
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document) = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
 | 
			
		||||
        val mangaId = chapter.url.substringAfter("/g/").toInt()
 | 
			
		||||
 | 
			
		||||
        return client.newCall(POST(baseUrl + MANGA_URL, headers, buildIdBody(mangaId)))
 | 
			
		||||
                .asObservableSuccess()
 | 
			
		||||
                .map { response ->
 | 
			
		||||
                    pageListParse(response)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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>()
 | 
			
		||||
 | 
			
		||||
        for (i in 1..totalPages) {
 | 
			
		||||
            pages.add(Page(pages.size, "", "$imageUrl$i.jpg"))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return pages
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun buildRequestBody(searchText: String = "", page: Int = 0, sort: Int = 0): RequestBody {
 | 
			
		||||
        //in the future switch this to dtos and actually build the json.  This is just a work around for
 | 
			
		||||
        //initial release, then you can have actual tag searching etc
 | 
			
		||||
        var json = """{"search":{"text":"","page":0,"sort":0,"pages":{"range":[0,2000]},"tag":{"text":"","type":1,"tags":[],"items":{"included":[],"excluded":[]}}}}"""
 | 
			
		||||
        if (searchText.isNotEmpty()) {
 | 
			
		||||
            val encodedSearch = URLEncoder.encode(searchText, "UTF-8")
 | 
			
		||||
            json = json.replaceFirst(""""text":""""", """"text":"$encodedSearch"""")
 | 
			
		||||
        }
 | 
			
		||||
        if (page > 0) {
 | 
			
		||||
            json =  json.replaceFirst(""""page":0""", """"page":$page""")
 | 
			
		||||
        }
 | 
			
		||||
        if (sort > 0) {
 | 
			
		||||
            json = json.replaceFirst(""""sort":0""", """"sort":$sort""")
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return RequestBody.create(MEDIA_TYPE, json)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsRequest(smanga: SManga): Request {
 | 
			
		||||
        val id = smanga.url.substringAfter("/g/").toInt()
 | 
			
		||||
        return POST(baseUrl + MANGA_URL, headers, buildIdBody(id))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun buildIdBody(id: Int): RequestBody {
 | 
			
		||||
        val dto = eu.kanade.tachiyomi.extension.all.ninehentai.id(id)
 | 
			
		||||
        return RequestBody.create(MEDIA_TYPE, Gson().toJson(dto))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document): String = ""
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector(): String = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector(): String? = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesSelector(): String = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not used")
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector(): 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")
 | 
			
		||||
 | 
			
		||||
    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"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user