diff --git a/src/en/hbrowse/AndroidManifest.xml b/src/en/hbrowse/AndroidManifest.xml
deleted file mode 100644
index 8072ee00d..000000000
--- a/src/en/hbrowse/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/src/en/hbrowse/build.gradle b/src/en/hbrowse/build.gradle
deleted file mode 100644
index f298f4b1a..000000000
--- a/src/en/hbrowse/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-
-ext {
- extName = 'HBrowse'
- pkgNameSuffix = 'en.hbrowse'
- extClass = '.HBrowse'
- extVersionCode = 7
- isNsfw = 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 f317b2733..000000000
--- a/src/en/hbrowse/src/eu/kanade/tachiyomi/extension/en/hbrowse/HBrowse.kt
+++ /dev/null
@@ -1,262 +0,0 @@
-package eu.kanade.tachiyomi.extension.en.hbrowse
-
-import android.app.Application
-import android.content.SharedPreferences
-import androidx.preference.CheckBoxPreference
-import androidx.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 kotlinx.serialization.decodeFromString
-import kotlinx.serialization.json.Json
-import okhttp3.CookieJar
-import okhttp3.FormBody
-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 uy.kohesive.injekt.injectLazy
-import java.io.IOException
-
-class HBrowse : ParsedHttpSource(), ConfigurableSource {
-
- override val name = "HBrowse"
-
- override val baseUrl = "https://www.hbrowse.com"
-
- override val lang = "en"
-
- override val supportsLatest = true
-
- private val json: Json by injectLazy()
-
- // 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()
- .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() = if (!hbrowseOnlyChapters()) "h2:contains(read manga online) + table tr" else "h2:contains(read manga online) + table tr:contains(chapter)"
-
- override fun chapterListParse(response: Response): List {
- return super.chapterListParse(response).reversed()
- }
-
- override fun chapterFromElement(element: Element): SChapter {
- return SChapter.create().apply {
- name = element.select("td:first-of-type").text()
- setUrlWithoutDomain(element.select("a.listLink").attr("href"))
- }
- }
-
- // Pages
-
- override fun pageListParse(document: Document): List {
- val script = document.select("script:containsData(imageDir)").first()!!.data()
- val imageDir = Regex("""imageDir\s*=\s*"(.+?)";""").find(script)?.groupValues!![1]
- var images = json.decodeFromString>(Regex("""list\s*=\s*(\[.+?]);""").find(script)?.groupValues!![1])
- images = images.subList(0, images.size - 2)
- return images.mapIndexed { index, element -> Page(index, "", baseUrl + imageDir + element) }
- }
-
- override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
-
- // 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].lowercase() + groupName.drop(1).replace(" ", "")}_$name"
- fun formValue() = when {
- this.isIncluded() -> "y"
- this.isExcluded() -> "n"
- else -> ""
- }
- }
- private class AdvancedFilter(val vals: List) : Filter.Group(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) = vals.map { AdvTriStateFilter(groupName, it) }
-
- // Preferences
-
- private val preferences: SharedPreferences by lazy {
- Injekt.get().getSharedPreferences("source_$id", 0x0000)
- }
-
- companion object {
- private const val ONLYCHAPTERS_PREF_KEY = "HBROWSE_ONLYCHAPTERS"
- private const val ONLYCHAPTERS_PREF_TITLE = "Only show chapters"
- private const val ONLYCHAPTERS_PREF_SUMMARY = "Only show chapters and not the cover/final pages"
- private const val ONLYCHAPTERS_PREF_DEFAULT_VALUE = false
- }
-
- override fun setupPreferenceScreen(screen: PreferenceScreen) {
- val onlychaptersPref = CheckBoxPreference(screen.context).apply {
- key = "${ONLYCHAPTERS_PREF_KEY}_$lang"
- title = ONLYCHAPTERS_PREF_TITLE
- summary = ONLYCHAPTERS_PREF_SUMMARY
- setDefaultValue(ONLYCHAPTERS_PREF_DEFAULT_VALUE)
-
- setOnPreferenceChangeListener { _, newValue ->
- val checkValue = newValue as Boolean
- preferences.edit().putBoolean("${ONLYCHAPTERS_PREF_KEY}_$lang", checkValue).commit()
- }
- }
- screen.addPreference(onlychaptersPref)
- }
-
- private fun hbrowseOnlyChapters(): Boolean = preferences.getBoolean("${ONLYCHAPTERS_PREF_KEY}_$lang", ONLYCHAPTERS_PREF_DEFAULT_VALUE)
-}