Compare commits
45 Commits
1699cece9f
...
c5c6d77479
Author | SHA1 | Date |
---|---|---|
bapeey | c5c6d77479 | |
Luka Mamukashvili | 2556e117be | |
Fermín Cirella | 925136fc15 | |
bapeey | c8ca375c1c | |
bapeey | 5b640b1512 | |
bapeey | 6850a721c0 | |
mohamedotaku | 8bb508d679 | |
mohamedotaku | cf5299c188 | |
mohamedotaku | 591c65a534 | |
Cuong M. Tran | 731145443b | |
MikeZeDev | bc4df5d008 | |
beerpsi | f71938e357 | |
Mike | 712c3a75be | |
beerpsi | f09c11a01c | |
Mike | 244fd8f4fa | |
beerpsi | 3db5f6edc2 | |
mohamedotaku | e54743b6c9 | |
mohamedotaku | cdd332ee78 | |
beerpsi | 49c3517510 | |
beerpsi | 4682cc8752 | |
AwkwardPeak7 | 879eb629b1 | |
Secozzi | d8f4f38676 | |
Mike | 95d3671f3d | |
Secozzi | c8f24dac99 | |
Secozzi | aae877a2a3 | |
mohamedotaku | bd0b4b0edd | |
beerpsi | 47e328dc84 | |
Mike | 8dd884535b | |
LLecca | 457475fc07 | |
mohamedotaku | 82983fb1b9 | |
LLecca | ea90594583 | |
LLecca | b78d35db07 | |
AwkwardPeak7 | b2ddfd348b | |
bapeey | 584e00b1dd | |
beerpsi | 2f244a72ff | |
bapeey | 7746e2a3fa | |
AlphaBoom | fb88e2ce97 | |
AwkwardPeak7 | 48c71e342d | |
beerpsi | c8c4110a55 | |
beerpsi | 23e385128e | |
beerpsi | b0b32918e1 | |
beerpsi | c02bd36b43 | |
Mike | fe97867cf1 | |
Mike | fdd2c5c6d1 | |
Secozzi | 8e610e1ea9 |
|
@ -0,0 +1,12 @@
|
|||
plugins {
|
||||
`java-library`
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.kotlin.stdlib)
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
package eu.kanade.tachiyomi.lib.lzstring
|
||||
|
||||
typealias getCharFromIntFn = (it: Int) -> String
|
||||
typealias getNextValueFn = (it: Int) -> Int
|
||||
|
||||
/**
|
||||
* Reimplementation of [lz-string](https://github.com/pieroxy/lz-string) compression/decompression.
|
||||
*/
|
||||
object LZString {
|
||||
private fun compress(
|
||||
uncompressed: String,
|
||||
bitsPerChar: Int,
|
||||
getCharFromInt: getCharFromIntFn,
|
||||
): String {
|
||||
val context = CompressionContext(uncompressed.length, bitsPerChar, getCharFromInt)
|
||||
|
||||
for (ii in uncompressed.indices) {
|
||||
context.c = uncompressed[ii].toString()
|
||||
|
||||
if (!context.dictionary.containsKey(context.c)) {
|
||||
context.dictionary[context.c] = context.dictSize++
|
||||
context.dictionaryToCreate[context.c] = true
|
||||
}
|
||||
|
||||
context.wc = context.w + context.c
|
||||
|
||||
if (context.dictionary.containsKey(context.wc)) {
|
||||
context.w = context.wc
|
||||
continue
|
||||
}
|
||||
|
||||
context.outputCodeForW()
|
||||
|
||||
context.decrementEnlargeIn()
|
||||
context.dictionary[context.wc] = context.dictSize++
|
||||
context.w = context.c
|
||||
}
|
||||
|
||||
if (context.w.isNotEmpty()) {
|
||||
context.outputCodeForW()
|
||||
context.decrementEnlargeIn()
|
||||
}
|
||||
|
||||
// Mark the end of the stream
|
||||
context.value = 2
|
||||
for (i in 0 until context.numBits) {
|
||||
context.dataVal = (context.dataVal shl 1) or (context.value and 1)
|
||||
context.appendDataOrAdvancePosition()
|
||||
context.value = context.value shr 1
|
||||
}
|
||||
|
||||
while (true) {
|
||||
context.dataVal = context.dataVal shl 1
|
||||
|
||||
if (context.dataPosition == bitsPerChar - 1) {
|
||||
context.data.append(getCharFromInt(context.dataVal))
|
||||
break
|
||||
}
|
||||
|
||||
context.dataPosition++
|
||||
}
|
||||
|
||||
return context.data.toString()
|
||||
}
|
||||
|
||||
private fun decompress(length: Int, resetValue: Int, getNextValue: getNextValueFn): String {
|
||||
val dictionary = mutableListOf<String>()
|
||||
val result = StringBuilder()
|
||||
val data = DecompressionContext(resetValue, getNextValue)
|
||||
var enlargeIn = 4
|
||||
var numBits = 3
|
||||
var entry: String
|
||||
var c: Char? = null
|
||||
|
||||
for (i in 0 until 3) {
|
||||
dictionary.add(i.toString())
|
||||
}
|
||||
|
||||
data.loopUntilMaxPower()
|
||||
|
||||
when (data.bits) {
|
||||
0 -> {
|
||||
data.bits = 0
|
||||
data.maxPower = 1 shl 8
|
||||
data.power = 1
|
||||
data.loopUntilMaxPower()
|
||||
c = data.bits.toChar()
|
||||
}
|
||||
1 -> {
|
||||
data.bits = 0
|
||||
data.maxPower = 1 shl 16
|
||||
data.power = 1
|
||||
data.loopUntilMaxPower()
|
||||
c = data.bits.toChar()
|
||||
}
|
||||
2 -> throw IllegalArgumentException("Invalid LZString")
|
||||
}
|
||||
|
||||
if (c == null) {
|
||||
throw Exception("No character found")
|
||||
}
|
||||
|
||||
dictionary.add(c.toString())
|
||||
var w = c.toString()
|
||||
result.append(c.toString())
|
||||
|
||||
while (true) {
|
||||
if (data.index > length) {
|
||||
throw IllegalArgumentException("Invalid LZString")
|
||||
}
|
||||
|
||||
data.bits = 0
|
||||
data.maxPower = 1 shl numBits
|
||||
data.power = 1
|
||||
data.loopUntilMaxPower()
|
||||
|
||||
var cc = data.bits
|
||||
|
||||
when (data.bits) {
|
||||
0 -> {
|
||||
data.bits = 0
|
||||
data.maxPower = 1 shl 8
|
||||
data.power = 1
|
||||
data.loopUntilMaxPower()
|
||||
dictionary.add(data.bits.toChar().toString())
|
||||
cc = dictionary.size - 1
|
||||
enlargeIn--
|
||||
}
|
||||
1 -> {
|
||||
data.bits = 0
|
||||
data.maxPower = 1 shl 16
|
||||
data.power = 1
|
||||
data.loopUntilMaxPower()
|
||||
dictionary.add(data.bits.toChar().toString())
|
||||
cc = dictionary.size - 1
|
||||
enlargeIn--
|
||||
}
|
||||
2 -> return result.toString()
|
||||
}
|
||||
|
||||
if (enlargeIn == 0) {
|
||||
enlargeIn = 1 shl numBits
|
||||
numBits++
|
||||
}
|
||||
|
||||
entry = if (cc < dictionary.size) {
|
||||
dictionary[cc]
|
||||
} else {
|
||||
if (cc == dictionary.size) {
|
||||
w + w[0]
|
||||
} else {
|
||||
throw Exception("Invalid LZString")
|
||||
}
|
||||
}
|
||||
result.append(entry)
|
||||
dictionary.add(w + entry[0])
|
||||
enlargeIn--
|
||||
w = entry
|
||||
|
||||
if (enlargeIn == 0) {
|
||||
enlargeIn = 1 shl numBits
|
||||
numBits++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val base64KeyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
||||
|
||||
fun compressToBase64(input: String): String =
|
||||
compress(input, 6) { base64KeyStr[it].toString() }.let {
|
||||
return when (it.length % 4) {
|
||||
0 -> it
|
||||
1 -> "$it==="
|
||||
2 -> "$it=="
|
||||
3 -> "$it="
|
||||
else -> throw IllegalStateException("Modulo of 4 should not exceed 3.")
|
||||
}
|
||||
}
|
||||
|
||||
fun decompressFromBase64(input: String): String =
|
||||
decompress(input.length, 32) {
|
||||
base64KeyStr.indexOf(input[it])
|
||||
}
|
||||
}
|
||||
|
||||
private data class DecompressionContext(
|
||||
val resetValue: Int,
|
||||
val getNextValue: getNextValueFn,
|
||||
var value: Int = getNextValue(0),
|
||||
var position: Int = resetValue,
|
||||
var index: Int = 1,
|
||||
var bits: Int = 0,
|
||||
var maxPower: Int = 1 shl 2,
|
||||
var power: Int = 1,
|
||||
) {
|
||||
fun loopUntilMaxPower() {
|
||||
while (power != maxPower) {
|
||||
val resb = value and position
|
||||
|
||||
position = position shr 1
|
||||
|
||||
if (position == 0) {
|
||||
position = resetValue
|
||||
value = getNextValue(index++)
|
||||
}
|
||||
|
||||
bits = bits or ((if (resb > 0) 1 else 0) * power)
|
||||
power = power shl 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class CompressionContext(
|
||||
val uncompressedLength: Int,
|
||||
val bitsPerChar: Int,
|
||||
val getCharFromInt: getCharFromIntFn,
|
||||
var value: Int = 0,
|
||||
val dictionary: MutableMap<String, Int> = HashMap(),
|
||||
val dictionaryToCreate: MutableMap<String, Boolean> = HashMap(),
|
||||
var c: String = "",
|
||||
var wc: String = "",
|
||||
var w: String = "",
|
||||
var enlargeIn: Int = 2, // Compensate for the first entry which should not count
|
||||
var dictSize: Int = 3,
|
||||
var numBits: Int = 2,
|
||||
val data: StringBuilder = StringBuilder(uncompressedLength / 3),
|
||||
var dataVal: Int = 0,
|
||||
var dataPosition: Int = 0,
|
||||
) {
|
||||
fun appendDataOrAdvancePosition() {
|
||||
if (dataPosition == bitsPerChar - 1) {
|
||||
dataPosition = 0
|
||||
data.append(getCharFromInt(dataVal))
|
||||
dataVal = 0
|
||||
} else {
|
||||
dataPosition++
|
||||
}
|
||||
}
|
||||
|
||||
fun decrementEnlargeIn() {
|
||||
enlargeIn--
|
||||
if (enlargeIn == 0) {
|
||||
enlargeIn = 1 shl numBits
|
||||
numBits++
|
||||
}
|
||||
}
|
||||
|
||||
// Output the code for W.
|
||||
fun outputCodeForW() {
|
||||
if (dictionaryToCreate.containsKey(w)) {
|
||||
if (w[0].code < 256) {
|
||||
for (i in 0 until numBits) {
|
||||
dataVal = dataVal shl 1
|
||||
appendDataOrAdvancePosition()
|
||||
}
|
||||
|
||||
value = w[0].code
|
||||
|
||||
for (i in 0 until 8) {
|
||||
dataVal = (dataVal shl 1) or (value and 1)
|
||||
appendDataOrAdvancePosition()
|
||||
value = value shr 1
|
||||
}
|
||||
} else {
|
||||
value = 1
|
||||
|
||||
for (i in 0 until numBits) {
|
||||
dataVal = (dataVal shl 1) or value
|
||||
appendDataOrAdvancePosition()
|
||||
value = 0
|
||||
}
|
||||
|
||||
value = w[0].code
|
||||
|
||||
for (i in 0 until 16) {
|
||||
dataVal = (dataVal shl 1) or (value and 1)
|
||||
appendDataOrAdvancePosition()
|
||||
value = value shr 1
|
||||
}
|
||||
}
|
||||
|
||||
decrementEnlargeIn()
|
||||
dictionaryToCreate.remove(w)
|
||||
} else {
|
||||
value = dictionary[w]!!
|
||||
|
||||
for (i in 0 until numBits) {
|
||||
dataVal = (dataVal shl 1) or (value and 1)
|
||||
appendDataOrAdvancePosition()
|
||||
value = value shr 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<activity
|
||||
android:name="eu.kanade.tachiyomi.multisrc.colamanga.ColaMangaUrlActivity"
|
||||
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="${SOURCEHOST}"
|
||||
android:scheme="${SOURCESCHEME}"
|
||||
android:pathPattern="/manga-..*/" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,3 @@
|
|||
dependencies {
|
||||
implementation(project(":lib:synchrony"))
|
||||
}
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 6.0 KiB |
|
@ -0,0 +1,123 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangadig
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.colamanga.ColaManga
|
||||
import eu.kanade.tachiyomi.multisrc.colamanga.UriPartFilter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
||||
class MangaDig : ColaManga("MangaDig", "https://mangadig.com", "en") {
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a:contains(Next):not(.fed-btns-disad)"
|
||||
|
||||
override val statusTitle = "Status"
|
||||
override val authorTitle = "Author"
|
||||
override val genreTitle = "Category"
|
||||
override val statusOngoing = "OnGoing"
|
||||
override val statusCompleted = "Complete"
|
||||
|
||||
override fun getFilterList(): FilterList {
|
||||
val filters = buildList {
|
||||
addAll(super.getFilterList().list)
|
||||
add(SortFilter())
|
||||
add(CategoryFilter())
|
||||
add(CharFilter())
|
||||
add(StatusFilter())
|
||||
}
|
||||
|
||||
return FilterList(filters)
|
||||
}
|
||||
|
||||
private class StatusFilter : UriPartFilter(
|
||||
"Status",
|
||||
"status",
|
||||
arrayOf(
|
||||
Pair("All", ""),
|
||||
Pair("Ongoing", "1"),
|
||||
Pair("Complete", "2"),
|
||||
),
|
||||
)
|
||||
|
||||
private class SortFilter : UriPartFilter(
|
||||
"Order by",
|
||||
"orderBy",
|
||||
arrayOf(
|
||||
Pair("Last updated", "update"),
|
||||
Pair("Recently added", "create"),
|
||||
Pair("Most popular today", "dailyCount"),
|
||||
Pair("Most popular this week", "weeklyCount"),
|
||||
Pair("Most popular this month", "monthlyCount"),
|
||||
),
|
||||
2,
|
||||
)
|
||||
|
||||
private class CategoryFilter : UriPartFilter(
|
||||
"Genre",
|
||||
"mainCategoryId",
|
||||
arrayOf(
|
||||
Pair("All", ""),
|
||||
Pair("Romance", "10008"),
|
||||
Pair("Drama", "10005"),
|
||||
Pair("Comedy", "10004"),
|
||||
Pair("Fantasy", "10006"),
|
||||
Pair("Action", "10002"),
|
||||
Pair("CEO", "10142"),
|
||||
Pair("Webtoons", "10012"),
|
||||
Pair("Historical", "10021"),
|
||||
Pair("Adventure", "10003"),
|
||||
Pair("Josei", "10059"),
|
||||
Pair("Smut", "10047"),
|
||||
Pair("Supernatural", "10018"),
|
||||
Pair("School life", "10017"),
|
||||
Pair("Completed", "10423"),
|
||||
Pair("Possessive", "10284"),
|
||||
Pair("Manhua", "10010"),
|
||||
Pair("Sweet", "10282"),
|
||||
Pair("Harem", "10007"),
|
||||
Pair("Slice of life", "10026"),
|
||||
Pair("Girl Power", "10144"),
|
||||
Pair("Martial arts", "10013"),
|
||||
Pair("Chinese Classic", "10243"),
|
||||
Pair("BL", "10262"),
|
||||
Pair("Manhwa", "10039"),
|
||||
Pair("Adult", "10030"),
|
||||
Pair("Shounen", "10009"),
|
||||
Pair("TimeTravel", "10143"),
|
||||
Pair("Shoujo", "10054"),
|
||||
Pair("Ecchi", "10027"),
|
||||
Pair("Revenge", "10556"),
|
||||
),
|
||||
)
|
||||
|
||||
private class CharFilter : UriPartFilter(
|
||||
"Alphabet",
|
||||
"charCategoryId",
|
||||
arrayOf(
|
||||
Pair("All", ""),
|
||||
Pair("A", "10015"),
|
||||
Pair("B", "10028"),
|
||||
Pair("C", "10055"),
|
||||
Pair("D", "10034"),
|
||||
Pair("E", "10049"),
|
||||
Pair("F", "10056"),
|
||||
Pair("G", "10023"),
|
||||
Pair("H", "10037"),
|
||||
Pair("I", "10035"),
|
||||
Pair("J", "10060"),
|
||||
Pair("K", "10022"),
|
||||
Pair("L", "10046"),
|
||||
Pair("M", "10020"),
|
||||
Pair("N", "10044"),
|
||||
Pair("O", "10024"),
|
||||
Pair("P", "10048"),
|
||||
Pair("Q", "10051"),
|
||||
Pair("R", "10025"),
|
||||
Pair("S", "10011"),
|
||||
Pair("T", "10001"),
|
||||
Pair("U", "10058"),
|
||||
Pair("V", "10016"),
|
||||
Pair("W", "10052"),
|
||||
Pair("X", "10061"),
|
||||
Pair("Y", "10036"),
|
||||
Pair("Z", "10101"),
|
||||
),
|
||||
)
|
||||
}
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 8.6 KiB |
|
@ -0,0 +1,120 @@
|
|||
package eu.kanade.tachiyomi.extension.zh.onemanhua
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.colamanga.ColaManga
|
||||
import eu.kanade.tachiyomi.multisrc.colamanga.UriPartFilter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
||||
class Onemanhua : ColaManga("COLAMANGA", "https://www.colamanga.com", "zh") {
|
||||
override val id = 8252565807829914103 // name used to be "One漫画"
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a:contains(下页):not(.fed-btns-disad)"
|
||||
|
||||
override val statusTitle = "状态"
|
||||
override val authorTitle = "作者"
|
||||
override val genreTitle = "类别"
|
||||
override val statusOngoing = "连载中"
|
||||
override val statusCompleted = "已完结"
|
||||
|
||||
override fun getFilterList(): FilterList {
|
||||
val filters = buildList {
|
||||
addAll(super.getFilterList().list)
|
||||
add(SortFilter())
|
||||
add(CategoryFilter())
|
||||
add(CharFilter())
|
||||
add(StatusFilter())
|
||||
}
|
||||
|
||||
return FilterList(filters)
|
||||
}
|
||||
|
||||
private class StatusFilter : UriPartFilter(
|
||||
"状态",
|
||||
"status",
|
||||
arrayOf(
|
||||
Pair("全部", ""),
|
||||
Pair("连载中", "1"),
|
||||
Pair("已完结", "2"),
|
||||
),
|
||||
)
|
||||
private class SortFilter : UriPartFilter(
|
||||
"排序",
|
||||
"orderBy",
|
||||
arrayOf(
|
||||
Pair("更新日", "update"),
|
||||
Pair("日点击", "dailyCount"),
|
||||
Pair("周点击", "weeklyCount"),
|
||||
Pair("月点击", "monthlyCount"),
|
||||
),
|
||||
1,
|
||||
)
|
||||
private class CategoryFilter : UriPartFilter(
|
||||
"类型",
|
||||
"mainCategoryId",
|
||||
arrayOf(
|
||||
Pair("全部", ""),
|
||||
Pair("热血", "10023"),
|
||||
Pair("玄幻", "10024"),
|
||||
Pair("恋爱", "10126"),
|
||||
Pair("冒险", "10210"),
|
||||
Pair("古风", "10143"),
|
||||
Pair("都市", "10124"),
|
||||
Pair("穿越", "10129"),
|
||||
Pair("奇幻", "10242"),
|
||||
Pair("其他", "10560"),
|
||||
Pair("少男", "10641"),
|
||||
Pair("搞笑", "10122"),
|
||||
Pair("战斗", "10309"),
|
||||
Pair("冒险热血", "11224"),
|
||||
Pair("重生", "10461"),
|
||||
Pair("爆笑", "10201"),
|
||||
Pair("逆袭", "10943"),
|
||||
Pair("后宫", "10138"),
|
||||
Pair("少年", "10321"),
|
||||
Pair("少女", "10301"),
|
||||
Pair("熱血", "12044"),
|
||||
Pair("系统", "10722"),
|
||||
Pair("动作", "10125"),
|
||||
Pair("校园", "10131"),
|
||||
Pair("冒險", "12123"),
|
||||
Pair("修真", "10133"),
|
||||
Pair("修仙", "10453"),
|
||||
Pair("剧情", "10480"),
|
||||
Pair("霸总", "10127"),
|
||||
Pair("大女主", "10706"),
|
||||
Pair("生活", "10142"),
|
||||
),
|
||||
)
|
||||
private class CharFilter : UriPartFilter(
|
||||
"字母",
|
||||
"charCategoryId",
|
||||
arrayOf(
|
||||
Pair("全部", ""),
|
||||
Pair("A", "10182"),
|
||||
Pair("B", "10081"),
|
||||
Pair("C", "10134"),
|
||||
Pair("D", "10001"),
|
||||
Pair("E", "10238"),
|
||||
Pair("F", "10161"),
|
||||
Pair("G", "10225"),
|
||||
Pair("H", "10137"),
|
||||
Pair("I", "10284"),
|
||||
Pair("J", "10141"),
|
||||
Pair("K", "10283"),
|
||||
Pair("L", "10132"),
|
||||
Pair("M", "10136"),
|
||||
Pair("N", "10130"),
|
||||
Pair("O", "10282"),
|
||||
Pair("P", "10262"),
|
||||
Pair("Q", "10164"),
|
||||
Pair("R", "10240"),
|
||||
Pair("S", "10121"),
|
||||
Pair("T", "10123"),
|
||||
Pair("U", "11184"),
|
||||
Pair("V", "11483"),
|
||||
Pair("W", "10135"),
|
||||
Pair("X", "10061"),
|
||||
Pair("Y", "10082"),
|
||||
Pair("Z", "10128"),
|
||||
),
|
||||
)
|
||||
}
|
|
@ -13,6 +13,9 @@ import rx.Observable
|
|||
import java.util.Calendar
|
||||
|
||||
class Manga1000 : FMReader("Manga1000", "https://manga1000.top", "ja") {
|
||||
|
||||
override val infoElementSelector = "div.row div.row"
|
||||
|
||||
// source is picky about URL format
|
||||
private fun mangaRequest(sortBy: String, page: Int): Request {
|
||||
return GET("$baseUrl/manga-list.html?listType=pagination&page=$page&artist=&author=&group=&m_status=&name=&genre=&ungenre=&magazine=&sort=$sortBy&sort_type=DESC", headers)
|
||||
|
@ -25,7 +28,7 @@ class Manga1000 : FMReader("Manga1000", "https://manga1000.top", "ja") {
|
|||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
val slug = manga.url.substringAfter("manga-").substringBefore(".html")
|
||||
|
||||
return client.newCall(GET("$baseUrl/app/manga/controllers/cont.Listchapterapi.php?slug=$slug", headers))
|
||||
return client.newCall(GET("$baseUrl/app/manga/controllers/cont.Listchapter.php?slug=$slug", headers))
|
||||
.asObservableSuccess()
|
||||
.map { res ->
|
||||
res.asJsoup().select(".at-series a").map {
|
||||
|
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 8.6 KiB |
|
@ -0,0 +1,96 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.idolgravureprincessdate
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.gravureblogger.GravureBlogger
|
||||
|
||||
class IdolGravureprincessDate : GravureBlogger(
|
||||
"Idol. gravureprincess .date",
|
||||
"https://idol.gravureprincess.date",
|
||||
"ja",
|
||||
) {
|
||||
override val labelFilters = buildMap {
|
||||
put("Idol", getIdols())
|
||||
put("Magazines", getMagazine())
|
||||
}
|
||||
|
||||
private fun getIdols() = listOf(
|
||||
"Nogizaka46",
|
||||
"AKB48",
|
||||
"NMB48",
|
||||
"Keyakizaka46",
|
||||
"HKT48",
|
||||
"SKE48",
|
||||
"NGT48",
|
||||
"SUPER☆GiRLS",
|
||||
"Morning Musume",
|
||||
"Dempagumi.inc",
|
||||
"Angerme",
|
||||
"Juice=Juice",
|
||||
"NijiCon-虹コン",
|
||||
"Houkago Princess",
|
||||
"Magical Punchline",
|
||||
"Idoling!!!",
|
||||
"Rev. from DVL",
|
||||
"Link STAR`s",
|
||||
"LADYBABY",
|
||||
"℃-ute",
|
||||
"Country Girls",
|
||||
"Up Up Girls (Kakko Kari)",
|
||||
"Yumemiru Adolescence",
|
||||
"Shiritsu Ebisu Chugaku",
|
||||
"Tenkoushoujo Kagekidan",
|
||||
"Drop",
|
||||
"Steam Girls",
|
||||
"Kamen Joshi's",
|
||||
"LinQ",
|
||||
"Doll☆Element",
|
||||
"TrySail",
|
||||
"Akihabara Backstage Pass",
|
||||
"Palet",
|
||||
"Passport☆",
|
||||
"Ange☆Reve",
|
||||
"BiSH",
|
||||
"Ciao Bella Cinquetti",
|
||||
"Gekidanherbest",
|
||||
"Haraeki Stage Ace",
|
||||
"Ru:Run",
|
||||
"SDN48",
|
||||
)
|
||||
|
||||
private fun getMagazine() = listOf(
|
||||
"FLASH",
|
||||
"Weekly Playboy",
|
||||
"FRIDAY Magazine",
|
||||
"Young Jump",
|
||||
"Young Magazine",
|
||||
"BLT",
|
||||
"ENTAME",
|
||||
"EX-Taishu",
|
||||
"SPA! Magazine",
|
||||
"Young Gangan",
|
||||
"UTB",
|
||||
"Young Animal",
|
||||
"Young Champion",
|
||||
"Big Comic Spirtis",
|
||||
"Shonen Magazine",
|
||||
"BUBKA",
|
||||
"BOMB",
|
||||
"Shonen Champion",
|
||||
"Manga Action",
|
||||
"Weekly Shonen Sunday",
|
||||
"Photobooks",
|
||||
"BRODY",
|
||||
"Hustle Press",
|
||||
"ANAN Magazine",
|
||||
"SMART Magazine",
|
||||
"Young Sunday",
|
||||
"Gravure The Television",
|
||||
"CD&DL My Girl",
|
||||
"Daily LoGiRL",
|
||||
"Shukan Taishu",
|
||||
"Girls! Magazine",
|
||||
"Soccer Game King",
|
||||
"Weekly Georgia",
|
||||
"Sunday Magazine",
|
||||
"Mery Magazine",
|
||||
)
|
||||
}
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
@ -0,0 +1,62 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.micmicidol
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.gravureblogger.GravureBlogger
|
||||
|
||||
class MicMicIdol : GravureBlogger("MIC MIC IDOL", "https://www.micmicidol.club", "ja") {
|
||||
override val labelFilters = buildMap {
|
||||
put("Type", getTypes())
|
||||
put("Japan Magazine", getJapanMagazines())
|
||||
put("Japan Fashion", getJapanFashion())
|
||||
}
|
||||
|
||||
private fun getJapanMagazines() = listOf(
|
||||
"cyzo",
|
||||
"EnTame",
|
||||
"EX大衆",
|
||||
"Friday",
|
||||
"Flash",
|
||||
"Shonen Magazine",
|
||||
"Shonen Sunday",
|
||||
"Weekly Shonen Champion",
|
||||
"Weekly Big Comic Spirits",
|
||||
"Weekly Jitsuwa",
|
||||
"Weekly Playboy",
|
||||
"Weekly SPA!",
|
||||
"Young Animal",
|
||||
"Young Champion",
|
||||
"Young Gangan",
|
||||
"Young Jump",
|
||||
"Young Magazine",
|
||||
)
|
||||
|
||||
private fun getJapanFashion() = listOf(
|
||||
"andGIRL",
|
||||
"aR",
|
||||
"Baila",
|
||||
"Biteki",
|
||||
"CanCam",
|
||||
"Classy",
|
||||
"ELLE Japan",
|
||||
"Ginger",
|
||||
"JJ",
|
||||
"Maquia",
|
||||
"Mina",
|
||||
"MORE",
|
||||
"Non-no",
|
||||
"Oggi",
|
||||
"Ray",
|
||||
"Scawaii",
|
||||
"Steady",
|
||||
"ViVi",
|
||||
"VoCE",
|
||||
"With",
|
||||
)
|
||||
|
||||
private fun getTypes() = listOf(
|
||||
"- Cover",
|
||||
"- Japan Magazine",
|
||||
"- Japan Fashion Magazine",
|
||||
"- Japan Idol Photobook",
|
||||
"- Asia Idol",
|
||||
)
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.es.atlantisscan
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AtlantisScan : Madara(
|
||||
"Atlantis Scan",
|
||||
"https://scansatlanticos.com",
|
||||
"es",
|
||||
dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US),
|
||||
) {
|
||||
override val id: Long = 2237642340381856331
|
||||
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
.rateLimit(2, 1, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
override val useNewChapterEndpoint = true
|
||||
|
||||
override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))"
|
||||
|
||||
private fun loadMoreRequest(page: Int, metaKey: String): Request {
|
||||
val formBody = FormBody.Builder().apply {
|
||||
add("action", "madara_load_more")
|
||||
add("page", page.toString())
|
||||
add("template", "madara-core/content/content-archive")
|
||||
add("vars[paged]", "1")
|
||||
add("vars[orderby]", "meta_value_num")
|
||||
add("vars[template]", "archive")
|
||||
add("vars[sidebar]", "full")
|
||||
add("vars[post_type]", "wp-manga")
|
||||
add("vars[post_status]", "publish")
|
||||
add("vars[meta_key]", metaKey)
|
||||
add("vars[order]", "desc")
|
||||
add("vars[meta_query][relation]", "AND")
|
||||
add("vars[manga_archives_item_layout]", "big_thumbnail")
|
||||
}.build()
|
||||
|
||||
val xhrHeaders = headersBuilder()
|
||||
.add("Content-Length", formBody.contentLength().toString())
|
||||
.add("Content-Type", formBody.contentType().toString())
|
||||
.add("X-Requested-With", "XMLHttpRequest")
|
||||
.build()
|
||||
|
||||
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return loadMoreRequest(page - 1, "_wp_manga_views")
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return loadMoreRequest(page - 1, "_latest_update")
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
class Jiangzaitoon : Madara(
|
||||
"Jiangzaitoon",
|
||||
"https://jiangzaitoon.cc",
|
||||
"https://jiangzaitoon.info",
|
||||
"tr",
|
||||
SimpleDateFormat("d MMM yyy", Locale("tr")),
|
||||
) {
|
||||
|
|
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,30 @@
|
|||
package eu.kanade.tachiyomi.extension.tr.mangagezgini
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class MangaGezgini : Madara(
|
||||
"MangaGezgini",
|
||||
"https://mangagezgini.com",
|
||||
"tr",
|
||||
SimpleDateFormat("dd/MM/yyy", Locale("tr")),
|
||||
) {
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val chapter = SChapter.create()
|
||||
with(element) {
|
||||
select(chapterUrlSelector).first()?.let { urlElement ->
|
||||
chapter.url = urlElement.attr("abs:href").let {
|
||||
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
|
||||
}
|
||||
chapter.name = element.select("li.wp-manga-chapter.has-thumb a").text()
|
||||
}
|
||||
chapter.date_upload = select("img:not(.thumb)").firstOrNull()?.attr("alt")?.let { parseRelativeDate(it) }
|
||||
?: select("span a").firstOrNull()?.attr("title")?.let { parseRelativeDate(it) }
|
||||
?: parseChapterDate(select(chapterDateSelector()).firstOrNull()?.text())
|
||||
}
|
||||
return chapter
|
||||
}
|
||||
}
|
|
@ -11,11 +11,11 @@ import uy.kohesive.injekt.api.get
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class Mangalink : Madara("مانجا لينك", "https://manga-link.net", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) {
|
||||
class Mangalink : Madara("مانجا لينك", "https://manga-link.com", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) {
|
||||
|
||||
override val chapterUrlSuffix = ""
|
||||
|
||||
private val defaultBaseUrl = "https://manga-link.net"
|
||||
private val defaultBaseUrl = "https://manga-link.com"
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
|
|
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 36 KiB |
|
@ -0,0 +1,12 @@
|
|||
package eu.kanade.tachiyomi.extension.tr.mangawow
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class MangaWOW : Madara(
|
||||
"MangaWOW",
|
||||
"https://mangawow.org",
|
||||
"tr",
|
||||
SimpleDateFormat("MMMM dd, yyyy", Locale("tr")),
|
||||
)
|
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 20 KiB |
|
@ -0,0 +1,21 @@
|
|||
package eu.kanade.tachiyomi.extension.pt.ninjascan
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class NinjaScan : Madara(
|
||||
"Ninja Scan",
|
||||
"https://ninjascan.site",
|
||||
"pt-BR",
|
||||
SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")),
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override val useNewChapterEndpoint = true
|
||||
|
||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
||||
}
|
|
@ -6,10 +6,12 @@ import java.util.Locale
|
|||
|
||||
class SamuraiScan : Madara(
|
||||
"SamuraiScan",
|
||||
"https://samuraiscan.com",
|
||||
"https://samuraiscan.org",
|
||||
"es",
|
||||
SimpleDateFormat("MMMM d, yyyy", Locale("es")),
|
||||
SimpleDateFormat("d MMMM, yyyy", Locale("es")),
|
||||
) {
|
||||
|
||||
override val mangaSubString = "l"
|
||||
override val useNewChapterEndpoint = true
|
||||
|
||||
override val mangaDetailsSelectorDescription = "div.summary_content div.manga-summary"
|
||||
|
|
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1,15 @@
|
|||
package eu.kanade.tachiyomi.extension.en.stonescape
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class StoneScape : Madara(
|
||||
"StoneScape",
|
||||
"https://stonescape.xyz",
|
||||
"en",
|
||||
SimpleDateFormat("MMMM dd, yyyy", Locale("en")),
|
||||
) {
|
||||
override val mangaSubString = "series"
|
||||
override val chapterUrlSelector = "div + a"
|
||||
}
|
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,21 @@
|
|||
package eu.kanade.tachiyomi.extension.pt.sussyscan
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class SussyScan : Madara(
|
||||
"Sussy Scan",
|
||||
"https://sussyscan.com",
|
||||
"pt-BR",
|
||||
SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override val useNewChapterEndpoint = true
|
||||
|
||||
override val mangaSubString = "sus"
|
||||
}
|
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,8 @@
|
|||
package eu.kanade.tachiyomi.extension.zh.hanman18
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.manga18.Manga18
|
||||
|
||||
class HANMAN18 : Manga18("HANMAN18", "https://hanman18.com", "zh") {
|
||||
// tag filter doesn't work on site
|
||||
override val getAvailableTags = false
|
||||
}
|
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 53 KiB |
|
@ -0,0 +1,16 @@
|
|||
package eu.kanade.tachiyomi.extension.en.hentai3zcc
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.manga18.Manga18
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class Hentai3zCC : Manga18("Hentai3z.CC", "https://hentai3z.cc", "en") {
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
|
||||
title = element.selectFirst("div.mg_info > div.mg_name a")!!.text()
|
||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||
?.replace("cover_thumb_2.webp", "cover_250x350.jpg")
|
||||
?.replace("admin.manga18.us", "bk.18porncomic.com")
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,251 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawotaku
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangareader.MangaReader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
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.util.asJsoup
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.TextNode
|
||||
import org.jsoup.select.Evaluator
|
||||
import rx.Observable
|
||||
import java.net.URLEncoder
|
||||
|
||||
class RawOtaku : MangaReader() {
|
||||
|
||||
override val name = "Raw Otaku"
|
||||
|
||||
override val lang = "ja"
|
||||
|
||||
override val baseUrl = "https://rawotaku.com"
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularMangaRequest(page: Int) =
|
||||
GET("$baseUrl/filter/?type=all&status=all&language=all&sort=most-viewed&p=$page", headers)
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) =
|
||||
GET("$baseUrl/filter/?type=all&status=all&language=all&sort=latest-updated&p=$page", headers)
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = baseUrl.toHttpUrl().newBuilder().apply {
|
||||
if (query.isNotBlank()) {
|
||||
addQueryParameter("q", query)
|
||||
} else {
|
||||
addPathSegment("filter")
|
||||
addPathSegment("")
|
||||
|
||||
filters.ifEmpty(::getFilterList).forEach { filter ->
|
||||
when (filter) {
|
||||
is TypeFilter -> {
|
||||
addQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
is StatusFilter -> {
|
||||
addQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
|
||||
is LanguageFilter -> {
|
||||
addQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
is SortFilter -> {
|
||||
addQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
is GenresFilter -> {
|
||||
filter.state.forEach {
|
||||
if (it.state) {
|
||||
addQueryParameter(filter.param, it.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addQueryParameter("p", page.toString())
|
||||
}.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = ".manga_list-sbs .manga-poster"
|
||||
|
||||
override fun searchMangaFromElement(element: Element) =
|
||||
SManga.create().apply {
|
||||
setUrlWithoutDomain(element.attr("href"))
|
||||
element.selectFirst(Evaluator.Tag("img"))!!.let {
|
||||
title = it.attr("alt")
|
||||
thumbnail_url = it.imgAttr()
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "ul.pagination > li.active + li"
|
||||
|
||||
// =============================== Filters ==============================
|
||||
|
||||
override fun getFilterList() =
|
||||
FilterList(
|
||||
Note,
|
||||
Filter.Separator(),
|
||||
TypeFilter(),
|
||||
StatusFilter(),
|
||||
LanguageFilter(),
|
||||
SortFilter(),
|
||||
GenresFilter(),
|
||||
)
|
||||
|
||||
// =========================== Manga Details ============================
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
val root = document.selectFirst(Evaluator.Id("ani_detail"))!!
|
||||
val mangaTitle = root.selectFirst(Evaluator.Class("manga-name"))!!.ownText()
|
||||
title = mangaTitle
|
||||
description = buildString {
|
||||
root.selectFirst(".description")?.ownText()?.let { append(it) }
|
||||
append("\n\n")
|
||||
root.selectFirst(".manga-name-or")?.ownText()?.let {
|
||||
if (it.isNotEmpty() && it != mangaTitle) {
|
||||
append("Alternative Title: ")
|
||||
append(it)
|
||||
}
|
||||
}
|
||||
}.trim()
|
||||
thumbnail_url = root.selectFirst(Evaluator.Tag("img"))!!.imgAttr()
|
||||
genre = root.selectFirst(Evaluator.Class("genres"))!!.children().joinToString { it.ownText() }
|
||||
for (item in root.selectFirst(Evaluator.Class("anisc-info"))!!.children()) {
|
||||
if (item.hasClass("item").not()) continue
|
||||
when (item.selectFirst(Evaluator.Class("item-head"))!!.ownText()) {
|
||||
"著者:" -> item.parseAuthorsTo(this)
|
||||
"地位:" -> status = when (item.selectFirst(Evaluator.Class("name"))!!.ownText().lowercase()) {
|
||||
"ongoing" -> SManga.ONGOING
|
||||
"completed" -> SManga.COMPLETED
|
||||
"on-hold" -> SManga.ON_HIATUS
|
||||
"canceled" -> SManga.CANCELLED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.parseAuthorsTo(manga: SManga) {
|
||||
val authors = select(Evaluator.Tag("a"))
|
||||
val text = authors.map { it.ownText().replace(",", "") }
|
||||
val count = authors.size
|
||||
when (count) {
|
||||
0 -> return
|
||||
1 -> {
|
||||
manga.author = text[0]
|
||||
return
|
||||
}
|
||||
}
|
||||
val authorList = ArrayList<String>(count)
|
||||
val artistList = ArrayList<String>(count)
|
||||
for ((index, author) in authors.withIndex()) {
|
||||
val textNode = author.nextSibling() as? TextNode
|
||||
val list = if (textNode != null && "(Art)" in textNode.wholeText) artistList else authorList
|
||||
list.add(text[index])
|
||||
}
|
||||
if (authorList.isEmpty().not()) manga.author = authorList.joinToString()
|
||||
if (artistList.isEmpty().not()) manga.artist = artistList.joinToString()
|
||||
}
|
||||
|
||||
// ============================== Chapters ==============================
|
||||
|
||||
override fun chapterListRequest(mangaUrl: String, type: String): Request =
|
||||
GET(baseUrl + mangaUrl, headers)
|
||||
|
||||
override fun parseChapterElements(response: Response, isVolume: Boolean): List<Element> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override val chapterType = ""
|
||||
override val volumeType = ""
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return client.newCall(chapterListRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.map(::parseChapterList)
|
||||
}
|
||||
|
||||
private fun parseChapterList(response: Response): List<SChapter> {
|
||||
val document = response.use { it.asJsoup() }
|
||||
|
||||
return document.select(chapterListSelector())
|
||||
.map(::chapterFromElement)
|
||||
}
|
||||
|
||||
private fun chapterListSelector(): String = "#ja-chaps > .chapter-item"
|
||||
|
||||
private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
val id = element.attr("data-id")
|
||||
element.selectFirst("a")!!.run {
|
||||
setUrlWithoutDomain(attr("href") + "#$id")
|
||||
name = selectFirst(".name")?.text() ?: text()
|
||||
}
|
||||
}
|
||||
|
||||
// =============================== Pages ================================
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.fromCallable {
|
||||
val id = chapter.url.substringAfterLast("#")
|
||||
|
||||
val ajaxHeaders = super.headersBuilder().apply {
|
||||
add("Accept", "application/json, text/javascript, */*; q=0.01")
|
||||
add("Referer", URLEncoder.encode(baseUrl + chapter.url.substringBeforeLast("#"), "utf-8"))
|
||||
add("X-Requested-With", "XMLHttpRequest")
|
||||
}.build()
|
||||
|
||||
val ajaxUrl = "$baseUrl/json/chapter?mode=vertical&id=$id"
|
||||
client.newCall(GET(ajaxUrl, ajaxHeaders)).execute().let(::pageListParse)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val document = response.use { it.parseHtmlProperty() }
|
||||
|
||||
val pageList = document.select(".container-reader-chapter > div > img").map {
|
||||
val index = it.attr("alt").toInt()
|
||||
val imgUrl = it.imgAttr()
|
||||
|
||||
Page(index, imageUrl = imgUrl)
|
||||
}
|
||||
|
||||
return pageList
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private fun Element.imgAttr(): String = when {
|
||||
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
|
||||
hasAttr("data-src") -> attr("abs:data-src")
|
||||
else -> attr("abs:src")
|
||||
}
|
||||
|
||||
private fun Response.parseHtmlProperty(): Document {
|
||||
val html = Json.parseToJsonElement(body.string()).jsonObject["html"]!!.jsonPrimitive.content
|
||||
return Jsoup.parseBodyFragment(html)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawotaku
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
object Note : Filter.Header("NOTE: Ignored if using text search!")
|
||||
|
||||
sealed class Select(
|
||||
name: String,
|
||||
val param: String,
|
||||
values: Array<String>,
|
||||
) : Filter.Select<String>(name, values) {
|
||||
open val selection: String
|
||||
get() = if (state == 0) "" else state.toString()
|
||||
}
|
||||
|
||||
class TypeFilter(
|
||||
values: Array<String> = types.keys.toTypedArray(),
|
||||
) : Select("タイプ", "type", values) {
|
||||
override val selection: String
|
||||
get() = types[values[state]]!!
|
||||
|
||||
companion object {
|
||||
private val types = mapOf(
|
||||
"全て" to "all",
|
||||
"Raw Manga" to "Raw Manga",
|
||||
"BLコミック" to "BLコミック",
|
||||
"TLコミック" to "TLコミック",
|
||||
"オトナコミック" to "オトナコミック",
|
||||
"女性マンガ" to "女性マンガ",
|
||||
"少女マンガ" to "少女マンガ",
|
||||
"少年マンガ" to "少年マンガ",
|
||||
"青年マンガ" to "青年マンガ",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class StatusFilter(
|
||||
values: Array<String> = statuses.keys.toTypedArray(),
|
||||
) : Select("地位", "status", values) {
|
||||
override val selection: String
|
||||
get() = statuses[values[state]]!!
|
||||
|
||||
companion object {
|
||||
private val statuses = mapOf(
|
||||
"全て" to "all",
|
||||
"Publishing" to "Publishing",
|
||||
"Finished" to "Finished",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageFilter(
|
||||
values: Array<String> = languages.keys.toTypedArray(),
|
||||
) : Select("言語", "language", values) {
|
||||
override val selection: String
|
||||
get() = languages[values[state]]!!
|
||||
|
||||
companion object {
|
||||
private val languages = mapOf(
|
||||
"全て" to "all",
|
||||
"Japanese" to "ja",
|
||||
"English" to "en",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SortFilter(
|
||||
values: Array<String> = sort.keys.toTypedArray(),
|
||||
) : Select("選別", "sort", values) {
|
||||
override val selection: String
|
||||
get() = sort[values[state]]!!
|
||||
|
||||
companion object {
|
||||
private val sort = mapOf(
|
||||
"デフォルト" to "default",
|
||||
"最新の更新" to "latest-updated",
|
||||
"最も見られました" to "most-viewed",
|
||||
"Title [A-Z]" to "title-az",
|
||||
"Title [Z-A]" to "title-za",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Genre(name: String, val id: String) : Filter.CheckBox(name)
|
||||
|
||||
class GenresFilter(
|
||||
values: List<Genre> = genres,
|
||||
) : Filter.Group<Genre>("ジャンル", values) {
|
||||
val param = "genre[]"
|
||||
|
||||
companion object {
|
||||
private val genres: List<Genre>
|
||||
get() = listOf(
|
||||
Genre("アクション", "55"),
|
||||
Genre("エッチ", "15706"),
|
||||
Genre("コメディ", "91"),
|
||||
Genre("ドラマ", "56"),
|
||||
Genre("ハーレム", "20"),
|
||||
Genre("ファンタジー", "1"),
|
||||
Genre("冒険", "54"),
|
||||
Genre("悪魔", "6820"),
|
||||
Genre("武道", "1064"),
|
||||
Genre("歴史的", "9600"),
|
||||
Genre("警察・特殊部隊", "6089"),
|
||||
Genre("車・バイク", "4329"),
|
||||
Genre("音楽", "473"),
|
||||
Genre("魔法", "1416"),
|
||||
)
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,22 @@
|
|||
package eu.kanade.tachiyomi.extension.es.atlantisscan
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import okhttp3.OkHttpClient
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AtlantisScan : MangaThemesia(
|
||||
"Atlantis Scan",
|
||||
"https://scansatlanticos.com",
|
||||
"es",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US),
|
||||
) {
|
||||
// Site moved from Madara to MangaThemesia
|
||||
override val versionId = 2
|
||||
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
.rateLimit(2, 1, TimeUnit.SECONDS)
|
||||
.build()
|
||||
}
|
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 9.4 KiB |
|
@ -0,0 +1,12 @@
|
|||
package eu.kanade.tachiyomi.extension.id.comicaso
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class Comicaso : MangaThemesia(
|
||||
"Comicaso",
|
||||
"https://comicaso.com",
|
||||
"id",
|
||||
dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("id")),
|
||||
)
|
|
@ -0,0 +1,27 @@
|
|||
package eu.kanade.tachiyomi.extension.id.comicsekai
|
||||
|
||||
import android.util.Base64
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class Comicsekai : MangaThemesia("Comicsekai", "http://www.comicsekai.com", "id") {
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
// "ts_reader.run({" in base64
|
||||
val script = document.selectFirst("script[src^=data:text/javascript;base64,dHNfcmVhZGVyLnJ1bih7]")
|
||||
?: return super.pageListParse(document)
|
||||
val data = Base64.decode(script.attr("src").substringAfter("base64,"), Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||
val imageListJson = JSON_IMAGE_LIST_REGEX.find(data)?.destructured?.toList()?.get(0).orEmpty()
|
||||
val imageList = try {
|
||||
json.parseToJsonElement(imageListJson).jsonArray
|
||||
} catch (_: IllegalArgumentException) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
return imageList.mapIndexed { i, jsonEl ->
|
||||
Page(i, imageUrl = jsonEl.jsonPrimitive.content)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package eu.kanade.tachiyomi.extension.th.dragonmanga
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class DragonManga : MangaThemesia(
|
||||
"DragonManga",
|
||||
"https://www.dragon-manga.com",
|
||||
"th",
|
||||
dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("th")),
|
||||
)
|
|
@ -8,7 +8,7 @@ import java.util.Locale
|
|||
|
||||
class InariManga : MangaThemesia(
|
||||
"InariManga",
|
||||
"https://inarimanga.com",
|
||||
"https://inarimanga.net",
|
||||
"es",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("en")),
|
||||
) {
|
||||
|
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 978 B |
After Width: | Height: | Size: 2.3 KiB |