Guess whos back (#5184)

non scanlator removals requests are
This commit is contained in:
Carlos 2020-12-18 16:10:19 -05:00 committed by GitHub
parent 81f3557be1
commit c7b49fa82c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 633 additions and 2 deletions

View File

@ -76,4 +76,4 @@ dependencies {
}
preBuild.dependsOn(lintKotlin)
lintKotlin.dependsOn(formatKotlin)
lintKotlin.dependsOn(formatKotlin)

View File

@ -5,7 +5,7 @@ ext {
extName = 'Madara (multiple sources)'
pkgNameSuffix = "all.madara"
extClass = '.MadaraFactory'
extVersionCode = 159
extVersionCode = 160
libVersion = '1.2'
containsNsfw = true
}

View File

@ -116,6 +116,7 @@ class MadaraFactory : SourceFactory {
MangaScantrad(),
MangaSco(),
MangaSpark(),
Mangastein()
MangaStarz(),
MangaSY(),
MangaTX(),
@ -174,6 +175,7 @@ class MadaraFactory : SourceFactory {
RenaScans(),
RuyaManga(),
S2Manga(),
Skymanga(),
SpookyScanlations(),
StageComics(),
TheTopComic(),
@ -987,6 +989,8 @@ class MangaWT : Madara("MangaWT", "https://mangawt.com", "tr")
class DecadenceScans : Madara("Decadence Scans", "https://reader.decadencescans.com", "en")
class MangaStein : Madara("MangaStein", "https://mangastein.com", "tr")
class MangaRockTeam : Madara("Manga Rock Team", "https://mangarockteam.com", "en")
class MixedManga : Madara("Mixed Manga", "https://mixedmanga.com", "en", SimpleDateFormat("d MMM yyyy", Locale.US)) {
@ -1298,6 +1302,8 @@ class AkuManga : Madara("AkuManga", "https://akumanga.com", "ar")
class AsgardTeam : Madara("Asgard Team", "https://www.asgard1team.com", "ar")
class Skymanga : Madara("Skymanga", "https://skymanga.co", "en")
@Nsfw
class ToonilyNet : Madara("Toonily.net", "https://toonily.net", "en")

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".HentaiNexusActivity"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true">
<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:scheme="https"
android:host="hentainexus.com"
android:pathPattern="/view/..*"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: HentaiNexus'
pkgNameSuffix = 'en.hentainexus'
extClass = '.HentaiNexus'
extVersionCode = 4
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,252 @@
package eu.kanade.tachiyomi.extension.en.hentainexus
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.network.GET
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.ParsedHttpSource
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import java.net.URLEncoder
import android.util.Base64
import kotlin.experimental.xor
import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
@Nsfw
class HentaiNexus : ParsedHttpSource() {
override val name = "HentaiNexus"
override val baseUrl = "https://hentainexus.com"
override val lang = "en"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override fun latestUpdatesSelector() = "div.container div.column"
override fun latestUpdatesRequest(page: Int) = pagedRequest("$baseUrl/", page)
override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create()
val item = element.select("div.column a")
manga.url = item.attr("href")
manga.title = item.text()
manga.thumbnail_url = element.select("figure.image > img").attr("src")
return manga
}
override fun latestUpdatesNextPageSelector() = "nav.pagination > a.pagination-next"
override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page)
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularMangaSelector() = latestUpdatesSelector()
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
var url: String? = null
var queryString: String? = null
fun requireNoUrl() = require(url == null && queryString == null) {
"You cannot combine filters or use text search with filters!"
}
filters.findInstance<ArtistFilter>()?.let { f ->
if (f.state.isNotBlank()) {
requireNoUrl()
url = "/"
queryString = "q=artist:%22${URLEncoder.encode(f.state, "UTF-8")}%22"
}
}
filters.findInstance<TagFilter>()?.let { f ->
if (f.state.isNotBlank()) {
requireNoUrl()
url = "/"
queryString = "q=tag:%22${URLEncoder.encode(f.state, "UTF-8")}%22"
}
}
if (query.isNotBlank()) {
requireNoUrl()
url = "/"
queryString = "q=" + URLEncoder.encode(query, "UTF-8")
}
return url?.let {
pagedRequest("$baseUrl$url", page, queryString)
} ?: latestUpdatesRequest(page)
}
private fun pagedRequest(url: String, page: Int, queryString: String? = null): Request {
// The site redirects page 1 -> url-without-page so we do this redirect early for optimization
val builtUrl = if (page == 1) url else "${url}page/$page"
return GET(if (queryString != null) "$builtUrl?$queryString" else builtUrl)
}
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(GET("$baseUrl/view/$id", headers)).asObservableSuccess()
.map { MangasPage(listOf(mangaDetailsParse(it).apply { url = "/view/$id" }), false) }
} else {
super.fetchSearchManga(page, query, filters)
}
}
override fun searchMangaSelector() = latestUpdatesSelector()
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun mangaDetailsRequest(manga: SManga): Request {
if (manga.url.startsWith("http")) {
return GET(manga.url, headers)
}
return super.mangaDetailsRequest(manga)
}
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div.column")
val manga = SManga.create()
val genres = mutableListOf<String>()
document.select("td.viewcolumn:containsOwn(Tags) + td a").forEach { element ->
val genre = element.text()
genres.add(genre)
}
manga.title = infoElement.select("h1").text()
manga.author = infoElement.select("td.viewcolumn:containsOwn(Artist) + td").text()
manga.artist = infoElement.select("td.viewcolumn:containsOwn(Artist) + td").text()
manga.status = SManga.COMPLETED
manga.genre = genres.joinToString(", ")
manga.description = getDesc(document)
manga.thumbnail_url = document.select("figure.image > img").attr("src")
return manga
}
private fun getDesc(document: Document): String {
val infoElement = document.select("div.column")
val stringBuilder = StringBuilder()
val description = infoElement.select("td.viewcolumn:containsOwn(Description) + td").text()
val magazine = infoElement.select("td.viewcolumn:containsOwn(Magazine) + td").text()
val parodies = infoElement.select("td.viewcolumn:containsOwn(Parody) + td").text()
val publisher = infoElement.select("td.viewcolumn:containsOwn(Publisher) + td").text()
val pagess = infoElement.select("td.viewcolumn:containsOwn(Pages) + td").text()
stringBuilder.append(description)
stringBuilder.append("\n\n")
stringBuilder.append("Magazine: ")
stringBuilder.append(magazine)
stringBuilder.append("\n\n")
stringBuilder.append("Parodies: ")
stringBuilder.append(parodies)
stringBuilder.append("\n\n")
stringBuilder.append("Publisher: ")
stringBuilder.append(publisher)
stringBuilder.append("\n\n")
stringBuilder.append("Pages: ")
stringBuilder.append(pagess)
return stringBuilder.toString()
}
override fun chapterListRequest(manga: SManga): Request {
if (manga.url.startsWith("http")) {
return GET(manga.url, headers)
}
return super.chapterListRequest(manga)
}
override fun chapterListSelector() = "div.container nav.depict-button-set"
override fun chapterFromElement(element: Element): SChapter {
return SChapter.create().apply {
url = element.select("div.level-item a").attr("href")
name = "Read Online: Chapter 0"
}
}
override fun pageListRequest(chapter: SChapter): Request {
if (chapter.url.startsWith("http")) {
return GET(chapter.url, headers)
}
return super.pageListRequest(chapter)
}
override fun pageListParse(document: Document): List<Page> {
return document.select("script:containsData(initreader)").first().data()
.substringAfter("initReader(\"")
.substringBefore("\", 1")
.let(::decodePages)
.mapIndexed { i, image -> Page(i, "", image) }
}
private fun decodePages(code: String): List<String> {
val hidden: ByteArray = Base64.decode(code, Base64.DEFAULT)
var key: ByteArray = hidden.sliceArray(0..63)
var body: ByteArray = hidden.sliceArray(64..hidden.size-1)
val buf = StringBuilder()
for (begin in 0 until body.size step 64) {
var chunk: ByteArray = body.sliceArray(begin..begin+63)
for (x in 0 until 64) {
buf.append((chunk[x] xor key[x]).toChar())
}
key = chunk
}
val json = JsonParser().parse(buf.toString()).asJsonObject
val base = json.get("b").asString
val folder = json.get("r").asString
val id = json.get("i").asString
return json.get("f").asJsonArray.map { it ->
val page = it.asJsonObject
"${base}${folder}${page.get("h").asString}/${id}/${page.get("p").asString}"
}
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
override fun getFilterList() = FilterList(
Filter.Header("Only one filter may be used at a time."),
Filter.Separator(),
ArtistFilter(),
TagFilter()
)
class ArtistFilter : Filter.Text("Search by Artist (must be exact match)")
class TagFilter : Filter.Text("Search by Tag (must be exact match)")
companion object {
const val PREFIX_ID_SEARCH = "id:"
}
}
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T

View File

@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.extension.en.hentainexus
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://hentainexus.com/view/xxxx intents
* and redirects them to the main Tachiyomi process.
*/
class HentaiNexusActivity : 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", "${HentaiNexus.PREFIX_ID_SEARCH}$id")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("HentaiNexusActivity", e.toString())
}
} else {
Log.e("HentaiNexusActivity", "Could not parse URI from intent $intent")
}
finish()
exitProcess(0)
}
}

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: Mangaworld'
pkgNameSuffix = 'it.mangaworld'
extClass = '.Mangaworld'
extVersionCode = 2
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,293 @@
package eu.kanade.tachiyomi.extension.it.mangaworld
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import okhttp3.*
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.*
import eu.kanade.tachiyomi.source.model.*
import java.text.ParseException
class Mangaworld: ParsedHttpSource() {
override val name = "Mangaworld"
override val baseUrl = "https://mangaworld.tv"
override val lang = "it"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=views", headers)
}
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/page/$page?s&post_type=wp-manga&m_orderby=latest", headers)
}
// LIST SELECTOR
override fun popularMangaSelector() = "div.c-tabs-item__content"
override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = popularMangaSelector()
// ELEMENT
override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
// NEXT SELECTOR
override fun popularMangaNextPageSelector() = "div.nav-previous.float-left > a"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaFromElement(element: Element):SManga {
val manga = SManga.create()
manga.thumbnail_url = element.select("div.tab-thumb > a > img").attr("src")
element.select("div.tab-thumb > a").first().let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title")
}
return manga
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder()
url.addQueryParameter("post_type","wp-manga")
val pattern = "\\s+".toRegex()
val q = query.replace(pattern, "+")
if(query.length > 0){
url.addQueryParameter("s", q)
}else{
url.addQueryParameter("s", "")
}
var orderBy = ""
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
// is Status -> url.addQueryParameter("manga_status", arrayOf("", "completed", "ongoing")[filter.state])
is GenreList -> {
val genreInclude = mutableListOf<String>()
filter.state.forEach {
if (it.state == 1) {
genreInclude.add(it.id)
}
}
if(genreInclude.isNotEmpty()){
genreInclude.forEach{ genre ->
url.addQueryParameter("genre[]", genre)
}
}
}
is StatusList ->{
val statuses = mutableListOf<String>()
filter.state.forEach {
if (it.state == 1) {
statuses.add(it.id)
}
}
if(statuses.isNotEmpty()){
statuses.forEach{ status ->
url.addQueryParameter("status[]", status)
}
}
}
is SortBy -> {
orderBy = filter.toUriPart();
url.addQueryParameter("m_orderby",orderBy)
}
is TextField -> url.addQueryParameter(filter.key, filter.state)
}
}
return GET(url.toString(), headers)
}
// max 200 results
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div.site-content").first()
val manga = SManga.create()
manga.author = infoElement.select("div.author-content")?.text()
manga.artist = infoElement.select("div.artist-content")?.text()
val genres = mutableListOf<String>()
infoElement.select("div.genres-content a").forEach { element ->
val genre = element.text()
genres.add(genre)
}
manga.genre =genres.joinToString(", ")
manga.status = parseStatus(infoElement.select("div.post-status > div:nth-child(2) div").text())
manga.description = document.select("div.summary__content > p")?.text()
manga.thumbnail_url = document.select("div.summary_image > a > img").attr("src")
return manga
}
private fun parseStatus(element: String): Int = when {
element.toLowerCase().contains("ongoing") -> SManga.ONGOING
element.toLowerCase().contains("completed") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
override fun chapterListSelector() = "li.wp-manga-chapter"
override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first()
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(getUrl(urlElement))
chapter.name = urlElement.text()
chapter.date_upload = element.select("span.chapter-release-date i").last()?.text()?.let {
try {
SimpleDateFormat("dd MMMM yyyy", Locale.ITALY).parse(it).time
} catch (e: ParseException) {
SimpleDateFormat("H", Locale.ITALY).parse(it).time
}
} ?: 0
return chapter
}
private fun getUrl(urlElement: Element): String {
var url = urlElement.attr("href")
return when {
url.endsWith("?style=list") -> url
else -> "$url?style=list"
}
}
override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
val basic = Regex("""Capitolo\s([0-9]+)""")
when {
basic.containsMatchIn(chapter.name) -> {
basic.find(chapter.name)?.let {
chapter.chapter_number = it.groups[1]?.value!!.toFloat()
}
}
}
}
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
var i = 0
document.select("div.reading-content * img").forEach { element ->
val url = element.attr("src")
i++
if(url.length != 0){
pages.add(Page(i, "", url))
}
}
return pages
}
override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request {
val imgHeader = Headers.Builder().apply {
add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30")
add("Referer", baseUrl)
}.build()
return GET(page.imageUrl!!, imgHeader)
}
// private class Status : Filter.TriState("Completed")
private class TextField(name: String, val key: String) : Filter.Text(name)
private class SortBy : UriPartFilter("Ordina per", arrayOf(
Pair("Rilevanza", ""),
Pair("Ultime Aggiunte", "latest"),
Pair("A-Z", "alphabet"),
Pair("Voto", "rating"),
Pair("Tendenza", "trending"),
Pair("Più Visualizzati", "views"),
Pair("Nuove Aggiunte", "new-manga")
))
private class Genre(name: String, val id: String = name) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres)
private class Status(name: String, val id: String = name) : Filter.TriState(name)
private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Stato", statuses)
override fun getFilterList() = FilterList(
// TextField("Judul", "title"),
TextField("Autore", "author"),
TextField("Anno di rilascio", "release"),
SortBy(),
StatusList(getStatusList()),
GenreList(getGenreList())
)
private fun getStatusList() = listOf(
Status("Completato","end"),
Status("In Corso","on-going"),
Status("Droppato","canceled"),
Status("In Pausa","on-hold")
)
private fun getGenreList() = listOf(
Genre("Adulti","adult"),
Genre("Anime","anime"),
Genre("Arti Marziali","martial-arts"),
Genre("Avventura","adventure"),
Genre("Azione","action"),
Genre("Cartoon","cartoon"),
Genre("Comic","comic"),
Genre("Commedia","comedy"),
Genre("Cucina","cooking"),
Genre("Demoni","demoni"),
Genre("Detective","detective"),
Genre("Doujinshi","doujinshi"),
Genre("Drama","drama-"),
Genre("Drammatico","drama"),
Genre("Ecchi","ecchi"),
Genre("Fantasy","fantasy"),
Genre("Game","game"),
Genre("Gender Bender","gender-bender"),
Genre("Harem","harem"),
Genre("Hentai","hentai"),
Genre("Horror","horror"),
Genre("Josei","josei"),
Genre("Live action","live-action"),
Genre("Magia","magia"),
Genre("Manga","manga"),
Genre("Manhua","manhua"),
Genre("Manhwa","manhwa"),
Genre("Mature","mature"),
Genre("Mecha","mecha"),
Genre("Militari","militari"),
Genre("Mistero","mystery"),
Genre("Musica","musica"),
Genre("One shot","one-shot"),
Genre("Parodia","parodia"),
Genre("Psicologico","psychological"),
Genre("Romantico","romance"),
Genre("RPG","rpg"),
Genre("Sci-fi","sci-fi"),
Genre("Scolastico","school-life"),
Genre("Seinen","seinen"),
Genre("Shoujo","shoujo"),
Genre("Shoujo Ai","shoujo-ai"),
Genre("Shounen","shounen"),
Genre("Shounen Ai","shounen-ai"),
Genre("Slice of Life","slice-of-life"),
Genre("Smut","smut"),
Genre("Soft Yaoi","soft-yaoi"),
Genre("Soft Yuri","soft-yuri"),
Genre("Soprannaturale","supernatural"),
Genre("Spazio","spazio"),
Genre("Sport","sports"),
Genre("Storico","historical"),
Genre("Super Poteri","superpower"),
Genre("Thriller","thriller"),
Genre("Tragico","tragedy"),
Genre("Vampiri","vampiri"),
Genre("Webtoon","webtoon"),
Genre("Yaoi","yaoi"),
Genre("Yuri","yuri")
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
}