From 6b8b650004139875c145481f9b8163683ff19570 Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:53:08 +0500 Subject: [PATCH] add Wolfdotcom (#6534) * wolfdotcom * fix selectors * domain preference and auto update * update domain number * auto update domain in ci * update regex * use * current domain number * don't set chapter number as it is more of an index than actual chapter num --- src/ko/wolfdotcom/build.gradle | 53 +++ .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2690 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1816 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3817 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6918 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 8853 bytes .../extension/ko/wolfdotcom/DomainNumber.kt | 3 + .../extension/ko/wolfdotcom/Filters.kt | 59 +++ .../tachiyomi/extension/ko/wolfdotcom/Wolf.kt | 417 ++++++++++++++++++ .../extension/ko/wolfdotcom/WolfFactory.kt | 21 + 10 files changed, 553 insertions(+) create mode 100644 src/ko/wolfdotcom/build.gradle create mode 100644 src/ko/wolfdotcom/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/ko/wolfdotcom/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/ko/wolfdotcom/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/ko/wolfdotcom/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/ko/wolfdotcom/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/DomainNumber.kt create mode 100644 src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/Filters.kt create mode 100644 src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/Wolf.kt create mode 100644 src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/WolfFactory.kt diff --git a/src/ko/wolfdotcom/build.gradle b/src/ko/wolfdotcom/build.gradle new file mode 100644 index 000000000..10484ae79 --- /dev/null +++ b/src/ko/wolfdotcom/build.gradle @@ -0,0 +1,53 @@ +ext { + extName = 'Wolf.com' + extClass = '.WolfFactory' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" + +def domainNumberFileName = "src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/DomainNumber.kt" +def domainNumberFile = new File(domainNumberFileName) +def backupFile = new File(domainNumberFileName + "_bak") + +tasks.register('updateDomainNumber') { + doLast { + def domainNumber = -1 + def response = new URL("https://nicelink52.com/").text + def matcher = response =~ ~/https?:\/\/wfwf(\d+)\.com/ + if (matcher) { + domainNumber = matcher[0][1] + println("[Wolf.com] new domain number: $domainNumber") + } else { + println("[Wolf.com] domain number not found") + } + + if (domainNumber != -1) { + domainNumberFile.renameTo(backupFile) + domainNumberFile.withPrintWriter { + it.println("// THIS FILE IS AUTO-GENERATED, DO NOT COMMIT") + it.println("package eu.kanade.tachiyomi.extension.ko.wolfdotcom") + it.println("const val DEFAULT_DOMAIN_NUMBER = \"$domainNumber\"") + } + } + } +} + +preBuild.dependsOn updateDomainNumber + +tasks.register('restoreBackup') { + doLast { + if (backupFile.exists()) { + println("[Wolf.com] Restoring placeholder file") + domainNumberFile.delete() + backupFile.renameTo(domainNumberFile) + } + } +} + +tasks.configureEach { task -> + if (task.name == "assembleDebug" || task.name == "assembleRelease") { + task.finalizedBy(restoreBackup) + } +} diff --git a/src/ko/wolfdotcom/res/mipmap-hdpi/ic_launcher.png b/src/ko/wolfdotcom/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..ea34c1cddfeb2282afd159f685b401bc3cc13ae3 GIT binary patch literal 2690 zcmV-|3Vrp7P)6raEZQh&w;Mjh)nwpwEO(hVaV3SPWv13QU(xpo` zXJ%&3cez}->@36TP-hI*L1Y%dk)sDa9#0=H@WEZXc0E{ESJ%U{T=sXSK=X60TeogP zLqo%stgNgbJ2KQbcS=dx_vAwl-SwMCAN4CqfdFN8babpJE-v0k$r&H#xj1rqm&bGd zWJ}8fm6es-c!F$^GIP~;h#B#OWZ~n zZhwH%xbs*Z{BVAL{_o>7{+OJ$ySsZm0sV<5yWq=U2LhDc+1dFp4{nIbNaJ*puCA`X z78Mme!qb1{%UJsZl+K;UB`u&)xYyICPyel?q(p$uCo~|>u=cxR95*BlpmTJz{a*t` z`Jw9CD8wW{mq`I=h_URxgFq%j(CT@i0va}{awieAqyW@M^Z3NGz|>OU&L073!;}7+ zkx2%vVY)1$LoML}$@Uz2A9(sj;QKRyRdptifdOFeo51`kV8S(aI?+)rtH*=~bfF)3 zyb(Cw2K;a?@XKL9Egis9F95$<0hAXzBtGmmp#e#H4ju(I|BKlo)5Y4`fIDtC-#_~w z;Ef|d{m+3J6$uU~Y@rTtpx|6&aKC%M38IftNX@M#q?7HylZ4jK-+%iH;QH$l98ffe zXMs6-!n8e#d-5j$Sz~}HlY!}!mTh|p?sXu0EYPqDC@qTS2(IiaVN^_D1_pt))4kp{X1m`p%L+;X$&_8BXsMG2Np9d|%y86m%Xz*HxJv82Wy zAeHv)v7ds+F_}yZDPd)lup29>iiB2>$H0=`N7`Zr$S%5?yn?^?J|dj|0o|W}Coxxm z@sb|zkJ?v&ikW^0&=&-Jp8ZOS##0R!eh;{9fw@n(EoOl11C*^f{)y?05$-Bh9)c2>XB5!|{Do03vxSy51tZ?zfNyT*KwOwY8 z_4YB~`6lz~`%^1T&#qc|v_I7h8W&JPCNhea$@!oKc#D1M{S8xqQ{B7(ZDtsdW301; z*Ww~U0--B@2$pExY2mLxByZl{O*Uqi?5if6%3A4WDZx{7S1x={8et>lQg7qc!9=65b};6 z5t^h_wrL8J-6|UK62g+53I$YL0DagBJi~Q@?lZ$`rKLH!CO(+$pbSlY|8V6;nWiyt*ILFh9K&_>6G;8Dk&L z%P4C|t?v1WS@w{T9!-;U_3)hA_5truqJ}t@S(M5}v}M;Z*}a-ElrK;*10>NuOq;Wv zX@WeBghd1P_*mHW5+sbfitc(ouy`S(Xsz9b*#t>7r1u%6?_#umf)`6MR2^;FPpi%M z-YsT;IzKb3f=U;zrRRC>C8iaOp1qql98F5@{MRgNOy;XqEDMv7aoCwJfQ?%y&8L9b zl%o6Y0P^GwZ4tN?x5W(5R~LDO=tVS!7hX1_XS-YX1;&D)flCcmaU$NeA0uS&(n)Vs zu|8{GjbqDB;J`b;OnSQe?=s=|YcT_)0m!Q~A2I<+)P30+YoMBpCN_~OCD%UpMzgFT zb+g(KE%2E>mtoh=QYzf^ZgTy~)nvqFr^f0#z8dPo-`-@tr|4a2$;w*35`3WCEvkU* z>BI=t;g5=#|3L%&`MK|Y^(Q&9HA)rc5lq3g=0pWd;JzVfW%6$^<~d4xB3q{Kj2MSq zYr=*q@>P)Zok9aaQp}Z4Z}_uiR9Qj_me@uWP$0XYL{vpC8Axv^)^T#V0wWi^fH9BU zx@B_|{AqG+UBG@yq53&bb@6WUcH?u*c&Cs#nxyF(e%9+4CAO*rP^DB$3Z=yakn*jq zbgz3AhxoYDMyY^o%k1l!CQPLfXMs!;wYN)L6Z?cl{S*P|y&~pzzPDNyrNiihM)#%} zrj%GxWJ!q?ZiNEI+Q)G~y7GJ90RBm@B{i@&iM%GHV!UFUY}yxpsel4L#V%@6uP2@Z zI+$;1@~Sj0;(FOkN=+`MC6esj8=j+pWG@uzKgA`R-m~VZ1waxrg9cZNfC~QwDwMEa z$19%N*=z=U0--v!5?6nL-e{!7*S?MdQZelBG|EaEWh_-Y)>T#_sn|w#!C&*I4w9b7 z8N(=ttGUJ80`(IzAU__)89+SQ0I&4F_t32~IFvnDbd#9_Ddy0^U#JpepPPXh8CYGRi7VJb1Uc_7&@vuz+Nkdztk&{g;(QF8-;|9*>r=fV5_Ii1!9* zg>+hZ0J{@T2Kp`O;0K2X=y{~$9%t{9R%xS^*?VPKlv0T>#V)^fa@k2#GLoF>WkMW7a$2f{nJq~3&P`78#o_njStN+3W zdIK!I#czU0lD^ z__nsTL#tP>UfI;t#QXZ8yO>6-SOqdJTC2=mzI^%A_3PJfEG;dao{^E^rdTGsx(!is z`rF&vkFQy?rhfPC-CXVjqq5OG)T;|FHB;Ftr>d%IQhj}Wbwx$R6sp@;LP|=jO=sib zqu`flcTQ~DwCTX%!-u)BWWdn_y*~e7AlO7DExJWgE9T0zwN7D4P_SbYQLCgvvNO7K zKq{q8aKidy0c(PiBvps4%~%!JZ9IA!KgpT^Y10xX?_x5xi2jSn#&9h_ w)@#r?B%_)Hom4b;NC65$GX*FGC^CinKk=a1g#h$NoB#j-07*qoM6N<$f}C9(>;M1& literal 0 HcmV?d00001 diff --git a/src/ko/wolfdotcom/res/mipmap-mdpi/ic_launcher.png b/src/ko/wolfdotcom/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..ef099026fe40c69e711b6b3ceb1f5d8e4007a68f GIT binary patch literal 1816 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 literal 0 HcmV?d00001 diff --git a/src/ko/wolfdotcom/res/mipmap-xxhdpi/ic_launcher.png b/src/ko/wolfdotcom/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..878819eb5b46a1c0e0a9dcc8985ca2890fd00062 GIT binary patch literal 6918 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 M07*qoM6N<$f{Gm*wg3PC literal 0 HcmV?d00001 diff --git a/src/ko/wolfdotcom/res/mipmap-xxxhdpi/ic_launcher.png b/src/ko/wolfdotcom/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db4b0eb3a6d4b29cb6f2c194d752203d9ffb258d GIT binary patch literal 8853 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, +) : Filter.Select( + "필터", + options.map { it.toString() }.toTypedArray(), +), + UrlPartFilter { + override fun addToUrl(url: HttpUrl.Builder) { + val selected = options[state] + url.addQueryParameter("type1", selected.type) + selected.value?.let { + url.addQueryParameter("type2", it) + } + } +} + +class SortFilter(default: Int = 0) : + Filter.Select( + "정렬 기준", + options.map { it.first }.toTypedArray(), + default, + ), + UrlPartFilter { + + override fun addToUrl(url: HttpUrl.Builder) { + url.addQueryParameter("o", options[state].second) + } + + companion object { + private val options = listOf( + "최신순" to "n", + "인기순" to "f", + ) + } +} + +val POPULAR = FilterList(SortFilter(1)) +val LATEST = FilterList(SortFilter(0)) diff --git a/src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/Wolf.kt b/src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/Wolf.kt new file mode 100644 index 000000000..f45e3e7fc --- /dev/null +++ b/src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/Wolf.kt @@ -0,0 +1,417 @@ +package eu.kanade.tachiyomi.extension.ko.wolfdotcom + +import android.app.Application +import android.content.SharedPreferences +import android.util.Log +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Filter +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.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.io.IOException +import java.net.URLEncoder +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Locale + +open class Wolf( + name: String, + private val browsePath: String, + private val entryPath: String, + private val readerPath: String, +) : HttpSource(), ConfigurableSource { + + override val name = "늑대닷컴 - $name" + + override val lang = "ko" + + override val baseUrl: String + get() = "https://wfwf$domainNumber.com" + + override val supportsLatest = true + + override val client = network.cloudflareClient.newBuilder() + .addInterceptor(::domainNumberInterceptor) + .build() + + private val json: Json by injectLazy() + + private val preference: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun fetchPopularManga(page: Int): Observable { + return fetchSearchManga(page, "", POPULAR) + } + + override fun fetchLatestUpdates(page: Int): Observable { + return fetchSearchManga(page, "", LATEST) + } + + private var searchFilters: List = emptyList() + private var filterParseError = false + + override fun getFilterList(): FilterList { + val filters: MutableList> = mutableListOf( + SortFilter(), + ) + + if (searchFilters.isNotEmpty()) { + filters.add( + SearchFilter(searchFilters), + ) + } else if (filterParseError) { + filters.add( + Filter.Header("unable to parse filters"), + ) + } else { + filters.add( + Filter.Header("press 'reset' to attempt to load more filters"), + ) + } + + return FilterList(filters) + } + + protected open fun parseSearchFilters(document: Document) { + if (searchFilters.isNotEmpty() || filterParseError) return + + try { + val displayName = document.select(".sub-tab > a").eachText() + assert(displayName.size == 3) + searchFilters = + document.select(".tab-day > a, .tab-genre1 > a, .tab-genre2 > a, .tab-alphabet > a") + .map { + val url = it.absUrl("href").toHttpUrl() + val type = url.queryParameter("type1")!! + FilterData( + type = type, + typeDisplayName = when (type) { + "day", "complete" -> displayName[0] + "genre" -> displayName[1] + "alphabet" -> displayName[2] + else -> null + }, + value = url.queryParameter("type2"), + valueDisplayName = it.ownText(), + ) + } + } catch (e: Throwable) { + Log.e(name, "error parsing filters", e) + filterParseError = true + } + } + + private lateinit var browseCache: List> + + class BrowseItem( + val id: Int, + val title: String, + val cover: String?, + ) + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (query.isNotBlank()) { + return querySearch(query) + } + + return if (page == 1) { + client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { + parseBrowsePage(it) + paginatedBrowsePage(0) + } + } else { + Observable.just( + paginatedBrowsePage(page - 1), + ) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/$browsePath".toHttpUrl().newBuilder().apply { + filters.filterIsInstance().forEach { filter -> + filter.addToUrl(this) + } + }.build() + + return GET(url, headers) + } + + private fun parseBrowsePage(response: Response) { + val document = response.asJsoup() + + parseSearchFilters(document) + + browseCache = document.select(".webtoon-list > ul > li > a").map { + val id = it.absUrl("href").toHttpUrl() + .queryParameter("toon")!!.toInt() + + BrowseItem( + id = id, + title = it.selectFirst(".txt > .subject")!!.ownText(), + cover = it.selectFirst(".img > img")?.attr("data-original"), + ) + }.chunked(20) + } + + private fun paginatedBrowsePage(index: Int): MangasPage { + return MangasPage( + browseCache[index].map { + SManga.create().apply { + url = it.id.toString() + title = it.title + thumbnail_url = it.cover + } + }, + browseCache.lastIndex > index, + ) + } + + private val specialChars = Regex("""[^\p{InHangul_Syllables}0-9a-z ]""", RegexOption.IGNORE_CASE) + private val styleImage = Regex("""background-image:url\(([^)]+)\)""") + + private fun querySearch(query: String): Observable { + if (query.length < 2) { + throw Exception("두 글자 이상 입력 해주세요.") + } + val stdQuery = query.replace(specialChars, "") + val searchUrl = "$baseUrl/search.html?q=${URLEncoder.encode(stdQuery, "EUC-KR")}" + + return client.newCall(GET(searchUrl, headers)) + .asObservableSuccess() + .map { response -> + val document = Jsoup.parseBodyFragment(response.body.string(), searchUrl) + val entries = document.select("article.searchItem") + .filter { el -> + el.selectFirst("a.searchLink")!!.attr("href").contains(entryPath) + } + .map { el -> + val mangaUrl = el.selectFirst("a.searchLink")!!.absUrl("href") + .toHttpUrl() + SManga.create().apply { + url = mangaUrl.queryParameter("toon")!! + title = el.selectFirst(".searchDetailTitle")!!.text() + thumbnail_url = el.selectFirst(".searchPng") + ?.attr("style") + ?.let { + styleImage.find(it)?.groupValues?.get(1) + } + } + } + + MangasPage(entries, false) + } + } + + override fun getMangaUrl(manga: SManga): String { + return baseUrl.toHttpUrl().newBuilder() + .addPathSegment(entryPath) + .addQueryParameter("toon", manga.url) + .toString() + } + + override fun mangaDetailsRequest(manga: SManga): Request { + return GET(getMangaUrl(manga), headers) + } + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + + return SManga.create().apply { + title = document.selectFirst(".text-box h1")!!.text() + thumbnail_url = document.selectFirst(".img-box img")?.absUrl("src") + description = document.selectFirst(".text-box .txt")?.text() + genre = document.selectFirst(".text-box .sub:has(> strong:contains(장르))")?.ownText()?.replace("/", ", ") + author = document.selectFirst(".text-box .sub:has(> strong:contains(작가))")?.ownText()?.replace("/", ", ") + } + } + + override fun chapterListRequest(manga: SManga): Request { + return mangaDetailsRequest(manga) + } + + @Serializable + class ChapterUrl( + val toon: String, + val num: String, + ) + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + + return document.select(".webtoon-bbs-list a.view_open").map { el -> + val chapUrl = el.absUrl("href").toHttpUrl() + SChapter.create().apply { + url = json.encodeToString( + ChapterUrl( + chapUrl.queryParameter("toon")!!, + chapUrl.queryParameter("num")!!, + ), + ) + name = el.selectFirst(".subject")!!.ownText() + date_upload = el.selectFirst(".date")?.text().parseDate() + } + } + } + + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + + private fun String?.parseDate(): Long { + this ?: return 0L + + return try { + dateFormat.parse(this)!!.time + } catch (_: ParseException) { + 0L + } + } + + override fun getChapterUrl(chapter: SChapter): String { + val chapUrl = json.decodeFromString(chapter.url) + + return baseUrl.toHttpUrl().newBuilder() + .addPathSegment(readerPath) + .addQueryParameter("toon", chapUrl.toon) + .addQueryParameter("num", chapUrl.num) + .toString() + } + + override fun pageListRequest(chapter: SChapter): Request { + return GET(getChapterUrl(chapter), headers) + } + + override fun pageListParse(response: Response): List { + val document = response.asJsoup() + + return document.select(".image-view img").mapIndexed { idx, img -> + Page(idx, imageUrl = img.absUrl("data-original")) + } + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + EditTextPreference(screen.context).apply { + key = PREF_DOMAIN_NUM + title = "도메인 번호" + setOnPreferenceChangeListener { _, newValue -> + val value = newValue as String + if (value.isEmpty() || value.toIntOrNull() == null) { + false + } else { + domainNumber = value.trim() + false + } + } + }.also(screen::addPreference) + } + + private var domainNumber = "" + get() { + val currentValue = field + if (currentValue.isNotEmpty()) return currentValue + + val prefValue = preference.getString(PREF_DOMAIN_NUM, "")!! + val prefDefaultValue = preference.getString(PREF_DOMAIN_NUM_DEFAULT, "")!! + + if (prefDefaultValue != DEFAULT_DOMAIN_NUMBER) { + preference.edit() + .putString(PREF_DOMAIN_NUM_DEFAULT, DEFAULT_DOMAIN_NUMBER) + .putString(PREF_DOMAIN_NUM, DEFAULT_DOMAIN_NUMBER) + .apply() + + field = DEFAULT_DOMAIN_NUMBER + return DEFAULT_DOMAIN_NUMBER + } + + if (prefValue.isNotEmpty()) { + field = prefValue + return prefValue + } + + return DEFAULT_DOMAIN_NUMBER + } + set(value) { + preference.edit().putString(PREF_DOMAIN_NUM, value).apply() + + field = value + } + + private fun domainNumberInterceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + + val url = request.url.toString() + + if (url.contains(domainRegex)) { + val document = Jsoup.parse(response.peekBody(Long.MAX_VALUE).string()) + val newUrl = document.selectFirst("""#pop-content a[href~=^https?://wfwf\d+\.com]""") + ?: return response + + response.close() + + val newDomainNum = domainRegex.find(newUrl.attr("href"))?.groupValues?.get(1) + ?: throw IOException("Failed to update domain number") + + domainNumber = newDomainNum.trim() + + return chain.proceed( + request.newBuilder() + .url( + request.url.newBuilder() + .host(baseUrl.toHttpUrl().host) + .build(), + ) + .build(), + ) + } + + return response + } + + private val domainRegex = Regex("""^https?://wfwf(\d+)\.com""") + + override fun imageUrlParse(response: Response): String { + throw UnsupportedOperationException() + } + override fun popularMangaParse(response: Response): MangasPage { + throw UnsupportedOperationException() + } + override fun popularMangaRequest(page: Int): Request { + throw UnsupportedOperationException() + } + override fun latestUpdatesParse(response: Response): MangasPage { + throw UnsupportedOperationException() + } + override fun latestUpdatesRequest(page: Int): Request { + throw UnsupportedOperationException() + } + override fun searchMangaParse(response: Response): MangasPage { + throw UnsupportedOperationException() + } +} + +private const val PREF_DOMAIN_NUM = "domain_number" +private const val PREF_DOMAIN_NUM_DEFAULT = "domain_number_default" diff --git a/src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/WolfFactory.kt b/src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/WolfFactory.kt new file mode 100644 index 000000000..dddd55c36 --- /dev/null +++ b/src/ko/wolfdotcom/src/eu/kanade/tachiyomi/extension/ko/wolfdotcom/WolfFactory.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.extension.ko.wolfdotcom + +import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.model.FilterList +import org.jsoup.nodes.Document + +class WolfFactory : SourceFactory { + override fun createSources() = listOf( + Wolf("웹툰", "ing", "list", "view"), // webtoon + Wolf("만화책", "cm", "cl", "cv"), // comic book + object : Wolf("포토툰", "pt", "list", "view") { // phototoon + override fun getFilterList(): FilterList { + return FilterList() + } + + override fun parseSearchFilters(document: Document) { + return + } + }, + ) +}