parent
bd43b9f982
commit
ef491c08ea
|
@ -5,7 +5,7 @@ ext {
|
||||||
extName = 'Kill Six Billion Demons'
|
extName = 'Kill Six Billion Demons'
|
||||||
pkgNameSuffix = 'en.killsixbilliondemons'
|
pkgNameSuffix = 'en.killsixbilliondemons'
|
||||||
extClass = '.KillSixBillionDemons'
|
extClass = '.KillSixBillionDemons'
|
||||||
extVersionCode = 3
|
extVersionCode = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.killsixbilliondemons
|
package eu.kanade.tachiyomi.extension.en.killsixbilliondemons
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
@ -11,12 +10,15 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* original creator:
|
||||||
* @author Aria Moradi <aria.moradi007@gmail.com>
|
* @author Aria Moradi <aria.moradi007@gmail.com>
|
||||||
|
* fixed some bugs and added title pages:
|
||||||
|
* @author THE_ORONCO <the_oronco@posteo.net>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class KillSixBillionDemons : HttpSource() {
|
class KillSixBillionDemons : HttpSource() {
|
||||||
|
@ -29,138 +31,197 @@ class KillSixBillionDemons : HttpSource() {
|
||||||
|
|
||||||
override val supportsLatest: Boolean = false
|
override val supportsLatest: Boolean = false
|
||||||
|
|
||||||
// list of books
|
private val authorKSBD = "Abbadon"
|
||||||
|
private val bookSelector: String = "#chapter option:contains(book)"
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
private val pagesOrder = "?order=ASC"
|
||||||
val document = response.asJsoup()
|
private val urlDateFormat = SimpleDateFormat("yyyy/MM", Locale.US)
|
||||||
|
private val descriptionKSBD =
|
||||||
val mangas = document.select(popularMangaSelector()).map { element ->
|
"Q: What is this all about?\nThis is a webcomic! It’s graphic novel style, meaning it’s meant to be read in large chunks, but you can subject yourself to the agony of reading it a couple pages a week!\n" +
|
||||||
popularMangaFromElement(element)
|
"\nQ: Do you have a twitter/tumble machine? Just who the hell draws this thing anyway?\n" +
|
||||||
}
|
"A mysterious comics goblin named Abbadon draws this mess. My twitter is @orbitaldropkick, my tumblr is orbitaldropkick.tumblr.com. If you’re feeling dangerous, you can e-mail me at ksbdabbadon@gmail.com\n" +
|
||||||
|
"\nQ: A webcomic, eh? When does it update?\n" +
|
||||||
return MangasPage(mangas, false)
|
"Tuesday and Friday evenings (and occasionally weekends). Sometimes it will be up quite late on those days.\n" +
|
||||||
}
|
"\nQ: Who’s this YISUN guy that keeps getting talked about?\n" +
|
||||||
|
"Someone has not read their Psalms and Spasms recently!\n" +
|
||||||
|
"\nQ: What’s this about suggestions?\n" +
|
||||||
|
"KSBD will periodically take suggestions, mostly on characters to stick in the background. You can also stick fanart, character ideas, concepts, and literature in the ‘Submit’ section up above. You need tumblr for this. If you want to suggest directly, the best way to do it is through the comments section below the comic! A huge chunk of minor characters have been named and inspired by reader comments so far.\n" +
|
||||||
|
"\nQ: Can I buy this book in a more traditional format?\n" +
|
||||||
|
"You absolutely can. You can get your hands on a print copy of the first and second books from Image comics in your local comics shop or anywhere else you can get comics. It looks fantastic in print and if you don’t like reading stuff online I highly recommend it."
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
return GET(baseUrl)
|
return GET(baseUrl, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun popularMangaSelector(): String {
|
// list of books
|
||||||
return "#chapter option:contains(book)"
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
return generateKSBDMangasPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun popularMangaFromElement(element: Element): SManga {
|
/**
|
||||||
return SManga.create().apply {
|
* @return the MangasPage containing the different books of Kill Six Billion Demons as manga
|
||||||
title = element.text().substringBefore(" (")
|
*/
|
||||||
thumbnail_url = "https://dummyimage.com/768x994/000/ffffff.jpg&text=$title"
|
private fun generateKSBDMangasPage(): MangasPage {
|
||||||
artist = "Abbadon"
|
return MangasPage(fetchBooksAsMangas(), false)
|
||||||
author = "Abbadon"
|
}
|
||||||
status = SManga.UNKNOWN
|
|
||||||
url = title // this url is not useful at all but must set to something unique or the app breaks!
|
/**
|
||||||
|
* This fetches the different books of Kill Six Billion Demons as different manga.
|
||||||
|
* @return a list of all books in form of multiple manga
|
||||||
|
*/
|
||||||
|
private fun fetchBooksAsMangas(): List<SManga> {
|
||||||
|
val doc = client.newCall(GET(baseUrl, headers)).execute().asJsoup()
|
||||||
|
val bookElements = doc.select(bookSelector)
|
||||||
|
return bookElements.map { bookElement ->
|
||||||
|
val bookOverviewUrl = bookElement.attr("value")
|
||||||
|
val bookTitle = bookElement.text().substringBefore(" (")
|
||||||
|
|
||||||
|
SManga.create().apply {
|
||||||
|
title = bookTitle
|
||||||
|
setUrlWithoutDomain(bookOverviewUrl)
|
||||||
|
artist = authorKSBD
|
||||||
|
author = authorKSBD
|
||||||
|
description = descriptionKSBD
|
||||||
|
thumbnail_url = fetchThumbnailUrl(bookOverviewUrl)
|
||||||
|
status = fetchStatusForBook(bookTitle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This fetches the Thumbnail given the url to a book overview. In ascending order the first
|
||||||
|
* image will always be the cover of the given book.
|
||||||
|
*
|
||||||
|
* @param bookOverviewUrl url to the book overview
|
||||||
|
* @return url to the cover of the book
|
||||||
|
*/
|
||||||
|
private fun fetchThumbnailUrl(bookOverviewUrl: String): String {
|
||||||
|
val overviewDoc =
|
||||||
|
client.newCall(GET(bookOverviewUrl + pagesOrder, headers)).execute().asJsoup()
|
||||||
|
return overviewDoc.selectFirst(".comic-thumbnail-in-archive a img").attr("src")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.lowercase(): String {
|
||||||
|
return this.toLowerCase(Locale.ROOT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SManga status for a given book by checking if the title of the newest page contains
|
||||||
|
* the title of the the given book.
|
||||||
|
*
|
||||||
|
* @param bookTitle name of the book the status should be fetched for
|
||||||
|
* @return the status of the book (as Enum value of SManga because chapters are mangas)
|
||||||
|
*/
|
||||||
|
private fun fetchStatusForBook(bookTitle: String): Int {
|
||||||
|
val bookTitleWithoutBook = bookTitle.substringAfter(": ")
|
||||||
|
val newestPage = client.newCall(GET(baseUrl, headers)).execute().asJsoup()
|
||||||
|
val postTitle = newestPage.selectFirst(".post-title")?.text() ?: ""
|
||||||
|
// title is "<book name> <page(s)>"
|
||||||
|
return if (postTitle.lowercase().contains(bookTitleWithoutBook.lowercase())) SManga.UNKNOWN
|
||||||
|
else SManga.COMPLETED
|
||||||
|
}
|
||||||
|
|
||||||
// latest Updates not used
|
// latest Updates not used
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used")
|
override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used")
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
|
override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used")
|
||||||
|
|
||||||
// books dont change around here, but still write the data again to avoid bugs in backup restore
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(popularMangaRequest(1))
|
return Observable.just(fetchBooksAsMangas().find { manga.title == it.title })
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
popularMangaParse(response).mangas.find { manga.title == it.title }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used")
|
override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used")
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
return client.newCall(chapterListRequest(manga))
|
return Observable.just(
|
||||||
.asObservableSuccess()
|
fetchChapterListTR(
|
||||||
.map { response ->
|
baseUrl + manga.url + pagesOrder,
|
||||||
chapterListParse(response, manga)
|
mutableListOf()
|
||||||
}
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request = popularMangaRequest(1)
|
/**
|
||||||
|
* Though this is recursive this will be optimized by the compiler into a for loop equivalent
|
||||||
|
* thing. This has to be done this way because the maximum number of further chapter overview
|
||||||
|
* pages that will be shown on one chapter overview page will at maximum be 5 enven though there
|
||||||
|
* might be more.
|
||||||
|
*
|
||||||
|
* @param currentUrl url of the current page / the page this algorithm will start the recursion on
|
||||||
|
* @param foundChapters a list of all found chapters (should be empty)
|
||||||
|
* @return a list of all chapters that were found under "currentUrl" and following pages
|
||||||
|
*/
|
||||||
|
private tailrec fun fetchChapterListTR(
|
||||||
|
currentUrl: String,
|
||||||
|
foundChapters: MutableList<SChapter>
|
||||||
|
): MutableList<SChapter> {
|
||||||
|
|
||||||
|
val numberOfPreviousChapters = foundChapters.size
|
||||||
|
val currentPage = client.newCall(GET(currentUrl, headers)).execute().asJsoup()
|
||||||
|
val chaptersOnCurrentPage = currentPage.select(".post-content")
|
||||||
|
.mapIndexed { index, chapterElement ->
|
||||||
|
val chapterTitle: String = chapterElement.select(".post-title a").text()
|
||||||
|
val chapterUrl: String =
|
||||||
|
chapterElement.select(".comic-thumbnail-in-archive a").attr("href")
|
||||||
|
val imageUrl =
|
||||||
|
chapterElement.select(".comic-thumbnail-in-archive a img").attr("src")
|
||||||
|
|
||||||
|
SChapter.create().apply {
|
||||||
|
setUrlWithoutDomain(chapterUrl)
|
||||||
|
name = chapterTitle
|
||||||
|
chapter_number = numberOfPreviousChapters + index + 1f
|
||||||
|
date_upload = extractDateFromImageUrl(imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foundChapters.addAll(chaptersOnCurrentPage)
|
||||||
|
|
||||||
|
val potentialNextPageUrl = currentPage.select(".paginav-next a").attr("href")
|
||||||
|
return if (potentialNextPageUrl.isEmpty()) {
|
||||||
|
foundChapters
|
||||||
|
} else {
|
||||||
|
fetchChapterListTR(potentialNextPageUrl, foundChapters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param imageUrl Url the date should be got from
|
||||||
|
* @return date of the image upload as a long
|
||||||
|
*/
|
||||||
|
private fun extractDateFromImageUrl(imageUrl: String): Long {
|
||||||
|
val dateRegex = "[0-9]{4}/[0-9]{2}".toRegex()
|
||||||
|
val dateString = dateRegex.find(imageUrl)
|
||||||
|
return if (dateString?.value != null) {
|
||||||
|
return urlDateFormat.parse(dateString.value)?.time ?: 0L
|
||||||
|
} else 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga): Request = throw Exception("Not used")
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> = throw Exception("Not used")
|
override fun chapterListParse(response: Response): List<SChapter> = throw Exception("Not used")
|
||||||
|
|
||||||
private fun chapterListParse(response: Response, manga: SManga): List<SChapter> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
|
|
||||||
val options = document.select(chapterListSelector())
|
|
||||||
|
|
||||||
val chapters = mutableListOf<SChapter>()
|
|
||||||
var bookNum = 0
|
|
||||||
val targetBookNum = manga.title.split(":")[0].split(" ")[1].toInt()
|
|
||||||
|
|
||||||
for (element in options) {
|
|
||||||
val text = element.text()
|
|
||||||
if (text.startsWith("Book")) {
|
|
||||||
bookNum += 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (bookNum > targetBookNum)
|
|
||||||
break
|
|
||||||
|
|
||||||
if (bookNum == targetBookNum) {
|
|
||||||
chapters.add(
|
|
||||||
SChapter.create().apply {
|
|
||||||
url = element.attr("value")
|
|
||||||
|
|
||||||
val textSplit = text.split(" ")
|
|
||||||
|
|
||||||
name = "Chapter ${textSplit[0]}"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chapters.reversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun chapterListSelector(): String {
|
|
||||||
return "#chapter option"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||||
val wordpressPages = mutableListOf<Document>()
|
val chapterDoc = client.newCall(GET(baseUrl + chapter.url, headers)).execute().asJsoup()
|
||||||
// get the first page and add ir to the list
|
val pages = chapterDoc.select("#comic img")
|
||||||
val firstPageURL = chapter.url + "?order=ASC" // change the url to ask Wordpress to reverse the posts
|
.mapIndexed { index, imageElement ->
|
||||||
val firstPage = client.newCall(GET(firstPageURL)).execute().asJsoup()
|
val imageUrl = imageElement.attr("src")
|
||||||
wordpressPages.add(firstPage)
|
Page(index + 1, "", imageUrl)
|
||||||
|
|
||||||
val otherPages = firstPage.select("#paginav a")
|
|
||||||
|
|
||||||
for (i in 0 until (otherPages.size - 1)) // ignore the last one (last page button)
|
|
||||||
wordpressPages.add(client.newCall(GET(otherPages[i].attr("href"))).execute().asJsoup())
|
|
||||||
|
|
||||||
val chapterPages = mutableListOf<Page>()
|
|
||||||
var pageNum = 1
|
|
||||||
|
|
||||||
wordpressPages.forEach { wordpressPage ->
|
|
||||||
wordpressPage.select(".post-content .entry a:has(img)").forEach { postImage ->
|
|
||||||
chapterPages.add(
|
|
||||||
Page(pageNum, postImage.attr("href"), postImage.select("img").attr("src"))
|
|
||||||
)
|
|
||||||
pageNum++
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Observable.just(chapterPages)
|
return Observable.just(pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String = throw Exception("Not used")
|
override fun imageUrlParse(response: Response): String = throw Exception("Not used")
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> = throw Exception("Not used")
|
override fun pageListParse(response: Response): List<Page> = throw Exception("Not used")
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = throw Exception("Search functionality is not available.")
|
override fun fetchSearchManga(
|
||||||
|
page: Int,
|
||||||
|
query: String,
|
||||||
|
filters: FilterList
|
||||||
|
): Observable<MangasPage> = throw Exception("Search functionality is not available.")
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used")
|
override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used")
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Not used")
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
|
||||||
|
throw Exception("Not used")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue