diff --git a/src/it/hentaifantasy/build.gradle b/src/it/hentaifantasy/build.gradle new file mode 100644 index 000000000..9c51210df --- /dev/null +++ b/src/it/hentaifantasy/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: HentaiFantasy' + pkgNameSuffix = 'it.hentaifantasy' + extClass = '.HentaiFantasy' + extVersionCode = 1 + extVersionSuffix = 1 + libVersion = '1.0' +} + +apply from: "$rootDir/common.gradle" \ No newline at end of file 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 new file mode 100644 index 000000000..13b4d329c --- /dev/null +++ b/src/it/hentaifantasy/src/eu/kanade/tachiyomi/extension/it/hentaifantasy/HentaiFantasy.kt @@ -0,0 +1,242 @@ +package eu.kanade.tachiyomi.extension.it.hentaifantasy + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +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 java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.regex.Pattern + +class HentaiFantasy : ParsedHttpSource() { + override val name = "HentaiFantasy" + + override val baseUrl = "http://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 { + var tags = mutableListOf() + var paths = mutableListOf() + 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) } + } + } + + var 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) + } + } + } + + var 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() + var genres = mutableListOf() + 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 if (date == "Oggi") { + Calendar.getInstance().timeInMillis + } else if (date == "Ieri") { + Calendar.getInstance().apply { + add(Calendar.DAY_OF_YEAR, -1) + }.timeInMillis + } else { + try { + dateFormat.parse(date).time + } catch (e: ParseException) { + 0L + } + } + } + + override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers) + + override fun pageListParse(response: Response): List { + val body = response.body().string() + val pages = mutableListOf() + + 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 { + 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) : Filter.Group(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 http://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) + ) +} \ No newline at end of file