Webtoons - Translations refactor (#1804)
Webtoons - Translations refactor
This commit is contained in:
		
							parent
							
								
									fe2a2c8be6
								
							
						
					
					
						commit
						1b54d43ce6
					
				@ -5,7 +5,7 @@ ext {
 | 
			
		||||
    appName = 'Tachiyomi: Webtoons'
 | 
			
		||||
    pkgNameSuffix = 'all.webtoons'
 | 
			
		||||
    extClass = '.WebtoonsFactory'
 | 
			
		||||
    extVersionCode = 10
 | 
			
		||||
    extVersionCode = 11
 | 
			
		||||
    libVersion = '1.2'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ class WebtoonsFactory : SourceFactory {
 | 
			
		||||
        // Fan translations
 | 
			
		||||
        WebtoonsTranslate("en", "ENG"),
 | 
			
		||||
        WebtoonsTranslate("zh", "CMN", " (Simplified)"),
 | 
			
		||||
        WebtoonsTranslate("zh", "CHT", " (Traditional)"),
 | 
			
		||||
        WebtoonsTranslate("zh", "CMT", " (Traditional)"),
 | 
			
		||||
        WebtoonsTranslate("th", "THA"),
 | 
			
		||||
        WebtoonsTranslate("in", "IND"),
 | 
			
		||||
        WebtoonsTranslate("fr", "FRA"),
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,9 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.webtoons
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
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.network.asObservableSuccess
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.*
 | 
			
		||||
import okhttp3.Headers
 | 
			
		||||
import okhttp3.HttpUrl
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
@ -12,8 +11,11 @@ import org.json.JSONObject
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import java.util.*
 | 
			
		||||
import rx.Observable
 | 
			
		||||
 | 
			
		||||
open class WebtoonsTranslate(override val lang: String, private val translateLangCode: String, private val languageNameExtra: String = "") : Webtoons(lang) {
 | 
			
		||||
open class WebtoonsTranslate(override val lang: String, private val translateLangCode: String, languageNameExtra: String = "") : Webtoons(lang) {
 | 
			
		||||
    // popularMangaRequest already returns manga sorted by latest update
 | 
			
		||||
    override val supportsLatest = false
 | 
			
		||||
 | 
			
		||||
    private val apiBaseUrl = HttpUrl.parse("https://global.apis.naver.com")!!
 | 
			
		||||
    private val mobileBaseUrl = HttpUrl.parse("https://m.webtoons.com")!!
 | 
			
		||||
@ -23,63 +25,107 @@ open class WebtoonsTranslate(override val lang: String, private val translateLan
 | 
			
		||||
 | 
			
		||||
    private val pageSize = 24
 | 
			
		||||
 | 
			
		||||
    override val name = "Webtoons.com Translations${languageNameExtra}"
 | 
			
		||||
    override val name = "Webtoons.com Translations$languageNameExtra"
 | 
			
		||||
 | 
			
		||||
    override fun headersBuilder() = super.headersBuilder()
 | 
			
		||||
    override fun headersBuilder(): Headers.Builder = super.headersBuilder()
 | 
			
		||||
        .removeAll("Referer")
 | 
			
		||||
        .add("Referer", mobileBaseUrl.toString())
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int): Request {
 | 
			
		||||
        // Webtoons translations doesn't really have a "popular" sort; just "UPDATE", "TITLE_ASC",
 | 
			
		||||
        // and "TITLE_DESC".  Pick UPDATE as the most useful sort.
 | 
			
		||||
        var url = apiBaseUrl
 | 
			
		||||
    private fun mangaRequest(page: Int, requeztSize: Int): Request {
 | 
			
		||||
        val url = apiBaseUrl
 | 
			
		||||
            .resolve("/lineWebtoon/ctrans/translatedWebtoons_jsonp.json")!!
 | 
			
		||||
            .newBuilder()
 | 
			
		||||
            .addQueryParameter("orderType", "UPDATE")
 | 
			
		||||
            .addQueryParameter("offset", "${page * pageSize}")
 | 
			
		||||
            .addQueryParameter("size", "${pageSize}")
 | 
			
		||||
            .addQueryParameter("offset", "${(page - 1) * requeztSize}")
 | 
			
		||||
            .addQueryParameter("size", "$requeztSize")
 | 
			
		||||
            .addQueryParameter("languageCode", translateLangCode)
 | 
			
		||||
            .build()
 | 
			
		||||
        return GET(url.toString(), headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Webtoons translations doesn't really have a "popular" sort; just "UPDATE", "TITLE_ASC",
 | 
			
		||||
    // and "TITLE_DESC".  Pick UPDATE as the most useful sort.
 | 
			
		||||
    override fun popularMangaRequest(page: Int): Request = mangaRequest(page, pageSize)
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val responseText = response.body()!!.string()
 | 
			
		||||
        val requestURL = response.request().url()
 | 
			
		||||
        val offset = requestURL.queryParameter("offset")!!.toInt()
 | 
			
		||||
        val responseJSON = JSONObject(responseText)
 | 
			
		||||
        val responseCode = responseJSON.getString("code")
 | 
			
		||||
        if (responseCode != "000") {
 | 
			
		||||
            throw Exception("Error getting popular manga: error code ${responseCode}")
 | 
			
		||||
        }
 | 
			
		||||
        var results = responseJSON.getJSONObject("result")
 | 
			
		||||
        val totalCount = results.getInt("totalCount")
 | 
			
		||||
        val titleList = results.getJSONArray("titleList")
 | 
			
		||||
        val mangas = (0 until titleList.length()).map { i ->
 | 
			
		||||
            val titleJSON = titleList.get(i) as JSONObject
 | 
			
		||||
            val titleNo = titleJSON.getInt("titleNo")
 | 
			
		||||
            val team = titleJSON.optInt("teamVersion", 0)
 | 
			
		||||
            val relativeThumnailURL = titleJSON.getString("thumbnailIPadUrl")
 | 
			
		||||
                ?: titleJSON.getString("thumbnailMobileUrl")
 | 
			
		||||
            SManga.create()
 | 
			
		||||
                .apply {
 | 
			
		||||
                    title = titleJSON.getString("representTitle")
 | 
			
		||||
                    author = titleJSON.getString("writeAuthorName")
 | 
			
		||||
                    artist = titleJSON.getString("pictureAuthorName") ?: author
 | 
			
		||||
                    thumbnail_url = if (relativeThumnailURL != null) "${thumbnailBaseUrl}${relativeThumnailURL}" else null
 | 
			
		||||
                    status = SManga.UNKNOWN
 | 
			
		||||
                    url = mobileBaseUrl
 | 
			
		||||
                        .resolve("/translate/episodeList")!!
 | 
			
		||||
                        .newBuilder()
 | 
			
		||||
                        .addQueryParameter("titleNo", titleNo.toString())
 | 
			
		||||
                        .addQueryParameter("languageCode", translateLangCode)
 | 
			
		||||
                        .addQueryParameter("teamVersion", team.toString())
 | 
			
		||||
                        .build()
 | 
			
		||||
                        .toString()
 | 
			
		||||
                    initialized = true
 | 
			
		||||
        val offset = response.request().url().queryParameter("offset")!!.toInt()
 | 
			
		||||
        var totalCount: Int
 | 
			
		||||
        val mangas = mutableListOf<SManga>()
 | 
			
		||||
 | 
			
		||||
        JSONObject(response.body()!!.string()).let { json ->
 | 
			
		||||
            json.getString("code").let { code ->
 | 
			
		||||
                if (code != "000") throw Exception("Error getting popular manga: error code $code")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            json.getJSONObject("result").let { results ->
 | 
			
		||||
                totalCount = results.getInt("totalCount")
 | 
			
		||||
 | 
			
		||||
                results.getJSONArray("titleList").let { array ->
 | 
			
		||||
                    for (i in 0 until array.length()) {
 | 
			
		||||
                        mangas.add(mangaFromJson(array[i] as JSONObject))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return MangasPage(mangas, totalCount < pageSize * offset)
 | 
			
		||||
 | 
			
		||||
        return MangasPage(mangas, totalCount > pageSize + offset)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun mangaFromJson(json: JSONObject): SManga {
 | 
			
		||||
        val relativeThumnailURL = json.getString("thumbnailIPadUrl")
 | 
			
		||||
            ?: json.getString("thumbnailMobileUrl")
 | 
			
		||||
 | 
			
		||||
        return SManga.create().apply {
 | 
			
		||||
                title = json.getString("representTitle")
 | 
			
		||||
                author = json.getString("writeAuthorName")
 | 
			
		||||
                artist = json.getString("pictureAuthorName") ?: author
 | 
			
		||||
                thumbnail_url = if (relativeThumnailURL != null) "$thumbnailBaseUrl$relativeThumnailURL" else null
 | 
			
		||||
                status = SManga.UNKNOWN
 | 
			
		||||
                url = mobileBaseUrl
 | 
			
		||||
                    .resolve("/translate/episodeList")!!
 | 
			
		||||
                    .newBuilder()
 | 
			
		||||
                    .addQueryParameter("titleNo", json.getInt("titleNo").toString())
 | 
			
		||||
                    .addQueryParameter("languageCode", translateLangCode)
 | 
			
		||||
                    .addQueryParameter("teamVersion", json.optInt("teamVersion", 0).toString())
 | 
			
		||||
                    .build()
 | 
			
		||||
                    .toString()
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
 | 
			
		||||
        return client.newCall(searchMangaRequest(page, query, filters))
 | 
			
		||||
            .asObservableSuccess()
 | 
			
		||||
            .map { response ->
 | 
			
		||||
                searchMangaParse(response, query)
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Don't see a search function for Fan Translations, so let's do it client side.
 | 
			
		||||
     * There's 75 webtoons as of 2019/11/21, a hardcoded request of 200 should be a sufficient request
 | 
			
		||||
     * to get all titles, in 1 request, for quite a while
 | 
			
		||||
     */
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = mangaRequest(page, 200)
 | 
			
		||||
 | 
			
		||||
    private fun searchMangaParse(response: Response, query: String): MangasPage {
 | 
			
		||||
        val mangas = mutableListOf<SManga>()
 | 
			
		||||
 | 
			
		||||
        JSONObject(response.body()!!.string()).let { json ->
 | 
			
		||||
            json.getString("code").let { code ->
 | 
			
		||||
                if (code != "000") throw Exception("Error getting manga: error code $code")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            json.getJSONObject("result").getJSONArray("titleList").let { array ->
 | 
			
		||||
                for (i in 0 until array.length()) {
 | 
			
		||||
                    (array[i] as JSONObject).let { jsonManga ->
 | 
			
		||||
                        if (jsonManga.getString("representTitle").contains(query, ignoreCase = true))
 | 
			
		||||
                            mangas.add(mangaFromJson(jsonManga))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MangasPage(mangas, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsRequest(manga: SManga): Request {
 | 
			
		||||
@ -88,7 +134,7 @@ open class WebtoonsTranslate(override val lang: String, private val translateLan
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga {
 | 
			
		||||
        val getMetaProp = fun(property: String): String =
 | 
			
		||||
            document.head().select("meta[property=\"${property}\"]").attr("content")
 | 
			
		||||
            document.head().select("meta[property=\"$property\"]").attr("content")
 | 
			
		||||
        var parsedAuthor = getMetaProp("com-linewebtoon:webtoon:author")
 | 
			
		||||
        var parsedArtist = parsedAuthor
 | 
			
		||||
        val authorSplit = parsedAuthor.split(" / ", limit = 2)
 | 
			
		||||
@ -132,10 +178,10 @@ open class WebtoonsTranslate(override val lang: String, private val translateLan
 | 
			
		||||
        val chapterJson = JSONObject(chapterData)
 | 
			
		||||
        val responseCode = chapterJson.getString("code")
 | 
			
		||||
        if (responseCode != "000") {
 | 
			
		||||
            val message = chapterJson.optString("message", "error code ${responseCode}")
 | 
			
		||||
            throw Exception("Error getting chapter list: ${message}")
 | 
			
		||||
            val message = chapterJson.optString("message", "error code $responseCode")
 | 
			
		||||
            throw Exception("Error getting chapter list: $message")
 | 
			
		||||
        }
 | 
			
		||||
        var results = chapterJson.getJSONObject("result").getJSONArray("episodes")
 | 
			
		||||
        val results = chapterJson.getJSONObject("result").getJSONArray("episodes")
 | 
			
		||||
        val ret = ArrayList<SChapter>()
 | 
			
		||||
        for (i in 0 until results.length()) {
 | 
			
		||||
            val result = results.getJSONObject(i)
 | 
			
		||||
@ -164,7 +210,7 @@ open class WebtoonsTranslate(override val lang: String, private val translateLan
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(response: Response): List<Page> {
 | 
			
		||||
        val pageJson = JSONObject(response.body()!!.string())
 | 
			
		||||
        var results = pageJson.getJSONObject("result").getJSONArray("imageInfo")
 | 
			
		||||
        val results = pageJson.getJSONObject("result").getJSONArray("imageInfo")
 | 
			
		||||
        val ret = ArrayList<Page>()
 | 
			
		||||
        for (i in 0 until results.length()) {
 | 
			
		||||
            val result = results.getJSONObject(i)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user