diff --git a/src/en/naniscans/AndroidManifest.xml b/src/en/naniscans/AndroidManifest.xml
new file mode 100644
index 000000000..072a686df
--- /dev/null
+++ b/src/en/naniscans/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/en/naniscans/build.gradle b/src/en/naniscans/build.gradle
index dc1eee6b4..f8ae8c85e 100644
--- a/src/en/naniscans/build.gradle
+++ b/src/en/naniscans/build.gradle
@@ -5,7 +5,7 @@ ext {
extName = 'NANI? Scans'
pkgNameSuffix = 'en.naniscans'
extClass = '.NaniScans'
- extVersionCode = 3
+ extVersionCode = 4
libVersion = '1.2'
}
diff --git a/src/en/naniscans/res/mipmap-hdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-hdpi/ic_launcher.png
index 62567c4d7..9821a2161 100644
Binary files a/src/en/naniscans/res/mipmap-hdpi/ic_launcher.png and b/src/en/naniscans/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/naniscans/res/mipmap-mdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-mdpi/ic_launcher.png
index 20c08aa07..584f7fd82 100644
Binary files a/src/en/naniscans/res/mipmap-mdpi/ic_launcher.png and b/src/en/naniscans/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/naniscans/res/mipmap-xhdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-xhdpi/ic_launcher.png
index e16a6c218..7b8855485 100644
Binary files a/src/en/naniscans/res/mipmap-xhdpi/ic_launcher.png and b/src/en/naniscans/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/naniscans/res/mipmap-xxhdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-xxhdpi/ic_launcher.png
index 520603a35..21fe8337e 100644
Binary files a/src/en/naniscans/res/mipmap-xxhdpi/ic_launcher.png and b/src/en/naniscans/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/naniscans/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/naniscans/res/mipmap-xxxhdpi/ic_launcher.png
index 20048f028..f18241d6a 100644
Binary files a/src/en/naniscans/res/mipmap-xxxhdpi/ic_launcher.png and b/src/en/naniscans/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/naniscans/res/web_hi_res_512.png b/src/en/naniscans/res/web_hi_res_512.png
index a1d2a0fff..4e3407e0b 100644
Binary files a/src/en/naniscans/res/web_hi_res_512.png and b/src/en/naniscans/res/web_hi_res_512.png differ
diff --git a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt
index 1914a0314..2179e40bc 100644
--- a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt
+++ b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScans.kt
@@ -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()
+
+ 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)
+ }
+
+ return MangasPage(mangaMap.toSortedMap().values.toList().asReversed(), false)
}
- override fun latestUpdatesSelector() = popularMangaSelector()
+ override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/api/titles")
- override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
+ override fun popularMangaParse(response: Response): MangasPage {
+ val titlesJson = JSONArray(response.body()!!.string())
+ val mangaList = mutableListOf()
- override fun latestUpdatesNextPageSelector(): String? = null
+ for (i in 0 until titlesJson.length()) {
+ val manga = titlesJson.getJSONObject(i)
- // Search
+ if (manga.getString("type") != "Comic")
+ continue
- // website doesn't have a search function
- override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
- return client.newCall(popularMangaRequest(1))
- .asObservableSuccess()
- .map { response ->
- searchMangaParse(response, query)
- }
+ mangaList.add(getBareSManga(manga))
+ }
+
+ return MangasPage(mangaList, false)
}
- private fun searchMangaParse(response: Response, query: String): MangasPage {
- val mangas = popularMangaParse(response).mangas.filter { it.title.contains(query, ignoreCase = true) }
+ override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
- return MangasPage(mangas, false)
- }
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/api/titles/search?term=$query")
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used")
+ // Workaround to allow "Open in browser" to use the real URL
+ override fun fetchMangaDetails(manga: SManga): Observable = client.newCall(chapterListRequest(manga)).asObservableSuccess().map { mangaDetailsParse(it).apply { initialized = true } }
- override fun searchMangaSelector() = throw UnsupportedOperationException("Not used")
+ // Return the real URL for "Open in browser"
+ override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/titles/${manga.url}")
- override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
+ override fun mangaDetailsParse(response: Response): SManga {
+ val titleJson = JSONObject(response.body()!!.string())
- override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used")
+ if (titleJson.getString("type") != "Comic")
+ throw UnsupportedOperationException("Tachiyomi only supports Comics.")
- // Details
-
- 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()
+ 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")
+ }
+ }
+
+ override fun chapterListRequest(manga: SManga) = GET("$baseUrl/api/titles/${manga.url}")
+
+ override fun chapterListParse(response: Response): List {
+ val titleJson = JSONObject(response.body()!!.string())
+
+ if (titleJson.getString("type") != "Comic")
+ throw UnsupportedOperationException("Tachiyomi only supports Comics.")
+
+ val chaptersJson = titleJson.getJSONArray("chapters")
+ val chaptersList = mutableListOf()
+
+ 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")}"
+ })
+ }
+
+ return chaptersList
+ }
+
+ 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 {
+ val jsonObject = JSONObject(response.body()!!.string())
+
+ val pagesJson = jsonObject.getJSONArray("pages")
+ val pagesList = mutableListOf()
+
+ 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()
+
+ 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("-")
}
- thumbnail_url = document.select("div.image img").attr("abs:src")
+
+ chapterName.add(chapter.getString("name"))
}
+
+ if (chapterName.isEmpty()) {
+ chapterName.add("Oneshot")
+ }
+
+ return chapterName.joinToString(" ")
}
- // Chapters
-
- override fun chapterListSelector() = "div.list div.item"
-
- 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
- }
+ private fun getBareSManga(manga: JSONObject): SManga = SManga.create().apply {
+ title = manga.getString("name")
+ thumbnail_url = "$baseUrl${manga.getString("coverUrl")}"
+ url = manga.getString("id")
}
-
- // Pages
-
- override fun pageListParse(document: Document): List {
- 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) }
- }
- }
-
- override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
}
diff --git a/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScansUrlActivity.kt b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScansUrlActivity.kt
new file mode 100644
index 000000000..f5b400bf0
--- /dev/null
+++ b/src/en/naniscans/src/eu/kanade/tachiyomi/extension/en/naniscans/NaniScansUrlActivity.kt
@@ -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)
+ }
+}