Update domain for NineHentai (+revert removal) (#6842)
* Revert 9bc701d65ae9493772848b5d5fd927a87b3d7fb5 (partial) * NineHentai: update domain
This commit is contained in:
parent
39ae8f5e73
commit
44ad6961d3
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".en.ninehentai.NineHentaiUrlActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="9hentai.so"
|
||||||
|
android:pathPattern="/g/..*"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,8 @@
|
||||||
|
ext {
|
||||||
|
extName = 'NineHentai'
|
||||||
|
extClass = '.NineHentai'
|
||||||
|
extVersionCode = 4
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,379 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.ninehentai
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
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.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.Buffer
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import rx.Observable
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
class NineHentai : HttpSource() {
|
||||||
|
|
||||||
|
override val baseUrl = "https://9hentai.so"
|
||||||
|
|
||||||
|
override val name = "NineHentai"
|
||||||
|
|
||||||
|
override val lang = "en"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
// Builds request for /api/getBooks endpoint
|
||||||
|
private fun buildSearchRequest(
|
||||||
|
searchText: String = "",
|
||||||
|
page: Int,
|
||||||
|
sort: Int = 0,
|
||||||
|
range: List<Int> = listOf(0, 2000),
|
||||||
|
includedTags: List<Tag> = listOf(),
|
||||||
|
excludedTags: List<Tag> = listOf(),
|
||||||
|
): Request {
|
||||||
|
val searchRequest = SearchRequest(
|
||||||
|
text = searchText,
|
||||||
|
page = page - 1, // Source starts counting from 0, not 1
|
||||||
|
sort = sort,
|
||||||
|
pages = Range(range),
|
||||||
|
tag = Items(
|
||||||
|
items = TagArrays(
|
||||||
|
included = includedTags,
|
||||||
|
excluded = excludedTags,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val jsonString = json.encodeToString(SearchRequestPayload(search = searchRequest))
|
||||||
|
return POST("$baseUrl$SEARCH_URL", headers, jsonString.toRequestBody(MEDIA_TYPE))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseSearchResponse(response: Response): MangasPage {
|
||||||
|
return response.use {
|
||||||
|
val page = json.decodeFromString<SearchRequestPayload>(it.request.bodyString).search.page
|
||||||
|
json.decodeFromString<SearchResponse>(it.body.string()).let { searchResponse ->
|
||||||
|
MangasPage(
|
||||||
|
searchResponse.results.map {
|
||||||
|
SManga.create().apply {
|
||||||
|
url = "/g/${it.id}"
|
||||||
|
title = it.title
|
||||||
|
// Cover is the compressed first page (cover might change if page count changes)
|
||||||
|
thumbnail_url = "${it.image_server}${it.id}/1.jpg?${it.total_page}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchResponse.totalCount - 1 > page,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds request for /api/getBookById endpoint
|
||||||
|
private fun buildDetailRequest(id: Int): Request {
|
||||||
|
val jsonString = buildJsonObject { put("id", id) }.toString()
|
||||||
|
return POST("$baseUrl$MANGA_URL", headers, jsonString.toRequestBody(MEDIA_TYPE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popular
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request = buildSearchRequest(page = page, sort = 1)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage = parseSearchResponse(response)
|
||||||
|
|
||||||
|
// Latest
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = buildSearchRequest(page = page)
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage = parseSearchResponse(response)
|
||||||
|
|
||||||
|
// Search
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
if (query.startsWith("id:")) {
|
||||||
|
val id = query.substringAfter("id:").toInt()
|
||||||
|
return client.newCall(buildDetailRequest(id))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
fetchSingleManga(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.fetchSearchManga(page, query, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||||
|
var sort = 0
|
||||||
|
val range = mutableListOf(0, 2000)
|
||||||
|
val includedTags = mutableListOf<Tag>()
|
||||||
|
val excludedTags = mutableListOf<Tag>()
|
||||||
|
for (filter in filterList) {
|
||||||
|
when (filter) {
|
||||||
|
is SortFilter -> {
|
||||||
|
sort = filter.state
|
||||||
|
}
|
||||||
|
is MinPagesFilter -> {
|
||||||
|
try {
|
||||||
|
range[0] = filter.state.toInt()
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
// Suppress and retain default value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is MaxPagesFilter -> {
|
||||||
|
try {
|
||||||
|
range[1] = filter.state.toInt()
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
// Suppress and retain default value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IncludedFilter -> {
|
||||||
|
includedTags += getTags(filter.state, 1)
|
||||||
|
}
|
||||||
|
is ExcludedFilter -> {
|
||||||
|
excludedTags += getTags(filter.state, 1)
|
||||||
|
}
|
||||||
|
is GroupFilter -> {
|
||||||
|
includedTags += getTags(filter.state, 2)
|
||||||
|
}
|
||||||
|
is ParodyFilter -> {
|
||||||
|
includedTags += getTags(filter.state, 3)
|
||||||
|
}
|
||||||
|
is ArtistFilter -> {
|
||||||
|
includedTags += getTags(filter.state, 4)
|
||||||
|
}
|
||||||
|
is CharacterFilter -> {
|
||||||
|
includedTags += getTags(filter.state, 5)
|
||||||
|
}
|
||||||
|
is CategoryFilter -> {
|
||||||
|
includedTags += getTags(filter.state, 6)
|
||||||
|
}
|
||||||
|
else -> { /* Do nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildSearchRequest(
|
||||||
|
searchText = query,
|
||||||
|
page = page,
|
||||||
|
sort = sort,
|
||||||
|
range = range,
|
||||||
|
includedTags = includedTags,
|
||||||
|
excludedTags = excludedTags,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage = parseSearchResponse(response)
|
||||||
|
|
||||||
|
// Manga Details
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
return SManga.create().apply {
|
||||||
|
response.asJsoup().selectFirst("div#bigcontainer")!!.let { info ->
|
||||||
|
title = info.select("h1").text()
|
||||||
|
thumbnail_url = info.selectFirst("div#cover v-lazy-image")!!.attr("abs:src")
|
||||||
|
status = SManga.COMPLETED
|
||||||
|
artist = info.selectTextOrNull("div.field-name:contains(Artist:) a.tag")
|
||||||
|
author = info.selectTextOrNull("div.field-name:contains(Group:) a.tag") ?: "Unknown circle"
|
||||||
|
genre = info.selectTextOrNull("div.field-name:contains(Tag:) a.tag")
|
||||||
|
// Additional details
|
||||||
|
description = listOf(
|
||||||
|
Pair("Alternative Title", info.selectTextOrNull("h2")),
|
||||||
|
Pair("Pages", info.selectTextOrNull("div#info > div:contains(pages)")),
|
||||||
|
Pair("Parody", info.selectTextOrNull("div.field-name:contains(Parody:) a.tag")),
|
||||||
|
Pair("Category", info.selectTextOrNull("div.field-name:contains(Category:) a.tag")),
|
||||||
|
Pair("Language", info.selectTextOrNull("div.field-name:contains(Language:) a.tag")),
|
||||||
|
).filterNot { it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}: ${it.second}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures no exceptions are thrown when scraping additional details
|
||||||
|
private fun Element.selectTextOrNull(selector: String): String? {
|
||||||
|
val list = this.select(selector)
|
||||||
|
return if (list.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
list.joinToString(", ") { it.text() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapter
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val time = response.asJsoup().select("div#info div time").text()
|
||||||
|
return listOf(
|
||||||
|
SChapter.create().apply {
|
||||||
|
name = "Chapter"
|
||||||
|
date_upload = parseChapterDate(time)
|
||||||
|
url = response.request.url.encodedPath
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseChapterDate(date: String): Long {
|
||||||
|
val dateStringSplit = date.split(" ")
|
||||||
|
val value = dateStringSplit[0].toInt()
|
||||||
|
|
||||||
|
return when (dateStringSplit[1].removeSuffix("s")) {
|
||||||
|
"sec" -> Calendar.getInstance().apply {
|
||||||
|
add(Calendar.SECOND, value * -1)
|
||||||
|
}.timeInMillis
|
||||||
|
"min" -> Calendar.getInstance().apply {
|
||||||
|
add(Calendar.MINUTE, value * -1)
|
||||||
|
}.timeInMillis
|
||||||
|
"hour" -> Calendar.getInstance().apply {
|
||||||
|
add(Calendar.HOUR_OF_DAY, value * -1)
|
||||||
|
}.timeInMillis
|
||||||
|
"day" -> Calendar.getInstance().apply {
|
||||||
|
add(Calendar.DATE, value * -1)
|
||||||
|
}.timeInMillis
|
||||||
|
"week" -> Calendar.getInstance().apply {
|
||||||
|
add(Calendar.DATE, value * 7 * -1)
|
||||||
|
}.timeInMillis
|
||||||
|
"month" -> Calendar.getInstance().apply {
|
||||||
|
add(Calendar.MONTH, value * -1)
|
||||||
|
}.timeInMillis
|
||||||
|
"year" -> Calendar.getInstance().apply {
|
||||||
|
add(Calendar.YEAR, value * -1)
|
||||||
|
}.timeInMillis
|
||||||
|
else -> {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page List
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
val mangaId = chapter.url.substringAfter("/g/").toInt()
|
||||||
|
return buildDetailRequest(mangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val resultsObj = json.parseToJsonElement(response.body.string()).jsonObject["results"]!!
|
||||||
|
val manga = json.decodeFromJsonElement<Manga>(resultsObj)
|
||||||
|
val imageUrl = manga.image_server + manga.id
|
||||||
|
var totalPages = manga.total_page
|
||||||
|
|
||||||
|
client.newCall(
|
||||||
|
GET(
|
||||||
|
"$imageUrl/preview/${totalPages}t.jpg",
|
||||||
|
headersBuilder().build(),
|
||||||
|
),
|
||||||
|
).execute().code.let { code ->
|
||||||
|
if (code == 404) totalPages--
|
||||||
|
}
|
||||||
|
|
||||||
|
return (1..totalPages).map {
|
||||||
|
Page(it - 1, "", "$imageUrl/$it.jpg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTags(queries: String, type: Int): List<Tag> {
|
||||||
|
return queries.split(",").map(String::trim)
|
||||||
|
.filterNot(String::isBlank).mapNotNull { query ->
|
||||||
|
val jsonString = buildJsonObject {
|
||||||
|
put("tag_name", query)
|
||||||
|
put("tag_type", type)
|
||||||
|
}.toString()
|
||||||
|
lookupTags(jsonString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on HentaiHand ext
|
||||||
|
private fun lookupTags(request: String): Tag? {
|
||||||
|
return client.newCall(POST("$baseUrl$TAG_URL", headers, request.toRequestBody(MEDIA_TYPE)))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.map { response ->
|
||||||
|
// Returns the first matched tag, or null if there are no results
|
||||||
|
val tagList = json.parseToJsonElement(response.body.string()).jsonObject["results"]!!.jsonArray.map {
|
||||||
|
json.decodeFromJsonElement<Tag>(it)
|
||||||
|
}
|
||||||
|
if (tagList.isEmpty()) {
|
||||||
|
return@map null
|
||||||
|
} else {
|
||||||
|
tagList.first()
|
||||||
|
}
|
||||||
|
}.toBlocking().first()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchSingleManga(response: Response): MangasPage {
|
||||||
|
val resultsObj = json.parseToJsonElement(response.body.string()).jsonObject["results"]!!
|
||||||
|
val manga = json.decodeFromJsonElement<Manga>(resultsObj)
|
||||||
|
val list = listOf(
|
||||||
|
SManga.create().apply {
|
||||||
|
setUrlWithoutDomain("/g/${manga.id}")
|
||||||
|
title = manga.title
|
||||||
|
thumbnail_url = "${manga.image_server + manga.id}/cover.jpg"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return MangasPage(list, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
|
||||||
|
private class SortFilter : Filter.Select<String>(
|
||||||
|
"Sort by",
|
||||||
|
arrayOf("Newest", "Popular Right now", "Most Fapped", "Most Viewed", "By Title"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private class MinPagesFilter : Filter.Text("Minimum Pages")
|
||||||
|
private class MaxPagesFilter : Filter.Text("Maximum Pages")
|
||||||
|
private class IncludedFilter : Filter.Text("Included Tags")
|
||||||
|
private class ExcludedFilter : Filter.Text("Excluded Tags")
|
||||||
|
private class ArtistFilter : Filter.Text("Artist")
|
||||||
|
private class GroupFilter : Filter.Text("Group")
|
||||||
|
private class ParodyFilter : Filter.Text("Parody")
|
||||||
|
private class CharacterFilter : Filter.Text("Character")
|
||||||
|
private class CategoryFilter : Filter.Text("Category")
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
Filter.Header("Search by id with \"id:\" in front of query"),
|
||||||
|
Filter.Separator(),
|
||||||
|
SortFilter(),
|
||||||
|
MinPagesFilter(),
|
||||||
|
MaxPagesFilter(),
|
||||||
|
IncludedFilter(),
|
||||||
|
ExcludedFilter(),
|
||||||
|
ArtistFilter(),
|
||||||
|
GroupFilter(),
|
||||||
|
ParodyFilter(),
|
||||||
|
CharacterFilter(),
|
||||||
|
CategoryFilter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
private val Request.bodyString: String
|
||||||
|
get() {
|
||||||
|
val requestCopy = newBuilder().build()
|
||||||
|
val buffer = Buffer()
|
||||||
|
|
||||||
|
return runCatching { buffer.apply { requestCopy.body!!.writeTo(this) }.readUtf8() }
|
||||||
|
.getOrNull() ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
|
||||||
|
private const val SEARCH_URL = "/api/getBook"
|
||||||
|
private const val MANGA_URL = "/api/getBookByID"
|
||||||
|
private const val TAG_URL = "/api/getTag"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.ninehentai
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Manga(
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val image_server: String,
|
||||||
|
val total_page: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
The basic search request JSON object looks like this:
|
||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"text": "",
|
||||||
|
"page": 1,
|
||||||
|
"sort": 1,
|
||||||
|
"pages": {
|
||||||
|
"range": [0, 2000]
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"items": {
|
||||||
|
"included": [],
|
||||||
|
"excluded": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sort = 0, Newest
|
||||||
|
Sort = 1, Popular right now
|
||||||
|
Sort = 2, Most Fapped
|
||||||
|
Sort = 3, Most Viewed
|
||||||
|
Sort = 4, By title
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchRequest(
|
||||||
|
val text: String,
|
||||||
|
val page: Int,
|
||||||
|
val sort: Int,
|
||||||
|
val pages: Range,
|
||||||
|
val tag: Items,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchRequestPayload(
|
||||||
|
val search: SearchRequest,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchResponse(
|
||||||
|
@SerialName("total_count") val totalCount: Int,
|
||||||
|
val results: List<Manga>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Range(
|
||||||
|
val range: List<Int>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Items(
|
||||||
|
val items: TagArrays,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TagArrays(
|
||||||
|
val included: List<Tag>,
|
||||||
|
val excluded: List<Tag>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Tag(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val description: String? = null,
|
||||||
|
val type: Int = 1,
|
||||||
|
)
|
|
@ -0,0 +1,38 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.ninehentai
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Springboard that accepts https://9hentai.so/g/xxxxxx intents and redirects them to
|
||||||
|
* the main Tachiyomi process.
|
||||||
|
*/
|
||||||
|
class NineHentaiUrlActivity : Activity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
val pathSegments = intent?.data?.pathSegments
|
||||||
|
if (pathSegments != null && pathSegments.size > 1) {
|
||||||
|
val id = pathSegments[1]
|
||||||
|
val mainIntent = Intent().apply {
|
||||||
|
action = "eu.kanade.tachiyomi.SEARCH"
|
||||||
|
putExtra("query", "id:$id")
|
||||||
|
putExtra("filter", packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(mainIntent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Log.e("NineHentaiUrlActivity", e.toString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e("NineHentaiUrlActivity", "could not parse uri from intent $intent")
|
||||||
|
}
|
||||||
|
|
||||||
|
finish()
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue