Twi4: Add URL intent and revert #10368 (#10725)

* Add URL intent

* A better pathPattern to reduce accidentally trigger the intent

* Explicitly ignore zadankai submissions

* Fixed set url after search by slug

* Reverts #10368

* Fixes to some manga entries where failsafe is not needed
This commit is contained in:
ringosham 2022-02-06 01:26:54 +00:00 committed by GitHub
parent f0abcbb5c6
commit 37586ead8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 131 additions and 9 deletions

View File

@ -1,2 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" /> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.extension">
<application>
<activity
android:name=".ja.twi4.Twi4Activity"
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="sai-zen-sen.jp"
android:pathPattern="/comics/twi4/..*/.*"
android:scheme="https"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -6,7 +6,7 @@ ext {
extName = 'Twi4' extName = 'Twi4'
pkgNameSuffix = 'ja.twi4' pkgNameSuffix = 'ja.twi4'
extClass = '.Twi4' extClass = '.Twi4'
extVersionCode = 2 extVersionCode = 3
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -9,6 +9,10 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -23,6 +27,11 @@ class Twi4 : HttpSource() {
override val name: String = "Twi4" override val name: String = "Twi4"
override val supportsLatest: Boolean = false override val supportsLatest: Boolean = false
private val application: Application by injectLazy() private val application: Application by injectLazy()
private val validPageTest: Regex = Regex("/comics/twi4/\\w+/works/\\d{4}\\.[0-9a-f]{32}\\.jpg")
companion object Constants {
const val SEARCH_PREFIX_SLUG = "SLUG:"
}
private fun getUrlDomain(): String = baseUrl.substring(0, 22) private fun getUrlDomain(): String = baseUrl.substring(0, 22)
@ -80,6 +89,16 @@ class Twi4 : HttpSource() {
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
val document = Jsoup.parse(response.body?.string()) val document = Jsoup.parse(response.body?.string())
return SManga.create().apply { return SManga.create().apply {
// We need to get the title and thumbnail again.
// This is only needed if you search by slug, as we have no information about the them.
// Interestingly the page body has no mention of the title at all. It only exists in <title>
val titleRegex = Regex("『(.+)』.+ \\| ツイ4 \\| 最前線")
val match = titleRegex.matchEntire(document.title())
title = match?.groups?.get(1)?.value.toString()
// Twi4 uses the exact same thumbnail at both the main page and manga details
thumbnail_url =
getUrlDomain() + document.select("#introduction > header > div > h2 > img")
.attr("src")
description = description =
document.select("#introduction > div > div > p").text() document.select("#introduction > div > div > p").text()
// Determine who are the authors and artists // Determine who are the authors and artists
@ -158,10 +177,45 @@ class Twi4 : HttpSource() {
// There should only be 1 article in the document // There should only be 1 article in the document
val page = doc.select("article.comic:first-child") val page = doc.select("article.comic:first-child")
val ret = mutableListOf<Page>() val ret = mutableListOf<Page>()
// The image url *in most cases* supposed to look like this /comic/twi4/comicName/works/pageNumber.suffix.jpg
// The noscript page broke the image links in a few mangas and they don't come with the suffix
// In this case we need to request an index file and obtain the file suffix
var imageUrl: String = getUrlDomain() + page.select("div > div > p > img").attr("src")
if (!validPageTest.matches(page.select("div > div > p > img").attr("src"))) {
val requestUrl = response.request.url.toUrl().toString()
val chapterNum = requestUrl.substringAfterLast("/").take(4).toInt()
// The index file contains everything about each image. Usually we can find the file name directly from the document
// This is a failsafe
val indexResponse = client.newCall(
GET(
requestUrl.substringBeforeLast("/") + "/index.js",
getChromeHeaders()
)
).execute()
if (!indexResponse.isSuccessful)
throw Exception("Failed to find pages!")
// We got a JS file that looks very much like a JSON object
// A few string manipulation and we can parse the whole thing as JSON!
val re = Regex("([A-z]+):")
var index = indexResponse.body?.string()?.substringAfter("=")?.dropLast(1)
index = index?.let { re.replace(it, "\"$1\":") }
indexResponse.close()
val indexElement = index?.let { Json.parseToJsonElement(it) }
var suffix: String? = null
if (indexElement != null) {
// Each entry in the Items array corresponds to 1 chapter/page
suffix = indexElement.jsonObject["Items"]?.jsonArray?.get(chapterNum - 1)?.jsonObject?.get("Suffix")?.jsonPrimitive?.content
}
// Twi4's image links are a bit of a mess
// Because in very rare cases, the image filename *doesn't* come with a suffix
// So only attach the suffix if there is one
if (suffix != null)
imageUrl = getUrlDomain() + page.select("div > div > p > img").attr("src").dropLast(4) + suffix + ".jpg"
}
ret.add( ret.add(
Page( Page(
index = page.select("header > div > h3 > span.number").text().toInt(), index = page.select("header > div > h3 > span.number").text().toInt(),
imageUrl = getUrlDomain() + page.select("div > div > p > img").attr("src") imageUrl = imageUrl
) )
) )
return ret return ret
@ -173,12 +227,34 @@ class Twi4 : HttpSource() {
page: Int, page: Int,
query: String, query: String,
filters: FilterList filters: FilterList
): Observable<MangasPage> = fetchPopularManga(page).map { mp -> ): Observable<MangasPage> {
mp.copy( if (query.startsWith(SEARCH_PREFIX_SLUG)) {
mp.mangas.filter { val slug = query.drop(SEARCH_PREFIX_SLUG.length)
it.title.contains(query, true) // Explicitly ignore anything that ends with .html or starts with zadankai
} // These will include the completed manga page, about page and zadankai submissions
) // For reasons to exclude zadankai, see parsePopularMangaRequest()
// There will still be some urls that would accidentally activate the intent (like the news page),
// but there's no way to avoid it.
if (slug.endsWith("html") || slug.startsWith("zadankai"))
return Observable.just(MangasPage(listOf(), false))
return client.newCall(GET(baseUrl + slug))
.asObservableSuccess()
.map { response -> searchMangaSlug(response, slug) }
}
return fetchPopularManga(page).map { mp ->
mp.copy(
mp.mangas.filter {
it.title.contains(query, true)
}
)
}
}
private fun searchMangaSlug(response: Response, slug: String): MangasPage {
val details = mangaDetailsParse(response)
details.setUrlWithoutDomain(baseUrl + slug)
return MangasPage(listOf(details), false)
} }
// All these functions are unused // All these functions are unused

View File

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.extension.ja.twi4
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import kotlin.system.exitProcess
class Twi4Activity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 2) {
val slug = pathSegments[2]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${Twi4.SEARCH_PREFIX_SLUG}$slug")
putExtra("filter", packageName)
}
startActivity(mainIntent)
}
finish()
exitProcess(0)
}
}