Webtoons: Fan translations (#1547)

Webtoons: Fan translations
This commit is contained in:
Mook 2019-09-25 20:00:12 -07:00 committed by arkon
parent 64904ba658
commit 948b756e7f
5 changed files with 157 additions and 30 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Webtoons'
pkgNameSuffix = 'all.webtoons'
extClass = '.WebtoonsFactory'
extVersionCode = 8
extVersionCode = 9
libVersion = '1.2'
}

View File

@ -4,9 +4,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl
import okhttp3.Request
import okhttp3.Response
import okhttp3.*
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.*
@ -71,7 +69,7 @@ abstract class Webtoons(override val lang: String, open val langCode: String = l
return manga
}
override fun popularMangaNextPageSelector() = null
override fun popularMangaNextPageSelector() : String? = null
override fun latestUpdatesNextPageSelector() = null

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.extension.all.webtoons
import eu.kanade.tachiyomi.extension.en.webtoons.WebtoonsEnglish
import eu.kanade.tachiyomi.extension.fr.webtoons.WebtoonsFrench
import eu.kanade.tachiyomi.extension.id.webtoons.WebtoonsIndonesian
import eu.kanade.tachiyomi.extension.th.webtoons.WebtoonsThai
import eu.kanade.tachiyomi.extension.zh.webtoons.WebtoonsChineseTraditional
@ -14,10 +13,42 @@ class WebtoonsFactory : SourceFactory {
fun getAllWebtoons(): List<Source> {
return listOf(
WebtoonsEnglish(),
WebtoonsChineseTraditional(),
WebtoonsFrench(),
WebtoonsIndonesian(),
WebtoonsThai()
WebtoonsEnglish(),
WebtoonsChineseTraditional(),
WebtoonsIndonesian(),
WebtoonsThai(),
// fan translations
WebtoonsTranslate("en", "ENG"),
WebtoonsTranslate("zh", "CMN", " (Simplified)"),
WebtoonsTranslate("zh", "CHT", " (Traditional)"),
WebtoonsTranslate("th", "THA"),
WebtoonsTranslate("in", "IND"),
WebtoonsTranslate("fr", "FRA"),
WebtoonsTranslate("vi", "VIE"),
WebtoonsTranslate("ru", "RUS"),
WebtoonsTranslate("ar", "ARA"),
WebtoonsTranslate("fil", "FIL"),
WebtoonsTranslate("de", "DEU"),
WebtoonsTranslate("hi", "HIN"),
WebtoonsTranslate("it", "ITA"),
WebtoonsTranslate("ja", "JPN"),
WebtoonsTranslate("pt", "POR", " (Brazilian)"),
WebtoonsTranslate("tr", "TUR"),
WebtoonsTranslate("ms", "MAY"),
WebtoonsTranslate("pl", "POL"),
WebtoonsTranslate("pt", "POT", " (European)"),
WebtoonsTranslate("bg", "BUL"),
WebtoonsTranslate("da", "DAN"),
WebtoonsTranslate("nl", "NLD"),
WebtoonsTranslate("ro", "RON"),
WebtoonsTranslate("mn", "MON"),
WebtoonsTranslate("el", "GRE"),
WebtoonsTranslate("lt", "LIT"),
WebtoonsTranslate("cs", "CES"),
WebtoonsTranslate("sv", "SWE"),
WebtoonsTranslate("bn", "BEN"),
WebtoonsTranslate("fa", "PER"),
WebtoonsTranslate("uk", "UKR")
)
}

View File

@ -1,8 +1,11 @@
package eu.kanade.tachiyomi.extension.all.webtoons
import android.util.Log.e
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.*
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 okhttp3.HttpUrl
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
@ -10,14 +13,100 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.*
open class WebtoonsTranslate(override val lang: String, private val translationLangCode: String) : Webtoons(lang) {
open class WebtoonsTranslate(override val lang: String, private val translateLangCode: String, private val languageNameExtra: String = "") : Webtoons(lang) {
private val apiBaseUrl = "https://global.apis.naver.com"
private val chapterListUrlPattern = "/lineWebtoon/ctrans/translatedEpisodes_jsonp.json?titleNo=%d&languageCode=%s&offset=0&limit=10000"
private val apiBaseUrl = HttpUrl.parse("https://global.apis.naver.com")!!
private val mobileBaseUrl = HttpUrl.parse("https://m.webtoons.com")!!
private val thumbnailBaseUrl = "https://mwebtoon-phinf.pstatic.net"
private val pageListUrlPattern = "/lineWebtoon/ctrans/translatedEpisodeDetail_jsonp.json?titleNo=%s&episodeNo=%d&languageCode=%s&teamVersion=%d"
private val pageSize = 24
override val name = "Webtoons.com Translations${languageNameExtra}"
override fun headersBuilder() = 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
.resolve("/lineWebtoon/ctrans/translatedWebtoons_jsonp.json")!!
.newBuilder()
.addQueryParameter("orderType", "UPDATE")
.addQueryParameter("offset", "${page * pageSize}")
.addQueryParameter("size", "${pageSize}")
.addQueryParameter("languageCode", translateLangCode)
.build()
return GET(url.toString(), headers)
}
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
}
}
return MangasPage(mangas, totalCount < pageSize * offset)
}
override fun mangaDetailsRequest(manga: SManga): Request {
return GET(manga.url, headers)
}
override fun mangaDetailsParse(document: Document): SManga {
val getMetaProp = fun(property: String): String =
document.head().select("meta[property=\"${property}\"]").attr("content")
var parsedAuthor = getMetaProp("com-linewebtoon:webtoon:author")
var parsedArtist = parsedAuthor
val authorSplit = parsedAuthor.split(" / ", limit = 2)
if (authorSplit.count() > 1) {
parsedAuthor = authorSplit[0]
parsedArtist = authorSplit[1]
}
return SManga.create().apply {
title = getMetaProp("og:title")
artist = parsedArtist
author = parsedAuthor
description = getMetaProp("og:description")
status = SManga.UNKNOWN
thumbnail_url = getMetaProp("og:image")
}
}
override fun chapterListSelector(): String = throw Exception("Not used")
override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used")
@ -25,16 +114,27 @@ open class WebtoonsTranslate(override val lang: String, private val translationL
override fun pageListParse(document: Document): List<Page> = throw Exception("Not used")
override fun chapterListRequest(manga: SManga): Request {
val original = manga.url;
val titleRegex = Regex("title_?[nN]o=([0-9]*)")
val titleNo = titleRegex.find(original)!!.groupValues[1].toInt()
val chapterUrl = String.format("$apiBaseUrl$chapterListUrlPattern", titleNo, translationLangCode)
return GET(chapterUrl, headers)
val titleNo = HttpUrl.parse(manga.url)!!
.queryParameter("titleNo")
val chapterUrl = apiBaseUrl
.resolve("/lineWebtoon/ctrans/translatedEpisodes_jsonp.json")!!
.newBuilder()
.addQueryParameter("titleNo", titleNo)
.addQueryParameter("languageCode", translateLangCode)
.addQueryParameter("offset", "0")
.addQueryParameter("limit", "10000")
.toString()
return GET(chapterUrl, mobileHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> {
val chapterJson = JSONObject(response.body()!!.string())
val chapterData = response.body()!!.string()
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}")
}
var results = chapterJson.getJSONObject("result").getJSONArray("episodes")
val ret = ArrayList<SChapter>()
for (i in 0 until results.length()) {
@ -52,11 +152,14 @@ open class WebtoonsTranslate(override val lang: String, private val translationL
chapter_number = obj.getInt("episodeSeq").toFloat()
date_upload = obj.getLong("updateYmdt")
scanlator = obj.getString("teamVersion")
if (scanlator == "0") {
scanlator = "(wiki)"
}
url = String.format(pageListUrlPattern, obj.getInt("titleNo"), obj.getInt("episodeNo"), obj.getString("languageCode"), obj.getInt("teamVersion"))
}
override fun pageListRequest(chapter: SChapter): Request {
return GET(apiBaseUrl + chapter.url, mobileHeaders)
return GET(apiBaseUrl.resolve(chapter.url).toString(), headers)
}
override fun pageListParse(response: Response): List<Page> {

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.fr.webtoons
import eu.kanade.tachiyomi.extension.all.webtoons.WebtoonsTranslate
class WebtoonsFrench : WebtoonsTranslate("fr", "FRA")