Add extension CommitStrip (#8878)
* the journey to a commitstrip ext begins copies over skeleton code from reallifecomics and renames files and folders to match new extension also creates a sourcefactory since the site has both en and fr variants * generate SManga entries since the site is able to show comics by year, we'll create one SManga entry for every year. Also aware of siteLang since it has brings some changes Archive years start from 2012 to current year * update build.gradle with commitstrip details uses CommitStripFactory * add chapter list selector and parse it comic entries don't really have a numbering in the website except for their dates so we'll just keep our own numbering needs to be reversed because the website shows the latest one first need to add paging * use better logo url * parse chapter from Element gets the url, regexes the url to find a date, and sets a name using a selector on the chapter element still didnt attempt paging yet btw * use correct selector for chapter pages images * fix package info actually forgot abt the things at top also made `siteLang` private because android studio recommended that * actually use the correct logo link for thumbnail... * use `lang` instead of `siteLang` at some places `siteLang` is used when creating URLs and for extension stuff like creating SManga object, it's better to use `lang` I feel. In the end, it doesn't matter in this case because both are the same for this extension. * remote `private val` as per speculation constructor parameter is never used as a property and so `val` can be removed this avoids more memory use due to unnecessary use - android studio * Add ext app icon The entire image assets placed in `res` folder. Made possible by their actual logo (small version), which for some reason refused to work as `SManga.thumbnail_url` * linting * adjust `baseUrl` usages rather than having `siteLang` inside the `baseUrl`, better to have it explicitly used everytime. also fixes `thumbnail_url` because the link to the logo is broken when you include `siteLang` in `baseUrl` * create `manga.url` explicitly since fetchChapterList will be overridden and its easier to have a full url to make a client.newCall rather than setUrlWithoutDomain * `thumbnail_url` doesn't need `siteLang` and I forgot abt that * add pagination for fetchChapterList tried to reuse whatever I already had to support pages might be slow because I do one extra request for Page 1 and some manga have too many pages (like 11) each with 20 comics * rewrite how pages are obtained since I changed `manga.url`, that changes `chapter.url` and so that implied a change to the page list as well. * maybe optimize chapterList a bit still a bit slow because some of them have a lot of pages but wanted to reduce an extra request just to get total no of pages. * just use your own links for the logo can't get the logo to load from the link even though it worked. Might as well get your own links to the logo. While I was searching, also found a cool logo in FR so I kept that. Both logos taken from the webcomics' Kickstarter and Ulule fundraisers * Revert "maybe optimize chapterList a bit" This reverts commit 672db52460cf597f28a80f24de3a7659f4c7a939. because that didn't work. at all.. * remove unused variable
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -0,0 +1,12 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'Commit Strip'
|
||||
pkgNameSuffix = 'all.commitstrip'
|
||||
extClass = '.CommitStripFactory'
|
||||
extVersionCode = 1
|
||||
libVersion = '1.2'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 38 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,185 @@
|
|||
package eu.kanade.tachiyomi.extension.all.commitstrip
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
abstract class CommitStrip(
|
||||
override val lang: String,
|
||||
private val siteLang: String
|
||||
) : ParsedHttpSource() {
|
||||
|
||||
override val name = "Commit Strip"
|
||||
override val baseUrl = "https://www.commitstrip.com"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
// Helper
|
||||
|
||||
private fun createManga(year: Int): SManga = SManga.create().apply {
|
||||
url = "$baseUrl/$siteLang/$year"
|
||||
title = "$name ($year)"
|
||||
thumbnail_url = when (lang) {
|
||||
"en" -> LOGO_EN
|
||||
"fr" -> LOGO_FR
|
||||
else -> LOGO_EN
|
||||
}
|
||||
author = when (lang) {
|
||||
"en" -> AUTHOR_EN
|
||||
"fr" -> AUTHOR_FR
|
||||
else -> AUTHOR_EN
|
||||
}
|
||||
artist = ARTIST
|
||||
status = if (year != currentYear) SManga.COMPLETED else SManga.ONGOING
|
||||
description = when (lang) {
|
||||
"en" -> "$SUMMARY_EN $NOTE $year"
|
||||
"fr" -> "$SUMMARY_FR $NOTE $year"
|
||||
else -> "$SUMMARY_EN $NOTE $year"
|
||||
}
|
||||
}
|
||||
|
||||
// Popular
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
// have one manga entry for each year
|
||||
return (currentYear downTo 2012)
|
||||
.map { createManga(it) }
|
||||
.let { Observable.just(MangasPage(it, false))!! }
|
||||
}
|
||||
|
||||
// Search
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
|
||||
fetchPopularManga(1).map { mangaList ->
|
||||
mangaList.copy(mangaList.mangas.filter { it.title.contains(query) })
|
||||
}
|
||||
|
||||
// Details
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga) = Observable.just(
|
||||
manga.apply {
|
||||
initialized = true
|
||||
}
|
||||
)!!
|
||||
|
||||
// Chapters
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
// create a new call to parse the no of pages in the site
|
||||
// example responseString - Page 1 of 11
|
||||
val responseString = client.newCall(GET("${manga.url}", headers)).execute().run {
|
||||
asJsoup().select(".wp-pagenavi .pages").first().text()
|
||||
}
|
||||
// use regex to get the last number (i.e. 11 above)
|
||||
val pages = Regex("\\d+").findAll(responseString).last().value.toInt()
|
||||
|
||||
return (1..pages).map {
|
||||
val response = chapterListRequest(manga, it)
|
||||
chapterListParse(response)
|
||||
}.let { Observable.just(it.flatten()) }
|
||||
}
|
||||
|
||||
private fun chapterListRequest(manga: SManga, page: Int): Response =
|
||||
client.newCall(GET("${manga.url}/page/$page", headers)).execute().run {
|
||||
if (!isSuccessful) {
|
||||
close()
|
||||
throw Exception("HTTP error $code")
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
return super.chapterListParse(response).reversed().distinct().mapIndexed { index, chapter ->
|
||||
chapter.apply { chapter_number = index.toFloat() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = ".excerpt a"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
url = element.attr("href")
|
||||
|
||||
// get the chapter date from the url
|
||||
val date = Regex("\\d{4}\\/\\d{2}\\/\\d{2}").find(url)?.value
|
||||
val parsedDate = SimpleDateFormat("yyyy/MM/dd", Locale.US).parse(date)
|
||||
date_upload = parsedDate?.time ?: 0L
|
||||
|
||||
name = element.select("span").text()
|
||||
}
|
||||
|
||||
// Page
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
return client.newCall(GET("${chapter.url}", headers)).execute().run {
|
||||
asJsoup().select(".entry-content p img").attr("src")
|
||||
}.let {
|
||||
Observable.just(listOf(Page(0, "", it)))
|
||||
}
|
||||
}
|
||||
|
||||
// Unsupported
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> = throw Exception("Not Used")
|
||||
|
||||
override fun imageUrlParse(document: Document) = throw Exception("Not used")
|
||||
|
||||
override fun popularMangaSelector() = throw Exception("Not used")
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = throw Exception("Not used")
|
||||
|
||||
override fun searchMangaNextPageSelector() = throw Exception("Not used")
|
||||
|
||||
override fun searchMangaSelector() = throw Exception("Not used")
|
||||
|
||||
override fun popularMangaRequest(page: Int) = throw Exception("Not used")
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception("Not used")
|
||||
|
||||
override fun popularMangaNextPageSelector() = throw Exception("Not used")
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = throw Exception("Not used")
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = throw Exception("Not used")
|
||||
|
||||
override fun latestUpdatesSelector() = throw Exception("Not used")
|
||||
|
||||
companion object {
|
||||
private const val LOGO_EN = "https://i.imgur.com/HODJlt9.jpg"
|
||||
|
||||
private const val LOGO_FR = "https://i.imgur.com/I7ps9zS.jpg"
|
||||
|
||||
private const val AUTHOR_EN = "Mark Nightingale"
|
||||
|
||||
private const val AUTHOR_FR = "Thomas Gx"
|
||||
|
||||
private const val ARTIST = "Etienne Issartial"
|
||||
|
||||
private const val SUMMARY_EN = "The blog relating the daily life of web agency developers."
|
||||
|
||||
private const val SUMMARY_FR = "Le blog qui raconte la vie des codeurs"
|
||||
|
||||
private const val NOTE = "\n\nNote: This entry includes all the chapters published in"
|
||||
|
||||
private val currentYear by lazy {
|
||||
Calendar.getInstance()[Calendar.YEAR]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package eu.kanade.tachiyomi.extension.all.commitstrip
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
|
||||
class CommitStripFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(
|
||||
CommitStripEnglish(),
|
||||
CommitStripFrench(),
|
||||
)
|
||||
}
|
||||
|
||||
class CommitStripEnglish() : CommitStrip("en", "en")
|
||||
class CommitStripFrench() : CommitStrip("fr", "fr")
|