diff --git a/src/fr/scanmanga/.gitignore b/src/fr/scanmanga/.gitignore new file mode 100644 index 000000000..2c34b6bff --- /dev/null +++ b/src/fr/scanmanga/.gitignore @@ -0,0 +1 @@ +local.properties diff --git a/src/fr/scanmanga/AndroidManifest.xml b/src/fr/scanmanga/AndroidManifest.xml new file mode 100644 index 000000000..30deb7f79 --- /dev/null +++ b/src/fr/scanmanga/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/src/fr/scanmanga/build.gradle b/src/fr/scanmanga/build.gradle new file mode 100644 index 000000000..c180e08fb --- /dev/null +++ b/src/fr/scanmanga/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Scan-Manga' + pkgNameSuffix = 'fr.scanmanga' + extClass = '.ScanManga' + extVersionCode = 1 + libVersion = '1.2' + containsNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/fr/scanmanga/res/mipmap-hdpi/ic_launcher.png b/src/fr/scanmanga/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..5c6a951ca Binary files /dev/null and b/src/fr/scanmanga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/fr/scanmanga/res/mipmap-mdpi/ic_launcher.png b/src/fr/scanmanga/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..4daa8fb1d Binary files /dev/null and b/src/fr/scanmanga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/fr/scanmanga/res/mipmap-xhdpi/ic_launcher.png b/src/fr/scanmanga/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bc9eea6e9 Binary files /dev/null and b/src/fr/scanmanga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt b/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt new file mode 100644 index 000000000..a89a1de3f --- /dev/null +++ b/src/fr/scanmanga/src/eu/kanade/tachiyomi/extension/fr/scanmanga/ScanManga.kt @@ -0,0 +1,191 @@ +package eu.kanade.tachiyomi.extension.fr.scanmanga + +import com.github.salomonbrys.kotson.fromJson +import com.github.salomonbrys.kotson.get +import com.google.gson.Gson +import com.google.gson.JsonObject +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.ParsedHttpSource +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.jsoup.parser.Parser +import rx.Observable +import kotlin.random.Random + +class ScanManga : ParsedHttpSource() { + + override val name = "Scan-Manga" + + override val baseUrl = "https://www.scan-manga.com" + + override val lang = "fr" + + override val supportsLatest = true + + override val client: OkHttpClient = network.client.newBuilder() + .addNetworkInterceptor { chain -> + val originalCookies = chain.request().header("Cookie") ?: "" + val newReq = chain + .request() + .newBuilder() + .header("Cookie", "$originalCookies; _ga=GA1.2.${shuffle("123456789")}.${System.currentTimeMillis() / 1000}") + .build() + chain.proceed(newReq) + }.build()!! + + private val gson = Gson() + + override fun headersBuilder(): Headers.Builder = super.headersBuilder() + .add("Accept-Language", "fr-FR") + + // Popular + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/Tout-le-TOP.html", headers) + } + + override fun popularMangaSelector() = "div.image_manga a[href]" + + override fun popularMangaFromElement(element: Element): SManga { + return SManga.create().apply { + title = element.select("img").attr("title") + setUrlWithoutDomain(element.attr("href")) + thumbnail_url = element.select("img").attr("data-original") + } + } + + override fun popularMangaNextPageSelector(): String? = null + + // Latest + override fun latestUpdatesRequest(page: Int): Request { + return GET(baseUrl, headers) + } + + override fun latestUpdatesSelector() = "#content_news .listing" + + override fun latestUpdatesFromElement(element: Element): SManga { + return SManga.create().apply { + title = element.select("a.nom_manga").text() + setUrlWithoutDomain(element.select("a.nom_manga").attr("href")) + /*thumbnail_url = element.select(".logo_manga img").let { + if (it.hasAttr("data-original")) + it.attr("data-original") else it.attr("src") + }*/ // Better not use it, width is too large, which results in terrible image + } + } + + override fun latestUpdatesNextPageSelector(): String? = null + + // Search + override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used") + + override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") + + override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) + + fun shuffle(s: String?): String? { + val result = StringBuffer(s!!) + var n = result.length + while (n > 1) { + val randomPoint: Int = Random.nextInt(n) + val randomChar = result[randomPoint] + result.setCharAt(n - 1, randomChar) + n-- + } + return result.toString() + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val searchHeaders = headersBuilder().apply { + add("Referer", "$baseUrl/scanlation/liste_series.html?q=$query") + add("x-requested-with", "XMLHttpRequest") + }.build() + return GET("$baseUrl/scanlation/scan.data.json", searchHeaders) + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response, query) + } + } + + private fun searchMangaParse(response: Response, query: String): MangasPage { + return MangasPage(parseMangaFromJson(response).mangas.filter { it.title.contains(query, ignoreCase = true) }, false) + } + + private fun parseMangaFromJson(response: Response): MangasPage { + val jsonString = response.body()!!.string() + if (jsonString.equals("")) { + return MangasPage(listOf(), false) + } + + val jsonObject = gson.fromJson(jsonString) + + val mangas = jsonObject.keySet() + .map { key -> + // "95","%24100-is-Too-Cheap","0","3","One Shot","","2 avril 2010","","335","178","4010","" + SManga.create().apply { + url = "/" + Integer.parseInt(jsonObject.get(key)?.get(0).toString().replace("\"", "")) + "/" + jsonObject.get(key)?.get(1).toString().replace("\"", "") + ".html" + title = Parser.unescapeEntities(key, false) + } + } + + return MangasPage(mangas, false) + } + + override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") + + // Details + override fun mangaDetailsParse(document: Document): SManga { + return SManga.create().apply { + title = document.select("h2[itemprop=\"name\"]").text() + author = document.select("li[itemprop=\"author\"]").text() + description = document.select("p[itemprop=\"description\"]").text() + thumbnail_url = document.select(".contenu_fiche_technique .image_manga img").attr("src") + } + } + + // Chapters + override fun chapterListSelector() = "div.texte_volume_manga ul li.chapitre div.chapitre_nom a" + + override fun chapterFromElement(element: Element): SChapter { + return SChapter.create().apply { + name = element.text() + setUrlWithoutDomain(element.attr("href")) + } + } + + // Pages + override fun pageListParse(document: Document): List { + val docString = document.toString() + + var lelUrl = Regex("""['"](http.*?scanmanga.eu.*)['"]""").find(docString)?.groupValues?.get(1) + if (lelUrl == null) { + lelUrl = Regex("""['"](http.*?le[il].scan-manga.com.*)['"]""").find(docString)?.groupValues?.get(1) + } + + return Regex("""["'](.*?zoneID.*?pageID.*?siteID.*?)["']""").findAll(docString).toList().mapIndexed { i, pageParam -> + Page(i, document.location(), lelUrl + pageParam.groupValues[1]) + } + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used") + + override fun imageRequest(page: Page): Request { + val imgHeaders = headersBuilder().apply { + add("Referer", page.url) + }.build() + return GET(page.imageUrl!!, imgHeaders) + } +}