League of Legends: new extension (#9463)
This commit is contained in:
parent
abf7fe4284
commit
64ba1e6ba4
2
src/all/leagueoflegends/AndroidManifest.xml
Normal file
2
src/all/leagueoflegends/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="eu.kanade.tachiyomi.extension"/>
|
12
src/all/leagueoflegends/build.gradle
Normal file
12
src/all/leagueoflegends/build.gradle
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'League of Legends'
|
||||||
|
pkgNameSuffix = 'all.leagueoflegends'
|
||||||
|
extClass = '.LOLFactory'
|
||||||
|
extVersionCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/all/leagueoflegends/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/all/leagueoflegends/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
BIN
src/all/leagueoflegends/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/all/leagueoflegends/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
src/all/leagueoflegends/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/all/leagueoflegends/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
src/all/leagueoflegends/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/all/leagueoflegends/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
src/all/leagueoflegends/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/all/leagueoflegends/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
src/all/leagueoflegends/res/web_hi_res_512.png
Normal file
BIN
src/all/leagueoflegends/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 220 KiB |
@ -0,0 +1,30 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.leagueoflegends
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
class LOLFactory : SourceFactory {
|
||||||
|
override fun createSources() = listOf(
|
||||||
|
LOLUniverse("en_us"),
|
||||||
|
// LOLUniverse("en_gb"),
|
||||||
|
LOLUniverse("de_de"),
|
||||||
|
LOLUniverse("es_es"),
|
||||||
|
LOLUniverse("fr_fr"),
|
||||||
|
LOLUniverse("it_it"),
|
||||||
|
// LOLUniverse("en_pl"),
|
||||||
|
LOLUniverse("pl_pl"),
|
||||||
|
LOLUniverse("el_gr"),
|
||||||
|
LOLUniverse("ro_ro"),
|
||||||
|
LOLUniverse("hu_hu"),
|
||||||
|
LOLUniverse("cs_cz"),
|
||||||
|
LOLUniverse("es_mx", "es-419"),
|
||||||
|
// LOLUniverse("es_ar", "es-419"),
|
||||||
|
LOLUniverse("pt_br", "pt-BR"),
|
||||||
|
LOLUniverse("ja_jp"),
|
||||||
|
LOLUniverse("ru_ru"),
|
||||||
|
LOLUniverse("tr_tr"),
|
||||||
|
// LOLUniverse("en_au"),
|
||||||
|
LOLUniverse("ko_kr"),
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.leagueoflegends
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLHub(
|
||||||
|
private val sections: LOLSections
|
||||||
|
) : Iterable<LOLComic> by sections
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLSections(
|
||||||
|
val series: LOLData,
|
||||||
|
@SerialName("one-shots")
|
||||||
|
private val oneShots: LOLData
|
||||||
|
) : Iterable<LOLComic> {
|
||||||
|
override fun iterator() = (series + oneShots).iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLData(
|
||||||
|
private val data: List<LOLComic>
|
||||||
|
) : Iterable<LOLComic> by data
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLComic(
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String? = null,
|
||||||
|
val index: Float? = null,
|
||||||
|
private val url: String,
|
||||||
|
val description: String,
|
||||||
|
val background: LOLImage,
|
||||||
|
@SerialName("featured-champions")
|
||||||
|
val champions: List<LOLChampion>? = null
|
||||||
|
) {
|
||||||
|
override fun toString() = url.substringAfter("/comic/")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLIssues(
|
||||||
|
private val issues: List<LOLComic>
|
||||||
|
) : Iterable<LOLComic> by issues
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLPages(
|
||||||
|
@SerialName("staging-date")
|
||||||
|
val date: String,
|
||||||
|
@SerialName("desktop-pages")
|
||||||
|
private val pages: List<List<LOLImage>>
|
||||||
|
) : Iterable<LOLImage> {
|
||||||
|
override fun iterator() = pages.flatten().iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLImage(private val uri: String) {
|
||||||
|
override fun toString() = uri
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class LOLChampion(private val name: String) {
|
||||||
|
override fun toString() = name
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.leagueoflegends
|
||||||
|
|
||||||
|
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 kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
|
class LOLUniverse(
|
||||||
|
private val siteLang: String,
|
||||||
|
override val lang: String = siteLang.substring(0, 2)
|
||||||
|
) : HttpSource() {
|
||||||
|
override val baseUrl = "$UNIVERSE_URL/$siteLang/comic/"
|
||||||
|
|
||||||
|
override val name = "League of Legends"
|
||||||
|
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
private val json by injectLazy<Json>()
|
||||||
|
|
||||||
|
private val pageCache = mutableMapOf<String, List<Page>>()
|
||||||
|
|
||||||
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
|
.set("Origin", UNIVERSE_URL).set("Referer", "$UNIVERSE_URL/")
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int) =
|
||||||
|
GET("$MEEPS_URL/$siteLang/comics/index.json", headers)
|
||||||
|
|
||||||
|
// Request the actual manga URL for the webview
|
||||||
|
override fun mangaDetailsRequest(manga: SManga) =
|
||||||
|
GET("$UNIVERSE_URL/$siteLang/comic/${manga.url}")
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga) =
|
||||||
|
GET("$MEEPS_URL/$siteLang/comics/${manga.url}/index.json", headers)
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter) =
|
||||||
|
GET("$COMICS_URL/$siteLang/${chapter.url}/index.json", headers)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response) =
|
||||||
|
response.decode<LOLHub>().map {
|
||||||
|
SManga.create().apply {
|
||||||
|
title = it.title
|
||||||
|
url = it.toString()
|
||||||
|
description = it.description.clean()
|
||||||
|
thumbnail_url = it.background.toString()
|
||||||
|
genre = it.subtitle ?: it.champions?.joinToString()
|
||||||
|
}
|
||||||
|
}.run { MangasPage(this, false) }
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response) =
|
||||||
|
response.decode<LOLIssues>().map {
|
||||||
|
SChapter.create().apply {
|
||||||
|
name = it.title
|
||||||
|
url = it.toString()
|
||||||
|
chapter_number = it.index ?: -1f
|
||||||
|
fetchPageList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
|
client.newCall(popularMangaRequest(page)).asObservableSuccess()
|
||||||
|
.map { popularMangaParse(it).filter(query) }!!
|
||||||
|
|
||||||
|
override fun fetchMangaDetails(manga: SManga) =
|
||||||
|
Observable.just(manga.apply { initialized = true })!!
|
||||||
|
|
||||||
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
if ('/' !in manga.url) return super.fetchChapterList(manga)
|
||||||
|
val chapter = SChapter.create().apply {
|
||||||
|
url = manga.url
|
||||||
|
name = "One Shot"
|
||||||
|
chapter_number = 0f
|
||||||
|
fetchPageList()
|
||||||
|
}
|
||||||
|
return Observable.just(listOf(chapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchPageList(chapter: SChapter) =
|
||||||
|
Observable.just(pageCache[chapter.url].orEmpty())!!
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
private inline fun <reified T> Response.decode() =
|
||||||
|
json.decodeFromString<T>(body!!.string())
|
||||||
|
|
||||||
|
private fun String.clean() =
|
||||||
|
replace("</p> ", "</p>").replace("</p>", "\n").replace("<p>", "")
|
||||||
|
|
||||||
|
private fun SChapter.fetchPageList() {
|
||||||
|
client.newCall(pageListRequest(this)).execute().decode<LOLPages>().let {
|
||||||
|
// The chapter date is only available in the page list
|
||||||
|
date_upload = isoDate.parse(it.date)?.time ?: 0L
|
||||||
|
pageCache[url] = it.mapIndexed { idx, img ->
|
||||||
|
Page(idx, "", img.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MangasPage.filter(query: String) = copy(
|
||||||
|
mangas.filter {
|
||||||
|
it.title.contains(query, true) ||
|
||||||
|
it.genre?.contains(query, true) ?: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val UNIVERSE_URL = "https://universe.leagueoflegends.com"
|
||||||
|
|
||||||
|
private const val MEEPS_URL = "https://universe-meeps.leagueoflegends.com/v1"
|
||||||
|
|
||||||
|
private const val COMICS_URL = "https://universe-comics.leagueoflegends.com/comics"
|
||||||
|
|
||||||
|
private val isoDate by lazy {
|
||||||
|
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user