Adds Schlock Mercenary (#3391)

* Start on the schlock mercenary extension

* Start on the new schlock mercenary plugin. Worked on getting books

* Current progress

move resoure files to correct position
get book and chapter fetching mostly working
fix extension name

* Remove log debug messages and fix chapter selection issue

* got pages working

* fix chapter selector to not select subchapter divisons

* re organize functions

* format code

* remove logging statement
This commit is contained in:
dakriy 2020-09-24 08:55:05 -07:00 committed by GitHub
parent 1fb4957117
commit a665c7bc1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 199 additions and 0 deletions

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Schlock Mercenary'
pkgNameSuffix = 'en.schlockmercenary'
extClass = '.Schlockmercenary'
extVersionCode = 1
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -0,0 +1,187 @@
package eu.kanade.tachiyomi.extension.en.schlockmercenary
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.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 java.lang.UnsupportedOperationException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
class Schlockmercenary : ParsedHttpSource() {
override val name = "Schlock Mercenary"
override val baseUrl = "https://www.schlockmercenary.com"
override val lang = "en"
override val supportsLatest = false
private var chapterCount = 1
// Books
override fun popularMangaRequest(page: Int): Request = GET("${baseUrl}$archiveUrl")
override fun popularMangaSelector(): String = "div.archive-book"
override fun popularMangaFromElement(element: Element): SManga {
val book = element.select("h4 > a").first()
val thumb = (baseUrl + (element.select("img").first()?.attr("src")
?: defaultThumbnailUrl)).substringBefore("?")
return SManga.create().apply {
url = book.attr("href")
title = book.text()
artist = "Howard Tayler"
author = "Howard Tayler"
// Schlock Mercenary finished as of July 2020
status = SManga.COMPLETED
description = element.select("p").first()?.text() ?: ""
thumbnail_url = thumb
}
}
// Chapters
override fun chapterListSelector() = "ul.chapters > li:not(ul > li > ul > li) > a"
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val requestUrl = "${baseUrl}$archiveUrl"
return client.newCall(GET(requestUrl))
.asObservableSuccess()
.map { response ->
getChaptersForBook(response, manga)
}
}
private fun getChaptersForBook(response: Response, manga: SManga): List<SChapter> {
val document = response.asJsoup()
val sanitizedTitle = manga.title.replace("""([",'])""".toRegex(), "\\\\$1")
val book = document.select(popularMangaSelector() + ":contains($sanitizedTitle)")
val chapters = mutableListOf<SChapter>()
chapterCount = 1
book.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
return chapters
}
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()
chapter.url = element.attr("href")
chapter.name = element.text()
chapter.chapter_number = chapterCount++.toFloat()
chapter.date_upload = chapter.url.takeLast(10).let {
SimpleDateFormat(dateFormat, Locale.getDefault()).parse(it)!!.time
}
return chapter
}
// Pages
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
val requestUrl = "${baseUrl}$archiveUrl"
return client.newCall(GET(requestUrl))
.asObservableSuccess()
.map { response ->
getPagesForChapter(response, chapter)
}
}
private fun getPagesForChapter(response: Response, chapter: SChapter): List<Page> {
val document = response.asJsoup()
/**
* To find the end page, first, the next chapter start must be found,
* then subtract one day off of the next chapter start.
* If no chapter start is found, grab the next book start.
* If no next book exists, assume one page long.
*/
val currentChapter = document.select(chapterListSelector() + "[href=${chapter.url}]").first()!!
val start = chapterFromElement(currentChapter).date_upload
// Find next chapter start
var nextChapter = currentChapter.parent()?.nextElementSibling()?.select("a")?.first()
var end = start + 1
// Chapter is the last in the book
if (nextChapter == null) {
// Grab next book start.
nextChapter = currentChapter.parents()[2]?.nextElementSibling()?.select(chapterListSelector())?.first()
}
if (nextChapter != null) {
end = chapterFromElement(nextChapter).date_upload
}
return generatePageListBetweenDates(start, end)
}
private fun generatePageListBetweenDates(start: Long, end: Long): List<Page> {
val pages = mutableListOf<Page>()
val calendar = GregorianCalendar()
calendar.time = Date(start)
while (calendar.time.before(Date(end))) {
val result = calendar.time
val formatter = SimpleDateFormat(dateFormat, Locale.getDefault())
val today = formatter.format(result)
getImageUrlsForDay(today).forEach { pages.add(Page(pages.size, "", it)) }
calendar.add(Calendar.DATE, 1)
}
return pages
}
private fun getImageUrlsForDay(day: String): List<String> {
val requestUrl = "$baseUrl/$day"
val document = client.newCall(GET(requestUrl)).execute().asJsoup()
val urls = document.select("div#strip-$day > img").map { it.attr("abs:src") }
return urls
}
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga)
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false))
override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
override fun searchMangaNextPageSelector(): String? = throw UnsupportedOperationException("Not used")
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
override fun popularMangaNextPageSelector(): String? = null
override fun searchMangaSelector(): String = throw UnsupportedOperationException("Not used")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used")
override fun mangaDetailsParse(document: Document): SManga = throw UnsupportedOperationException("Not used")
override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used")
override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used")
override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used")
override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used")
companion object {
const val defaultThumbnailUrl = "/static/img/logo.b6dacbb8.jpg"
const val archiveUrl = "/archives/"
const val dateFormat = "yyyy-MM-dd"
}
}