Convert Arc-Relight extension to MangAdventure bundle (#905)
Convert Arc-Relight extension to MangAdventure bundle
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 35 KiB |
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ") }
|
|
@ -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"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 27 KiB |
|
@ -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")
|
|
||||||
|
|