diff --git a/build.gradle b/build.gradle index 7caaa82fb..d80c49aca 100644 --- a/build.gradle +++ b/build.gradle @@ -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" } } diff --git a/src/all/ninehentai/build.gradle b/src/all/ninehentai/build.gradle new file mode 100644 index 000000000..fb22dcaa5 --- /dev/null +++ b/src/all/ninehentai/build.gradle @@ -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" diff --git a/src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..1fa8c130b Binary files /dev/null and b/src/all/ninehentai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..45a458885 Binary files /dev/null and b/src/all/ninehentai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..227b84683 Binary files /dev/null and b/src/all/ninehentai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..2d854fe03 Binary files /dev/null and b/src/all/ninehentai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..93bd7ff8b Binary files /dev/null and b/src/all/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/ninehentai/res/web_hi_res_512.png b/src/all/ninehentai/res/web_hi_res_512.png new file mode 100644 index 000000000..ac94bbdf8 Binary files /dev/null and b/src/all/ninehentai/res/web_hi_res_512.png 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 new file mode 100644 index 000000000..c9966f905 --- /dev/null +++ b/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/Dto.kt @@ -0,0 +1,5 @@ +package eu.kanade.tachiyomi.extension.all.ninehentai + +data class id( + val id : Int +) \ No newline at end of file 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 new file mode 100644 index 000000000..f0327ba88 --- /dev/null +++ b/src/all/ninehentai/src/eu/kanade/tachiyomi/extension/all/ninehentai/NineHentai.kt @@ -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 { + 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 { + return client.newCall(latestUpdatesRequest(page)) + .asObservableSuccess() + .map { response -> + latestUpdatesParse(response) + } + } + + override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) + + private fun getMangaList(response: Response): List { + 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): List { + val mutableList = mutableListOf() + 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> { + 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 { + 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 = listOf(getChapter(response)) + + override fun pageListParse(document: Document) = throw Exception("Not used") + + override fun fetchPageList(chapter: SChapter): Observable> { + 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 { + 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() + + 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" + } +} \ No newline at end of file