Update NANI? Scans extension to use their API (#4060)

* Update NANI? Scans extension to use their API

* Fix "Open in Browser" for NANI? Scans extension

* Add search intent for NANI? Scans extension

* Save chapter url as combination of titleId and chapterId

* Share single instance of SimpleDateFormat

* Set thumbnail_url on popularMangaParse() and latestUpdatesParse() using new method and share single instance of SimpleDateFormat

* Bump versionId

Co-authored-by: snakedoc83 <mikepeterson83@gmail.com>
This commit is contained in:
Micael Valentim 2020-08-10 22:42:01 -03:00 committed by GitHub
parent 90308cf7b2
commit 0c8a6ee453
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 196 additions and 91 deletions

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".NaniScansUrlActivity"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="naniscans.com"
android:pathPattern="/titles/..*" />
<data
android:scheme="https"
android:host="www.naniscans.com"
android:pathPattern="/titles/..*" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -5,7 +5,7 @@ ext {
extName = 'NANI? Scans'
pkgNameSuffix = 'en.naniscans'
extClass = '.NaniScans'
extVersionCode = 3
extVersionCode = 4
libVersion = '1.2'
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -7,126 +7,173 @@ 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.source.online.HttpSource
import java.lang.UnsupportedOperationException
import java.text.SimpleDateFormat
import java.util.Locale
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.json.JSONArray
import org.json.JSONObject
import rx.Observable
class NaniScans : ParsedHttpSource() {
override val name = "NANI? Scans"
override val baseUrl = "https://naniscans.xyz"
class NaniScans : HttpSource() {
override val baseUrl = "https://naniscans.com"
override val lang = "en"
override val name = "NANI? Scans"
override val supportsLatest = true
override val versionId = 2
override val client: OkHttpClient = network.cloudflareClient
private val dateParser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
// Popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/titles", headers)
}
override fun popularMangaSelector() = "div.card"
override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply {
element.select("h4 a").let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.select("img").attr("abs:src")
}
}
override fun popularMangaNextPageSelector(): String? = null
// Latest
override fun latestUpdatesRequest(page: Int): Request {
return GET(baseUrl, headers)
}
override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page)
override fun latestUpdatesParse(response: Response): MangasPage {
return MangasPage(super.latestUpdatesParse(response).mangas.distinctBy { it.title }, false)
val titlesJson = JSONArray(response.body()!!.string())
val mangaMap = mutableMapOf<Long, SManga>()
for (i in 0 until titlesJson.length()) {
val manga = titlesJson.getJSONObject(i)
if (manga.getString("type") != "Comic")
continue
var date = manga.getString("updatedAt")
if (date == "null")
date = "2018-04-10T17:38:56"
mangaMap[dateParser.parse(date)!!.time] = getBareSManga(manga)
}
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector(): String? = null
// Search
// website doesn't have a search function
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(popularMangaRequest(1))
.asObservableSuccess()
.map { response ->
searchMangaParse(response, query)
}
return MangasPage(mangaMap.toSortedMap().values.toList().asReversed(), false)
}
private fun searchMangaParse(response: Response, query: String): MangasPage {
val mangas = popularMangaParse(response).mangas.filter { it.title.contains(query, ignoreCase = true) }
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/titles")
return MangasPage(mangas, false)
override fun popularMangaParse(response: Response): MangasPage {
val titlesJson = JSONArray(response.body()!!.string())
val mangaList = mutableListOf<SManga>()
for (i in 0 until titlesJson.length()) {
val manga = titlesJson.getJSONObject(i)
if (manga.getString("type") != "Comic")
continue
mangaList.add(getBareSManga(manga))
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used")
return MangasPage(mangaList, false)
}
override fun searchMangaSelector() = throw UnsupportedOperationException("Not used")
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/api/titles/search?term=$query")
override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used")
// Workaround to allow "Open in browser" to use the real URL
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = client.newCall(chapterListRequest(manga)).asObservableSuccess().map { mangaDetailsParse(it).apply { initialized = true } }
// Details
// Return the real URL for "Open in browser"
override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/titles/${manga.url}")
override fun mangaDetailsParse(response: Response): SManga {
val titleJson = JSONObject(response.body()!!.string())
if (titleJson.getString("type") != "Comic")
throw UnsupportedOperationException("Tachiyomi only supports Comics.")
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("div.content").let { info ->
author = info.select("div.center p:contains(Author:)").text().substringAfter("Author: ")
artist = info.select("div.center p:contains(Artist:)").text().substringAfter("Artist: ")
genre = info.select("div.labels > div").joinToString { it.text() }
description = info.select("div.description p").text()
}
thumbnail_url = document.select("div.image img").attr("abs:src")
title = titleJson.getString("name")
artist = titleJson.getString("artist")
author = titleJson.getString("author")
description = titleJson.getString("synopsis")
status = getStatus(titleJson.getString("status"))
thumbnail_url = "$baseUrl${titleJson.getString("coverUrl")}"
genre = titleJson.getJSONArray("tags").join(", ").replace("\"", "")
url = titleJson.getString("id")
}
}
// Chapters
override fun chapterListRequest(manga: SManga) = GET("$baseUrl/api/titles/${manga.url}")
override fun chapterListSelector() = "div.list div.item"
override fun chapterListParse(response: Response): List<SChapter> {
val titleJson = JSONObject(response.body()!!.string())
override fun chapterFromElement(element: Element): SChapter {
return SChapter.create().apply {
element.select("p.header a:last-of-type").let {
name = it.text()
setUrlWithoutDomain(it.attr("href"))
}
date_upload = element.select("div.description p").firstOrNull()?.ownText()
?.let { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(it).time } ?: 0
}
if (titleJson.getString("type") != "Comic")
throw UnsupportedOperationException("Tachiyomi only supports Comics.")
val chaptersJson = titleJson.getJSONArray("chapters")
val chaptersList = mutableListOf<SChapter>()
for (i in 0 until chaptersJson.length()) {
val chapter = chaptersJson.getJSONObject(i)
chaptersList.add(SChapter.create().apply {
chapter_number = chapter.get("number").toString().toFloat()
name = getChapterTitle(chapter)
date_upload = dateParser.parse(chapter.getString("releaseDate"))!!.time
url = "${titleJson.getString("id")}_${chapter.getString("id")}"
})
}
// Pages
override fun pageListParse(document: Document): List<Page> {
return document.select("script:containsData(let pages)").first().data().let { script ->
script.substringAfter("let pages = [").substringBefore("]").replace("\"", "")
.split(",").mapIndexed { i, string -> Page(i, "", baseUrl + string) }
}
return chaptersList
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/api/titles/${chapter.url.substring(0, 36)}/chapters/${chapter.url.substring(37, 73)}")
override fun pageListParse(response: Response): List<Page> {
val jsonObject = JSONObject(response.body()!!.string())
val pagesJson = jsonObject.getJSONArray("pages")
val pagesList = mutableListOf<Page>()
for (i in 0 until pagesJson.length()) {
val item = pagesJson.getJSONObject(i)
pagesList.add(Page(item.getInt("number"), "", "$baseUrl${item.getString("pageUrl")}"))
}
return pagesList
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not Used.")
private fun getStatus(status: String): Int = when (status) {
"Ongoing" -> SManga.ONGOING
"Completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
private fun getChapterTitle(chapter: JSONObject): String {
val chapterName = mutableListOf<String>()
if (chapter.getString("volume") != "null") {
chapterName.add("Vol." + chapter.getString("volume"))
}
if (chapter.getString("number") != "null") {
chapterName.add("Ch." + chapter.getString("number"))
}
if (chapter.getString("name") != "null") {
if (chapterName.isNotEmpty()) {
chapterName.add("-")
}
chapterName.add(chapter.getString("name"))
}
if (chapterName.isEmpty()) {
chapterName.add("Oneshot")
}
return chapterName.joinToString(" ")
}
private fun getBareSManga(manga: JSONObject): SManga = SManga.create().apply {
title = manga.getString("name")
thumbnail_url = "$baseUrl${manga.getString("coverUrl")}"
url = manga.getString("id")
}
}

View File

@ -0,0 +1,35 @@
package eu.kanade.tachiyomi.extension.en.naniscans
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class NaniScansUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val titleid = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", titleid)
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("NaniScansUrlActivity", e.toString())
}
} else {
Log.e("NaniScansUrlActivity", "could not parse uri from intent $intent")
}
finish()
exitProcess(0)
}
}