Remove Koushoku (#13329)

This commit is contained in:
Vetle Ledaal 2022-09-03 02:14:16 +02:00 committed by GitHub
parent 2821450458
commit b56fe66fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1 additions and 438 deletions

View File

@ -37,7 +37,7 @@ jobs:
}, },
{ {
"type": "both", "type": "both",
"regex": ".*(mangago|mangafox|hq\\s*dragon|manga\\s*host|supermangas|superhentais|union\\s*mangas|yes\\s*mangas|manhuascan|heroscan|manhwahot|leitor\\.?net|manga\\s*livre|tsuki\\s*mangas|manga\\s*yabu|mangas\\.in|mangas\\.pw|hentaikai|toptoon\\+?|cocomanga|hitomi\\.la|copymanga|neox|1manga\\.co|mangafox\\.fun|mangahere\\.onl|manga\\s*hub|mangakakalot\\.fun|manganel(?!o)|mangaonline\\.fun|mangapanda\\.onl|mangareader\\.site|mangatoday|manga\\.town|onemanga\\.info).*", "regex": ".*(mangago|mangafox|hq\\s*dragon|manga\\s*host|supermangas|superhentais|union\\s*mangas|yes\\s*mangas|manhuascan|heroscan|manhwahot|leitor\\.?net|manga\\s*livre|tsuki\\s*mangas|manga\\s*yabu|mangas\\.in|mangas\\.pw|hentaikai|toptoon\\+?|cocomanga|hitomi\\.la|copymanga|neox|1manga\\.co|mangafox\\.fun|mangahere\\.onl|manga\\s*hub|mangakakalot\\.fun|manganel(?!o)|mangaonline\\.fun|mangapanda\\.onl|mangareader\\.site|mangatoday|manga\\.town|onemanga\\.info|koushoku).*",
"ignoreCase": true, "ignoreCase": true,
"message": "{match} will not be added back as it is too difficult to maintain. Read #3475 for more information" "message": "{match} will not be added back as it is too difficult to maintain. Read #3475 for more information"
}, },

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.extension">
<application>
<activity
android:name=".en.koushoku.KoushokuUrlActivity"
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="koushoku.org"
android:pathPattern="/archive/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,12 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Koushoku'
pkgNameSuffix = 'en.koushoku'
extClass = '.Koushoku'
extVersionCode = 15
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

View File

@ -1,290 +0,0 @@
package eu.kanade.tachiyomi.extension.en.koushoku
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
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.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.random.Random
class Koushoku : ParsedHttpSource() {
companion object {
const val PREFIX_ID_SEARCH = "id:"
const val thumbnailSelector = "figure img"
const val magazinesSelector = ".metadata a[href^='/magazines/']"
private val PATTERN_IMAGES = "(.+/)(\\d+)(.*)".toRegex()
private val DATE_FORMAT = SimpleDateFormat("E, d MMM yyy HH:mm:ss 'UTC'", Locale.US)
}
override val baseUrl = "https://koushoku.org"
override val name = "Koushoku"
override val lang = "en"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(KoushokuWebViewInterceptor())
// Site: 40req per 1 minute
// Here: 1req per 2 sec -> 30req per 1 minute
// (somewhat lower due to caching)
.rateLimitHost("https://koushoku.org".toHttpUrl(), 1, 2)
.build()
override fun headersBuilder(): Headers.Builder {
val chromeStableVersion = listOf("104.0.5112.69", "103.0.5060.71", "103.0.5060.70", "103.0.5060.53", "103.0.5060.129", "102.0.5005.99", "102.0.5005.98", "102.0.5005.78", "102.0.5005.125").random()
val chromeCanaryVersion = listOf("106.0.5227.0", "106.0.5209.0", "106.0.5206.0", "106.0.5201.2", "106.0.5201.0", "106.0.5200.0", "106.0.5199.0", "106.0.5197.0", "106.0.5196.0", "105.0.5195.2", "105.0.5194.0", "105.0.5193.0", "105.0.5192.0", "105.0.5191.0", "105.0.5190.0", "105.0.5189.0", "105.0.5186.0", "105.0.5185.0", "105.0.5184.0", "105.0.5182.0", "105.0.5180.0", "105.0.5179.3", "105.0.5178.0", "105.0.5177.2", "105.0.5176.0", "105.0.5175.0", "105.0.5174.0", "105.0.5173.0", "105.0.5172.0", "105.0.5171.0").random()
val chromeVersion = if (Random.nextFloat() > 0.2) chromeStableVersion else chromeCanaryVersion
val deviceInfo = if (Random.nextFloat() > 0.2) "" else "; " + listOf("SM-S908B", "SM-S908U", "SM-A536B", "SM-A536U", "SM-S901B", "SM-S901U", "SM-A736B", "SM-G973F", "SM-A528B", "SM-G975U", "SM-G990B", "SM-G990U").random()
val androidVersion = IntRange(if (deviceInfo.isEmpty()) 9 else 11, 12).random()
return super.headersBuilder()
.set("User-Agent", "Mozilla/5.0 (Linux; Android $androidVersion$deviceInfo) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/$chromeVersion Mobile Safari/537.36")
.add("Referer", "$baseUrl/")
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
override fun latestUpdatesSelector() = "#archives.feed .entries > .entry"
override fun latestUpdatesNextPageSelector() = "footer nav li:has(a.active) + li:not(:last-child) > a"
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
setUrlWithoutDomain(element.selectFirst("a").attr("href"))
title = element.selectFirst("[title]").attr("title")
thumbnail_url = element.selectFirst(thumbnailSelector).absUrl("src")
}
private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/archive/$id", headers)
// taken from Tsumino ext
private fun searchMangaByIdParse(response: Response, id: String): MangasPage {
val details = mangaDetailsParse(response)
details.url = "/archive/$id"
return MangasPage(listOf(details), false)
}
// taken from Tsumino ext
override fun fetchSearchManga(
page: Int,
query: String,
filters: FilterList
): Observable<MangasPage> {
return if (query.startsWith(PREFIX_ID_SEARCH)) {
val id = query.removePrefix(PREFIX_ID_SEARCH)
client.newCall(searchMangaByIdRequest(id)).asObservableSuccess()
.map { response -> searchMangaByIdParse(response, id) }
} else {
super.fetchSearchManga(page, query, filters)
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/search".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("page", page.toString())
val filterList = if (filters.isEmpty()) getFilterList() else filters
filterList.findInstance<SortFilter>()?.addQueryParameter(url)
url.addQueryParameter("q", buildAdvQuery(query, filterList))
return GET(url.toString(), headers)
}
private fun buildAdvQuery(query: String, filterList: FilterList): String {
val title = if (query.isNotBlank()) "title*:\"$query\" " else ""
val filters: List<String> = filterList.filterIsInstance<Filter.Text>().map { filter ->
if (filter.state.isBlank()) return@map ""
val included = mutableListOf<String>()
val excluded = mutableListOf<String>()
val name = if (filter.name.lowercase().contentEquals("tags")) "tag" else filter.name.lowercase()
filter.state.split(",").map(String::trim).filterNot(String::isBlank).forEach { entry ->
if (entry.startsWith("-")) {
excluded.add(entry.slice(1 until entry.length))
} else {
included.add(entry)
}
}
buildString {
if (included.isNotEmpty()) append("$name&*:\"${included.joinToString(",")}\" ")
if (excluded.isNotEmpty()) append("-$name&*:\"${excluded.joinToString(",")}\"")
}
}
return "$title${
filters.filterNot(String::isBlank).joinToString(" ", transform = String::trim)
}"
}
override fun searchMangaSelector() = latestUpdatesSelector()
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularMangaRequest(page: Int) = GET("$baseUrl/popular?page=$page", headers)
override fun popularMangaSelector() = latestUpdatesSelector()
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return if (!manga.initialized) {
super.fetchMangaDetails(manga)
} else {
Observable.just(manga)
}
}
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst(".metadata > h1").text()
// Reuse cover from browse
thumbnail_url = document.selectFirst(thumbnailSelector).absUrl("src")
.replace(Regex("/\\d+\\.webp\$"), "/288.webp")
artist = document.select(".metadata a[href^='/artists/'], .metadata a[href^='/circles/']")
.joinToString { it.text() }
author = artist
genre = document.select(".metadata .tags a, $magazinesSelector")
.ifEmpty { null }?.joinToString { it.text() }
description = getDesc(document)
status = SManga.COMPLETED
}
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return listOf(
SChapter.create().apply {
setUrlWithoutDomain(response.request.url.encodedPath)
name = "Chapter"
val dateText = document.select("tr > td:first-child:contains(Uploaded Date) + td")
.text()
date_upload = runCatching { DATE_FORMAT.parse(dateText) }
.getOrNull()
?.time
?: 0
}
)
}
override fun chapterFromElement(element: Element) =
throw UnsupportedOperationException("Not used")
override fun chapterListSelector() = throw UnsupportedOperationException("Not used")
override fun pageListRequest(chapter: SChapter) = GET("$baseUrl${chapter.url}/1", headers)
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val totalPages = document.selectFirst(".total")?.text()?.toInt() ?: 0
if (totalPages == 0)
throw UnsupportedOperationException("Error: Empty pages (try Webview)")
val match = PATTERN_IMAGES.find(response.request.url.toString())!!
val prefix = match.groupValues[1]
val suffix = match.groupValues[3]
return (1..totalPages).map {
Page(it, "$prefix$it$suffix")
}
}
override fun pageListParse(document: Document): List<Page> =
throw UnsupportedOperationException("Not used")
override fun imageUrlParse(document: Document): String =
document.selectFirst(".main img, main img").absUrl("src")
override fun getFilterList() = FilterList(
SortFilter(
"Sort",
arrayOf(
Sortable("ID", "id"),
Sortable("Title", "title"),
Sortable("Created Date", "created_at"),
Sortable("Uploaded Date", "published_at"),
Sortable("Pages", "pages"),
)
),
Filter.Header("Separate tags with commas (,)"),
Filter.Header("Prepend with dash (-) to exclude"),
ArtistFilter(),
CircleFilter(),
MagazineFilter(),
ParodyFilter(),
TagFilter(),
PagesFilter()
)
// Adapted from Mangadex ext
class SortFilter(displayName: String, private val sortables: Array<Sortable>) :
Filter.Sort(
displayName,
sortables.map(Sortable::title).toTypedArray(),
Selection(2, false)
) {
fun addQueryParameter(url: HttpUrl.Builder) {
if (state != null) {
val sort = sortables[state!!.index].value
val order = when (state!!.ascending) {
true -> "asc"
false -> "desc"
}
url.addQueryParameter("sort", sort)
url.addQueryParameter("order", order)
}
}
}
data class Sortable(val title: String, val value: String) {
override fun toString(): String = title
}
class ArtistFilter : Filter.Text("Artist")
class CircleFilter : Filter.Text("Circle")
class MagazineFilter : Filter.Text("Magazine")
class ParodyFilter : Filter.Text("Parody")
class TagFilter : Filter.Text("Tags")
class PagesFilter : Filter.Text("Pages")
// Taken from nhentai ext
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
private fun getDesc(document: Document) = buildString {
val magazines = document.select(magazinesSelector)
if (magazines.isNotEmpty()) {
append("Magazines: ")
append(magazines.joinToString { it.text() })
append("\n")
}
val parodies = document.select(".metadata a[href^='/parodies/']")
if (parodies.isNotEmpty()) {
append("Parodies: ")
append(parodies.joinToString { it.text() })
append("\n")
}
val pages = document.selectFirst("tr > td:first-child:contains(Pages) + td")
append("Pages: ").append(pages.text()).append("\n")
val size: Element? = document.selectFirst("tr > td:first-child:contains(Size) + td")
append("Size: ").append(size?.text() ?: "Unknown")
}
}

View File

@ -1,38 +0,0 @@
package eu.kanade.tachiyomi.extension.en.koushoku
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://koushoku.org/archive/xxxxx intents and redirects them to
* the main Tachiyomi process.
*/
class KoushokuUrlActivity : 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", "${Koushoku.PREFIX_ID_SEARCH}$id")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("KoushokuUrlActivity", e.toString())
}
} else {
Log.e("KoushokuUrlActivity", "could not parse uri from intent $intent")
}
finish()
exitProcess(0)
}
}

View File

@ -1,73 +0,0 @@
package eu.kanade.tachiyomi.extension.en.koushoku
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.webkit.WebView
import android.webkit.WebViewClient
import okhttp3.Interceptor
import okhttp3.Response
import org.jsoup.Jsoup
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
import java.util.concurrent.CountDownLatch
class KoushokuWebViewInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if (response.headers("Content-Type").any { it.contains("text/html") }) {
val responseBody = response.peekBody(1 * 1024 * 1024).string()
if (response.code == 403) {
val document = Jsoup.parse(responseBody)
if (document.selectFirst("h1")?.text()?.contains(Regex("banned$")) == true) {
throw IOException("You have been banned. Check WebView for details.")
}
}
if (response.networkResponse != null) {
try {
proceedWithWebView(response, responseBody)
} catch (e: Exception) {
throw IOException(e)
}
}
}
return response
}
private fun proceedWithWebView(response: Response, responseBody: String) {
val latch = CountDownLatch(1)
val handler = Handler(Looper.getMainLooper())
handler.post {
val webView = WebView(Injekt.get<Application>())
with(webView.settings) {
loadsImagesAutomatically = false
userAgentString = response.request.header("User-Agent")
}
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
webView.stopLoading()
webView.destroy()
latch.countDown()
}
}
webView.loadDataWithBaseURL(
response.request.url.toString(),
responseBody,
"text/html",
"utf-8",
null
)
}
latch.await()
}
}