Manga Ship extension, DataImageInterceptor (#3177)
* Manga Ship extension, DataImageInterceptor * tweak regex
This commit is contained in:
parent
af17930c45
commit
7e9bb52cbc
|
@ -0,0 +1,30 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 27
|
||||||
|
buildToolsVersion '29.0.3'
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 27
|
||||||
|
versionCode 1
|
||||||
|
versionName '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
compileOnly 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
|
compileOnly 'org.jsoup:jsoup:1.13.1'
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="eu.kanade.tachiyomi.lib.dataimage" />
|
|
@ -0,0 +1,65 @@
|
||||||
|
package eu.kanade.tachiyomi.lib.dataimage
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.Protocol
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a source provides images via a data:image string instead of a URL, use these functions and interceptor
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use if the attribute tag could have a data:image string or URL
|
||||||
|
* Transforms data:image in to a fake URL that OkHttp won't die on
|
||||||
|
*/
|
||||||
|
fun Element.dataImageAsUrl(attr: String): String {
|
||||||
|
return if (this.attr(attr).startsWith("data")) {
|
||||||
|
"https://127.0.0.1/?" + this.attr(attr).substringAfter(":")
|
||||||
|
} else {
|
||||||
|
this.attr("abs:$attr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use if the attribute tag has a data:image string but real URLs are on a different attribute
|
||||||
|
*/
|
||||||
|
fun Element.dataImageAsUrlOrNull(attr: String): String? {
|
||||||
|
return if (this.attr(attr).startsWith("data")) {
|
||||||
|
"https://127.0.0.1/?" + this.attr(attr).substringAfter(":")
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interceptor that detects the URLs we created with the above functions, base64 decodes the data if necessary,
|
||||||
|
* and builds a response with a valid image that Tachiyomi can display
|
||||||
|
*/
|
||||||
|
class DataImageInterceptor : Interceptor {
|
||||||
|
private val mediaTypePattern = Regex("""(^[^;,]*)[;,]""")
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val url = chain.request().url().toString()
|
||||||
|
return if (url.startsWith("https://127.0.0.1/?image")) {
|
||||||
|
val dataString = url.substringAfter("?")
|
||||||
|
val byteArray = if (dataString.contains("base64")) {
|
||||||
|
Base64.decode(dataString.substringAfter("base64,"), Base64.DEFAULT)
|
||||||
|
} else {
|
||||||
|
dataString.substringAfter(",").toByteArray()
|
||||||
|
}
|
||||||
|
val mediaType = MediaType.parse(mediaTypePattern.find(dataString)!!.value)
|
||||||
|
Response.Builder().body(ResponseBody.create(mediaType, byteArray))
|
||||||
|
.request(chain.request())
|
||||||
|
.protocol(Protocol.HTTP_1_0)
|
||||||
|
.code(200)
|
||||||
|
.message("")
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
chain.proceed(chain.request())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,9 @@ project(':lib-ratelimit').projectDir = new File("lib/ratelimit")
|
||||||
include ':duktape-stub'
|
include ':duktape-stub'
|
||||||
project(':duktape-stub').projectDir = new File("lib/duktape-stub")
|
project(':duktape-stub').projectDir = new File("lib/duktape-stub")
|
||||||
|
|
||||||
|
include ':lib-dataimage'
|
||||||
|
project(':lib-dataimage').projectDir = new File("lib/dataimage")
|
||||||
|
|
||||||
new File(rootDir, "src").eachDir { dir ->
|
new File(rootDir, "src").eachDir { dir ->
|
||||||
dir.eachDir { subdir ->
|
dir.eachDir { subdir ->
|
||||||
String name = ":${dir.name}-${subdir.name}"
|
String name = ":${dir.name}-${subdir.name}"
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
appName = 'Tachiyomi: Manga Ship'
|
||||||
|
pkgNameSuffix = 'tr.mangaship'
|
||||||
|
extClass = '.MangaShip'
|
||||||
|
extVersionCode = 1
|
||||||
|
libVersion = '1.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':lib-dataimage')
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
|
@ -0,0 +1,107 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.tr.mangaship
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
|
||||||
|
import eu.kanade.tachiyomi.lib.dataimage.dataImageAsUrl
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
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 okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
class MangaShip : ParsedHttpSource() {
|
||||||
|
|
||||||
|
override val name = "Manga Ship"
|
||||||
|
|
||||||
|
override val baseUrl = "https://www.mangaship.com"
|
||||||
|
|
||||||
|
override val lang = "tr"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
|
.addInterceptor(DataImageInterceptor())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Popular
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/Tr/PopulerMangalar?page=$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = "div.movie-item-contents"
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
|
return SManga.create().apply {
|
||||||
|
element.select("div.movie-item-title a").let {
|
||||||
|
title = it.text()
|
||||||
|
setUrlWithoutDomain(it.attr("href"))
|
||||||
|
}
|
||||||
|
thumbnail_url = element.select("img").attr("abs:src")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector() = "li.active + li a:not(.lastpage)"
|
||||||
|
|
||||||
|
// Latest
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/Tr/YeniMangalar?page=$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = popularMangaSelector()
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
// Search
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
return GET("$baseUrl/Tr/Search?kelime=$query&tur=Manga&page=$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
// Details
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
|
return SManga.create().apply {
|
||||||
|
thumbnail_url = document.select("div.dec-review-img img").attr("abs:src")
|
||||||
|
genre = document.select("div.col-md-10 li:contains(Kategori) div a").joinToString { it.text() }
|
||||||
|
author = document.select("div.col-md-10 li:contains(Yazar) div a").text()
|
||||||
|
description = document.select("div.details-dectiontion p").joinToString("\n") { it.text() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters
|
||||||
|
|
||||||
|
override fun chapterListSelector() = "div.item > div"
|
||||||
|
|
||||||
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
|
return SChapter.create().apply {
|
||||||
|
element.select("div.plylist-single-content > a[title]").let {
|
||||||
|
name = it.text()
|
||||||
|
setUrlWithoutDomain(it.attr("href"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
|
||||||
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
|
return document.select("div.reading-content img").mapIndexed { i, img ->
|
||||||
|
Page(i, "", img.dataImageAsUrl("src"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
||||||
|
}
|
Loading…
Reference in New Issue