diff --git a/purplecress/AndroidManifest.xml b/purplecress/AndroidManifest.xml new file mode 100644 index 000000000..0d7e33d58 --- /dev/null +++ b/purplecress/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/purplecress/build.gradle b/purplecress/build.gradle new file mode 100644 index 000000000..9054d302b --- /dev/null +++ b/purplecress/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Purple Cress' + pkgNameSuffix = 'en.purplecress' + extClass = '.PurpleCress' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/purplecress/res/mipmap-hdpi/ic_launcher.png b/purplecress/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..7f6f6eb1c Binary files /dev/null and b/purplecress/res/mipmap-hdpi/ic_launcher.png differ diff --git a/purplecress/res/mipmap-mdpi/ic_launcher.png b/purplecress/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..d1edc90ab Binary files /dev/null and b/purplecress/res/mipmap-mdpi/ic_launcher.png differ diff --git a/purplecress/res/mipmap-xhdpi/ic_launcher.png b/purplecress/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..9a3537aa0 Binary files /dev/null and b/purplecress/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/purplecress/res/mipmap-xxhdpi/ic_launcher.png b/purplecress/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..1eabaa49b Binary files /dev/null and b/purplecress/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/purplecress/res/mipmap-xxxhdpi/ic_launcher.png b/purplecress/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..8919f8318 Binary files /dev/null and b/purplecress/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/purplecress/res/web_hi_res_512.png b/purplecress/res/web_hi_res_512.png new file mode 100644 index 000000000..0f46b816f Binary files /dev/null and b/purplecress/res/web_hi_res_512.png differ diff --git a/purplecress/src/eu/kanade/tachiyomi/extension/en/purplecress/PurpleCress.kt b/purplecress/src/eu/kanade/tachiyomi/extension/en/purplecress/PurpleCress.kt new file mode 100644 index 000000000..70431ff1f --- /dev/null +++ b/purplecress/src/eu/kanade/tachiyomi/extension/en/purplecress/PurpleCress.kt @@ -0,0 +1,133 @@ +package eu.kanade.tachiyomi.extension.en.purplecress + +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.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import java.text.SimpleDateFormat +import java.util.Locale + +class PurpleCress : HttpSource() { + override val name = "Purple Cress" + + override val baseUrl = "https://purplecress.com" + + override val lang = "en" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + override fun popularMangaRequest(page: Int) = GET(baseUrl) + + override fun popularMangaParse(response: Response): MangasPage { + val seriesContainer = response.asJsoup().selectFirst("div.container-grid--small") + val mangaList: List = seriesContainer.select("a").map { + SManga.create().apply { + title = it.selectFirst("div.card__info").selectFirst("h3").html() + url = it.attr("href") + author = it.selectFirst("p.card__author").html().substringAfter("by ") + artist = author + description = it.attr("description") + thumbnail_url = it.selectFirst("img.image").attr("src") + status = when (it.selectFirst("h3.card__status").html()) { + "Ongoing" -> SManga.ONGOING + "Dropped" -> SManga.COMPLETED // Not sure what the best status is for "Dropped" + "Completed" -> SManga.COMPLETED // There aren't any completed series on the site, so I'm just guessing as to the string + else -> SManga.UNKNOWN + } + initialized = true // We have all the fields + } + } + return MangasPage(mangaList, false) + } + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl) + + override fun latestUpdatesParse(response: Response): MangasPage { + val seriesContainer = response.asJsoup().selectFirst("div.container-grid--large") + val mangaList: List = seriesContainer.select("a").map { + SManga.create().apply { + title = it.selectFirst("h3.chapter__series-name").html() + url = it.attr("href").replaceFirst("chapter", "series").substringBeforeLast("/") + thumbnail_url = it.selectFirst("img.image").attr("src") + initialized = false + } + } + return MangasPage(mangaList, false) + } + + override fun fetchMangaDetails(manga: SManga): Observable { + val oldUrl = manga.url + return client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess() + .map { response -> + mangaDetailsParse(response).apply { + initialized = true + url = oldUrl // Sets URL in result to original URL + } + } + } + + override fun mangaDetailsRequest(manga: SManga): Request = chapterListRequest(manga) + + override fun mangaDetailsParse(response: Response): SManga { + val responseJ = response.asJsoup() + val infoBox = responseJ.selectFirst("div.series__info") + return SManga.create().apply { + title = infoBox.selectFirst("h1.series__name").html() + // url is set by overridden fetchMangaDetails + author = infoBox.selectFirst("p.series__author").html().substringAfter("by ") + artist = author + description = infoBox.selectFirst("p.description-pagagraph").html() + thumbnail_url = responseJ.selectFirst("img.thumbnail").attr("src") + status = when (infoBox.selectFirst("span.series__status").html()) { + "Ongoing" -> SManga.ONGOING + "Dropped" -> SManga.COMPLETED // See comments in popularMangaParse + "Completed" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + } + } + + override fun chapterListParse(response: Response): List { + return response.asJsoup().select("a.chapter__card") + .map { + SChapter.create().apply { + url = it.attr("href") + name = it.selectFirst("span.chapter__name").html() + date_upload = it.selectFirst("h5.chapter__date").html() + .let { SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it)?.time ?: 0L } + } + } + } + + override fun pageListParse(response: Response): List { + return response.asJsoup().select("img.page__img").mapIndexed { index, element -> + Page(index, "", element.attr("src")) + } + } + + override fun fetchImageUrl(page: Page): Observable { + return Observable.just(page.imageUrl) + } + + override fun imageUrlRequest(page: Page): Request = throw UnsupportedOperationException("Not used") + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable = Observable.just(MangasPage(emptyList(), false)) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") + + override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") +}