Convert Arc-Relight extension to MangAdventure bundle (#905)

Convert Arc-Relight extension to MangAdventure bundle
This commit is contained in:
ObserverOfTime 2019-03-15 10:14:50 +00:00 committed by Carlos
parent 3b82504fc0
commit 6591683ec2
17 changed files with 164 additions and 120 deletions

View File

@ -2,10 +2,10 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
ext { ext {
appName = 'Tachiyomi: Arc-Relight' appName = 'Tachiyomi: MangAdventure'
pkgNameSuffix = 'en.arcrelight' pkgNameSuffix = 'all.mangadventure'
extClass = '.ArcRelight' extClass = '.MangAdventureFactory'
extVersionCode = 2 extVersionCode = 1
libVersion = '1.2' libVersion = '1.2'
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -1,9 +1,10 @@
package eu.kanade.tachiyomi.extension.en.arcrelight package eu.kanade.tachiyomi.extension.all.mangadventure
import android.net.Uri import android.net.Uri
import android.os.Build.VERSION import android.os.Build.VERSION
import eu.kanade.tachiyomi.extension.BuildConfig import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
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
@ -15,16 +16,20 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Locale
/** Arc-Relight source */ /** MangAdventure source. */
class ArcRelight : HttpSource() { open class MangAdventure(
override val versionId = 1 override val name: String,
override val baseUrl: String,
val categories: Array<String> = DEFAULT_CATEGORIES,
override val lang: String = "en",
override val versionId: Int = 1,
apiPath: String = "/api") : HttpSource() {
override val name = "Arc-Relight" /** The URL to the site's API. */
open val apiUrl by lazy { "$baseUrl/$apiPath/v$versionId" }
override val baseUrl = "https://arc-relight.site/api/v$versionId"
override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
@ -43,31 +48,22 @@ class ArcRelight : HttpSource() {
} }
override fun latestUpdatesRequest(page: Int) = GET( override fun latestUpdatesRequest(page: Int) = GET(
"$baseUrl/releases/", headers "$apiUrl/releases/", headers
) )
override fun pageListRequest(chapter: SChapter) = GET( override fun pageListRequest(chapter: SChapter) = GET(
"$baseUrl/series/${chapter.url.substringAfter("/reader/")}", headers "$apiUrl/series/${chapter.url.substringAfter("/reader/")}", headers
) )
override fun chapterListRequest(manga: SManga) = GET( override fun chapterListRequest(manga: SManga) = GET(
"$baseUrl/series/${Uri.parse(manga.url).lastPathSegment}/", headers "$apiUrl/series/${Uri.parse(manga.url).lastPathSegment}/", headers
) )
override fun mangaDetailsRequest(manga: SManga): Request { override fun mangaDetailsRequest(manga: SManga) = chapterListRequest(manga)
// Workaround to get the proper URL in openInBrowser
val method = Thread.currentThread()
.stackTrace.getOrNull(2)?.methodName ?: ""
return if (method == "openInBrowser") {
GET(manga.url, headers)
} else {
chapterListRequest(manga)
}
}
override fun searchMangaRequest(page: Int, query: String, override fun searchMangaRequest(page: Int, query: String,
filters: FilterList): Request { filters: FilterList): Request {
val uri = Uri.parse("$baseUrl/series/").buildUpon() val uri = Uri.parse("$apiUrl/series/").buildUpon()
uri.appendQueryParameter("q", query) uri.appendQueryParameter("q", query)
val cat = mutableListOf<String>() val cat = mutableListOf<String>()
filters.forEach { filters.forEach {
@ -107,19 +103,19 @@ class ArcRelight : HttpSource() {
volumes.keys().forEach { vol -> volumes.keys().forEach { vol ->
val chapters = volumes.getJSONObject(vol) val chapters = volumes.getJSONObject(vol)
chapters.keys().forEach { ch -> chapters.keys().forEach { ch ->
ret.add(SChapter.create().apply { ret.add(SChapter.create().fromJSON(
fromJSON(chapters.getJSONObject(ch).also { chapters.getJSONObject(ch).also {
it.put("volume", vol) it.put("volume", vol)
it.put("chapter", ch) it.put("chapter", ch)
}) }
}) ))
} }
} }
return ret.sortedByDescending { it.name } return ret.sortedByDescending { it.name }
} }
override fun mangaDetailsParse(response: Response) = SManga.create() override fun mangaDetailsParse(response: Response) =
.apply { fromJSON(JSONObject(response.body()!!.string())) } SManga.create().fromJSON(JSONObject(response.body()!!.string()))
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val obj = JSONObject(response.body()!!.string()) val obj = JSONObject(response.body()!!.string())
@ -165,5 +161,103 @@ class ArcRelight : HttpSource() {
throw UnsupportedOperationException( throw UnsupportedOperationException(
"This method should not be called!" "This method should not be called!"
) )
companion object {
/** The possible statuses of a manga. */
private val STATUSES = arrayOf("Any", "Completed", "Ongoing")
/** Manga categories from MangAdventure `categories.xml` fixture. */
internal val DEFAULT_CATEGORIES = arrayOf(
"4-Koma",
"Action",
"Adventure",
"Comedy",
"Doujinshi",
"Drama",
"Ecchi",
"Fantasy",
"Gender Bender",
"Harem",
"Hentai",
"Historical",
"Horror",
"Josei",
"Martial Arts",
"Mecha",
"Mystery",
"Psychological",
"Romance",
"School Life",
"Sci-Fi",
"Seinen",
"Shoujo",
"Shoujo Ai",
"Shounen",
"Shounen Ai",
"Slice of Life",
"Smut",
"Sports",
"Supernatural",
"Tragedy",
"Yaoi",
"Yuri"
)
/**
* The HTTP date format specified in
* [RFC 1123](https://tools.ietf.org/html/rfc1123#page-55).
*/
private const val HTTP_DATE = "EEE, dd MMM yyyy HH:mm:ss zzz"
/**
* Converts a date in the [HTTP_DATE] format to a Unix timestamp.
*
* @param date The date to convert.
* @return The timestamp of the date.
*/
fun httpDateToTimestamp(date: String) =
SimpleDateFormat(HTTP_DATE, Locale.US).parse(date).time
}
/**
* Filter representing the status of a manga.
*
* @constructor Creates a [Filter.Select] object with [STATUSES].
*/
inner class Status : Filter.Select<String>("Status", STATUSES) {
/** Returns the [state] as a string. */
fun string() = values[state].toLowerCase()
}
/**
* Filter representing a manga category.
*
* @property name The display name of the category.
* @constructor Creates a [Filter.TriState] object using [name].
*/
inner class Category(name: String) : Filter.TriState(name) {
/** Returns the [state] as a string, or null if [isIgnored]. */
fun optString() = when (state) {
STATE_INCLUDE -> name.toLowerCase()
STATE_EXCLUDE -> "-" + name.toLowerCase()
else -> null
}
}
/**
* Filter representing the [categories][Category] of a manga.
*
* @constructor Creates a [Filter.Group] object with categories.
*/
inner class CategoryList : Filter.Group<Category>(
"Categories", categories.map { Category(it) }
)
/**
* Filter representing the name of an author or artist.
*
* @constructor Creates a [Filter.Text] object.
*/
inner class Person : Filter.Text("Author/Artist")
} }

View File

@ -1,27 +1,10 @@
package eu.kanade.tachiyomi.extension.en.arcrelight package eu.kanade.tachiyomi.extension.all.mangadventure
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.Locale
/**
* The HTTP date format specified in
* [RFC 1123](https://tools.ietf.org/html/rfc1123#page-55).
*/
private const val HTTP_DATE = "EEE, dd MMM yyyy HH:mm:ss zzz"
/**
* Converts a date in the [HTTP_DATE] format to a Unix timestamp.
*
* @param date The date to convert.
* @return The timestamp of the date.
*/
fun httpDateToTimestamp(date: String) =
SimpleDateFormat(HTTP_DATE, Locale.US).parse(date).time
/** /**
* Joins each value of a given [field] of the array using [sep]. * Joins each value of a given [field] of the array using [sep].
@ -52,7 +35,7 @@ fun JSONArray.joinField(field: Any, sep: String = ", "): String? {
* *
* @param obj The object containing the manga info. * @param obj The object containing the manga info.
*/ */
fun SManga.fromJSON(obj: JSONObject) { fun SManga.fromJSON(obj: JSONObject) = apply {
url = obj.getString("url") url = obj.getString("url")
title = obj.getString("title") title = obj.getString("title")
description = obj.getString("description") description = obj.getString("description")
@ -71,10 +54,10 @@ fun SManga.fromJSON(obj: JSONObject) {
* *
* @param obj The object containing the chapter info. * @param obj The object containing the chapter info.
*/ */
fun SChapter.fromJSON(obj: JSONObject) { fun SChapter.fromJSON(obj: JSONObject) = apply {
url = obj.getString("url") url = obj.getString("url")
chapter_number = obj.optString("chapter", "0").toFloat() chapter_number = obj.optString("chapter", "0").toFloat()
date_upload = httpDateToTimestamp(obj.getString("date")) date_upload = MangAdventure.httpDateToTimestamp(obj.getString("date"))
scanlator = obj.getJSONArray("groups")?.joinField("name", " & ") scanlator = obj.getJSONArray("groups")?.joinField("name", " & ")
name = buildString { name = buildString {
obj.optInt("volume").let { if (it != 0) append("Vol.$it ") } obj.optInt("volume").let { if (it != 0) append("Vol.$it ") }

View File

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.extension.all.mangadventure
import eu.kanade.tachiyomi.source.SourceFactory
/** [MangAdventure] source factory. */
class MangAdventureFactory : SourceFactory {
override fun createSources() = listOf(
ArcRelight()
)
}
/** Arc-Relight source. */
class ArcRelight : MangAdventure(
"Arc-Relight", "https://arc-relight.site", arrayOf(
"4-Koma",
"Chaos;Head",
"Collection",
"Comedy",
"Drama",
"Jubilee",
"Mystery",
"Psychological",
"Robotics;Notes",
"Romance",
"Sci-Fi",
"Seinen",
"Shounen",
"Steins;Gate",
"Supernatural",
"Tragedy"
)
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,66 +0,0 @@
package eu.kanade.tachiyomi.extension.en.arcrelight
import eu.kanade.tachiyomi.source.model.Filter
/** Array containing the possible statuses of a manga */
private val STATUSES = arrayOf("Any", "Completed", "Ongoing")
/** List containing the possible categories of a manga */
private val CATEGORIES = listOf(
Category("4-Koma"),
Category("Chaos;Head"),
Category("Collection"),
Category("Comedy"),
Category("Drama"),
Category("Jubilee"),
Category("Mystery"),
Category("Psychological"),
Category("Robotics;Notes"),
Category("Romance"),
Category("Sci-Fi"),
Category("Seinen"),
Category("Shounen"),
Category("Steins;Gate"),
Category("Supernatural"),
Category("Tragedy")
)
/**
* Filter representing the status of a manga.
*
* @constructor Creates a [Filter.Select] object with [STATUSES].
*/
class Status : Filter.Select<String>("Status", STATUSES) {
/** Returns the [state] as a string. */
fun string() = values[state].toLowerCase()
}
/**
* Filter representing a manga category.
*
* @property name The display name of the category.
* @constructor Creates a [Filter.TriState] object using [name].
*/
class Category(name: String) : Filter.TriState(name) {
/** Returns the [state] as a string, or null if [isIgnored]. */
fun optString() = when (state) {
STATE_INCLUDE -> name.toLowerCase()
STATE_EXCLUDE -> "-" + name.toLowerCase()
else -> null
}
}
/**
* Filter representing the [categories][Category] of a manga.
*
* @constructor Creates a [Filter.Group] object with [CATEGORIES].
*/
class CategoryList : Filter.Group<Category>("Categories", CATEGORIES)
/**
* Filter representing the name of an author or artist.
*
* @constructor Creates a [Filter.Text] object.
*/
class Person : Filter.Text("Author/Artist")