Pururin refactor (#438)
@ -1,8 +1,7 @@
 | 
			
		||||
ext {
 | 
			
		||||
    extName = 'Pururin'
 | 
			
		||||
    pkgNameSuffix = 'en.pururin'
 | 
			
		||||
    extClass = '.Pururin'
 | 
			
		||||
    extVersionCode = 6
 | 
			
		||||
    extClass = '.PururinFactory'
 | 
			
		||||
    extVersionCode = 7
 | 
			
		||||
    isNsfw = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB  | 
| 
		 Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB  | 
| 
		 Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB  | 
| 
		 Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB  | 
@ -0,0 +1,163 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.pururin
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
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.util.asJsoup
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrl
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
 | 
			
		||||
abstract class Pururin(
 | 
			
		||||
    override val lang: String = "all",
 | 
			
		||||
    private val searchLang: String? = null,
 | 
			
		||||
    private val langPath: String = "",
 | 
			
		||||
) : ParsedHttpSource() {
 | 
			
		||||
    override val name = "Pururin"
 | 
			
		||||
 | 
			
		||||
    override val baseUrl = "https://pururin.to"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client = network.cloudflareClient
 | 
			
		||||
 | 
			
		||||
    // Popular
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int): Request {
 | 
			
		||||
        return GET("$baseUrl/browse$langPath?sort=most-popular&page=$page", headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaSelector(): String = "a.card"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element): SManga {
 | 
			
		||||
        return SManga.create().apply {
 | 
			
		||||
            title = element.attr("title")
 | 
			
		||||
            setUrlWithoutDomain(element.attr("abs:href"))
 | 
			
		||||
            thumbnail_url = element.select("img").attr("abs:src")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector(): String = ".page-item [rel=next]"
 | 
			
		||||
 | 
			
		||||
    // Latest
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int): Request {
 | 
			
		||||
        return GET("$baseUrl/browse$langPath?page=$page", headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesSelector(): String = popularMangaSelector()
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector()
 | 
			
		||||
 | 
			
		||||
    // Search
 | 
			
		||||
 | 
			
		||||
    private fun List<String>.toValue(): String {
 | 
			
		||||
        return "[${this.joinToString(",")}]"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val includeTags = mutableListOf<String>()
 | 
			
		||||
        val excludeTags = mutableListOf<String>()
 | 
			
		||||
        var pagesMin: Int
 | 
			
		||||
        var pagesMax: Int
 | 
			
		||||
 | 
			
		||||
        if (searchLang != null) includeTags.add(searchLang)
 | 
			
		||||
 | 
			
		||||
        filters.filterIsInstance<TagGroup<*>>().map { group ->
 | 
			
		||||
            group.state.map {
 | 
			
		||||
                if (it.isIncluded()) includeTags.add(it.id)
 | 
			
		||||
                if (it.isExcluded()) excludeTags.add(it.id)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        filters.find<PagesGroup>().range.let {
 | 
			
		||||
            pagesMin = it.first
 | 
			
		||||
            pagesMax = it.last
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val url = baseUrl.toHttpUrl().newBuilder().apply {
 | 
			
		||||
            addPathSegment("search")
 | 
			
		||||
            addQueryParameter("q", query)
 | 
			
		||||
            addQueryParameter("start_page", pagesMin.toString())
 | 
			
		||||
            addQueryParameter("last_page", pagesMax.toString())
 | 
			
		||||
            if (includeTags.isNotEmpty()) addQueryParameter("included_tags", includeTags.toValue())
 | 
			
		||||
            if (excludeTags.isNotEmpty()) addQueryParameter("excluded_tags", excludeTags.toValue())
 | 
			
		||||
            if (page > 1) addQueryParameter("page", page.toString())
 | 
			
		||||
        }
 | 
			
		||||
        return GET(url.build().toString(), headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaSelector(): String = popularMangaSelector()
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
 | 
			
		||||
 | 
			
		||||
    // Details
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga {
 | 
			
		||||
        return SManga.create().apply {
 | 
			
		||||
            document.select(".box-gallery").let { e ->
 | 
			
		||||
                initialized = true
 | 
			
		||||
                title = e.select(".title").text()
 | 
			
		||||
                author = e.select("[itemprop=author]").text()
 | 
			
		||||
                description = e.select(".box-gallery .table-info tr")
 | 
			
		||||
                    .joinToString("\n") { tr ->
 | 
			
		||||
                        tr.select("td")
 | 
			
		||||
                            .joinToString(": ") { it.text() }
 | 
			
		||||
                    }
 | 
			
		||||
                thumbnail_url = e.select("img").attr("abs:src")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Chapters
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector(): String = ".table-collection tbody tr a"
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter {
 | 
			
		||||
        return SChapter.create().apply {
 | 
			
		||||
            name = element.text()
 | 
			
		||||
            setUrlWithoutDomain(element.attr("abs:href"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> {
 | 
			
		||||
        return response.asJsoup().select(chapterListSelector())
 | 
			
		||||
            .map { chapterFromElement(it) }
 | 
			
		||||
            .reversed()
 | 
			
		||||
            .let { list ->
 | 
			
		||||
                list.ifEmpty {
 | 
			
		||||
                    listOf(
 | 
			
		||||
                        SChapter.create().apply {
 | 
			
		||||
                            setUrlWithoutDomain(response.request.url.toString())
 | 
			
		||||
                            name = "Chapter"
 | 
			
		||||
                        },
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pages
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select(".gallery-preview a img")
 | 
			
		||||
            .mapIndexed { i, img ->
 | 
			
		||||
                Page(i, "", img.attr("abs:src").replace("t.", "."))
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
 | 
			
		||||
 | 
			
		||||
    override fun getFilterList() = FilterList(
 | 
			
		||||
        CategoryGroup(),
 | 
			
		||||
        PagesGroup(),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.pururin
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceFactory
 | 
			
		||||
 | 
			
		||||
class PururinFactory : SourceFactory {
 | 
			
		||||
    override fun createSources(): List<Source> = listOf(
 | 
			
		||||
        PururinAll(),
 | 
			
		||||
        PururinEN(),
 | 
			
		||||
        PururinJA(),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class PururinAll : Pururin()
 | 
			
		||||
class PururinEN : Pururin(
 | 
			
		||||
    "en",
 | 
			
		||||
    "{\"id\":13010,\"name\":\"English [Language]\"}",
 | 
			
		||||
    "/tags/language/13010/english",
 | 
			
		||||
)
 | 
			
		||||
class PururinJA : Pururin(
 | 
			
		||||
    "ja",
 | 
			
		||||
    "{\"id\":13011,\"name\":\"Japanese [Language]\"}",
 | 
			
		||||
    "/tags/language/13011/japanese",
 | 
			
		||||
)
 | 
			
		||||
@ -1,16 +1,10 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.pururin
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.pururin
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Filter
 | 
			
		||||
 | 
			
		||||
class SortFilter(
 | 
			
		||||
    values: Array<Search.Sort> = Search.Sort.values(),
 | 
			
		||||
) : Filter.Select<Search.Sort>("Sort by", values) {
 | 
			
		||||
    inline val sort get() = values[state]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sealed class TagFilter(
 | 
			
		||||
    name: String,
 | 
			
		||||
    val id: Int,
 | 
			
		||||
    val id: String,
 | 
			
		||||
) : Filter.TriState(name)
 | 
			
		||||
 | 
			
		||||
sealed class TagGroup<T : TagFilter>(
 | 
			
		||||
@ -18,38 +12,30 @@ sealed class TagGroup<T : TagFilter>(
 | 
			
		||||
    values: List<T>,
 | 
			
		||||
) : Filter.Group<T>(name, values)
 | 
			
		||||
 | 
			
		||||
// TODO: Artist, Circle, Contents, Parody, Character, Convention
 | 
			
		||||
 | 
			
		||||
class Category(name: String, id: Int) : TagFilter(name, id)
 | 
			
		||||
class Category(name: String, id: String) : TagFilter(name, id)
 | 
			
		||||
 | 
			
		||||
class CategoryGroup(
 | 
			
		||||
    values: List<Category> = categories,
 | 
			
		||||
) : TagGroup<Category>("Categories", values) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val categories get() = listOf(
 | 
			
		||||
            Category("Doujinshi", 13003),
 | 
			
		||||
            Category("Manga", 13004),
 | 
			
		||||
            Category("Artist CG", 13006),
 | 
			
		||||
            Category("Game CG", 13008),
 | 
			
		||||
            Category("Artbook", 17783),
 | 
			
		||||
            Category("Webtoon", 27939),
 | 
			
		||||
            Category("Doujinshi", "{\"id\":13003,\"name\":\"Doujinshi [Category]\"}"),
 | 
			
		||||
            Category("Manga", "{\"id\":13004,\"name\":\"Manga [Category]\"}"),
 | 
			
		||||
            Category("Artist CG", "{\"id\":13006,\"name\":\"Artist CG [Category]\"}"),
 | 
			
		||||
            Category("Game CG", "{\"id\":13008,\"name\":\"Game CG [Category]\"}"),
 | 
			
		||||
            Category("Artbook", "{\"id\":17783,\"name\":\"Artbook [Category]\"}"),
 | 
			
		||||
            Category("Webtoon", "{\"id\":27939,\"name\":\"Webtoon [Category]\"}"),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TagModeFilter(
 | 
			
		||||
    values: Array<Search.TagMode> = Search.TagMode.values(),
 | 
			
		||||
) : Filter.Select<Search.TagMode>("Tag mode", values) {
 | 
			
		||||
    inline val mode get() = values[state]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class PagesFilter(
 | 
			
		||||
    name: String,
 | 
			
		||||
    default: Int,
 | 
			
		||||
    values: Array<Int> = range,
 | 
			
		||||
) : Filter.Select<Int>(name, values, default) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val range get() = Array(1001) { it }
 | 
			
		||||
        private val range get() = Array(301) { it }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -63,7 +49,7 @@ class PagesGroup(
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val minmax get() = listOf(
 | 
			
		||||
            PagesFilter("Minimum", 0),
 | 
			
		||||
            PagesFilter("Maximum", 100),
 | 
			
		||||
            PagesFilter("Maximum", 300),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 26 KiB  | 
@ -1,163 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.pururin
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
import eu.kanade.tachiyomi.network.asObservable
 | 
			
		||||
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.HttpSource
 | 
			
		||||
import kotlinx.serialization.decodeFromString
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import kotlinx.serialization.json.decodeFromJsonElement
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
class Pururin : HttpSource() {
 | 
			
		||||
    override val name = "Pururin"
 | 
			
		||||
 | 
			
		||||
    override val baseUrl = "https://pururin.to"
 | 
			
		||||
 | 
			
		||||
    override val lang = "en"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client = network.cloudflareClient
 | 
			
		||||
 | 
			
		||||
    private val searchUrl = "$baseUrl/api/search/advance"
 | 
			
		||||
 | 
			
		||||
    private val galleryUrl = "$baseUrl/api/contribute/gallery/info"
 | 
			
		||||
 | 
			
		||||
    private val json by injectLazy<Json>()
 | 
			
		||||
 | 
			
		||||
    override fun headersBuilder() = super.headersBuilder()
 | 
			
		||||
        .set("Origin", baseUrl).set("X-Requested-With", "XMLHttpRequest")
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int) =
 | 
			
		||||
        POST(searchUrl, headers, Search(Search.Sort.NEWEST, page))
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesParse(response: Response) =
 | 
			
		||||
        searchMangaParse(response)
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int) =
 | 
			
		||||
        POST(searchUrl, headers, Search(Search.Sort.POPULAR, page))
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaParse(response: Response) =
 | 
			
		||||
        searchMangaParse(response)
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
 | 
			
		||||
        filters.ifEmpty(::getFilterList).run {
 | 
			
		||||
            val whitelist = mutableListOf<Int>()
 | 
			
		||||
            val blacklist = mutableListOf<Int>()
 | 
			
		||||
            filterIsInstance<TagGroup<*>>().forEach { group ->
 | 
			
		||||
                group.state.forEach {
 | 
			
		||||
                    when {
 | 
			
		||||
                        it.isIncluded() -> whitelist += it.id
 | 
			
		||||
                        it.isExcluded() -> blacklist += it.id
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            val body = Search(
 | 
			
		||||
                find<SortFilter>().sort,
 | 
			
		||||
                page,
 | 
			
		||||
                query,
 | 
			
		||||
                whitelist,
 | 
			
		||||
                blacklist,
 | 
			
		||||
                find<TagModeFilter>().mode,
 | 
			
		||||
                find<PagesGroup>().range,
 | 
			
		||||
            )
 | 
			
		||||
            POST(searchUrl, headers, body)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val results = json.decodeFromString<Results>(
 | 
			
		||||
            response.jsonObject["results"]!!.jsonPrimitive.content,
 | 
			
		||||
        )
 | 
			
		||||
        val mp = results.map {
 | 
			
		||||
            SManga.create().apply {
 | 
			
		||||
                url = it.path
 | 
			
		||||
                title = it.title
 | 
			
		||||
                thumbnail_url = CDN_URL + it.cover
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return MangasPage(mp, results.hasNext)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(response: Response): SManga {
 | 
			
		||||
        val gallery = json.decodeFromJsonElement<Gallery>(
 | 
			
		||||
            response.jsonObject["gallery"]!!,
 | 
			
		||||
        )
 | 
			
		||||
        return SManga.create().apply {
 | 
			
		||||
            description = gallery.description
 | 
			
		||||
            artist = gallery.artists.joinToString()
 | 
			
		||||
            author = gallery.authors.joinToString()
 | 
			
		||||
            genre = gallery.genres.joinToString()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchMangaDetails(manga: SManga) =
 | 
			
		||||
        client.newCall(chapterListRequest(manga))
 | 
			
		||||
            .asObservableSuccess().map(::mangaDetailsParse)!!
 | 
			
		||||
 | 
			
		||||
    override fun chapterListRequest(manga: SManga) =
 | 
			
		||||
        POST(galleryUrl, headers, Search.info(manga.id))
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> {
 | 
			
		||||
        val gallery = json.decodeFromJsonElement<Gallery>(
 | 
			
		||||
            response.jsonObject["gallery"]!!,
 | 
			
		||||
        )
 | 
			
		||||
        val chapter = SChapter.create().apply {
 | 
			
		||||
            name = "Chapter"
 | 
			
		||||
            url = gallery.id.toString()
 | 
			
		||||
            scanlator = gallery.scanlators.joinToString()
 | 
			
		||||
        }
 | 
			
		||||
        return listOf(chapter)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pageListRequest(chapter: SChapter) =
 | 
			
		||||
        POST(galleryUrl, headers, Search.info(chapter.url))
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(response: Response): List<Page> {
 | 
			
		||||
        val pages = json.decodeFromJsonElement<Gallery>(
 | 
			
		||||
            response.jsonObject["gallery"]!!,
 | 
			
		||||
        ).pages
 | 
			
		||||
        return pages.mapIndexed { idx, img ->
 | 
			
		||||
            Page(idx + 1, CDN_URL + img)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(response: Response) =
 | 
			
		||||
        throw UnsupportedOperationException()
 | 
			
		||||
 | 
			
		||||
    override fun fetchImageUrl(page: Page) =
 | 
			
		||||
        Request.Builder().url(page.url).head().build()
 | 
			
		||||
            .run(client::newCall).asObservable().map {
 | 
			
		||||
                when (it.code) {
 | 
			
		||||
                    200 -> page.url
 | 
			
		||||
                    // try to fix images that are broken even on the site
 | 
			
		||||
                    404 -> page.url.replaceAfterLast('.', "png")
 | 
			
		||||
                    else -> throw Error("HTTP error ${it.code}")
 | 
			
		||||
                }
 | 
			
		||||
            }!!
 | 
			
		||||
 | 
			
		||||
    override fun getFilterList() = FilterList(
 | 
			
		||||
        SortFilter(),
 | 
			
		||||
        CategoryGroup(),
 | 
			
		||||
        TagModeFilter(),
 | 
			
		||||
        PagesGroup(),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private inline val Response.jsonObject
 | 
			
		||||
        get() = json.parseToJsonElement(body.string()).jsonObject
 | 
			
		||||
 | 
			
		||||
    private inline val SManga.id get() = url.split('/')[2]
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val CDN_URL = "https://cdn.pururin.to/assets/images/data"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,66 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.pururin
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Results(
 | 
			
		||||
    private val current_page: Int,
 | 
			
		||||
    private val data: List<Data>,
 | 
			
		||||
    private val last_page: Int,
 | 
			
		||||
) : Iterable<Data> by data {
 | 
			
		||||
    val hasNext get() = current_page != last_page
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Data(
 | 
			
		||||
    private val id: Int,
 | 
			
		||||
    val title: String,
 | 
			
		||||
    private val slug: String,
 | 
			
		||||
) {
 | 
			
		||||
    val path get() = "/gallery/$id/$slug"
 | 
			
		||||
 | 
			
		||||
    val cover get() = "/$id/cover.jpg"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Gallery(
 | 
			
		||||
    val id: Int,
 | 
			
		||||
    private val j_title: String,
 | 
			
		||||
    private val alt_title: String?,
 | 
			
		||||
    private val total_pages: Int,
 | 
			
		||||
    private val image_extension: String,
 | 
			
		||||
    private val tags: TagList,
 | 
			
		||||
) {
 | 
			
		||||
    val description get() = "$j_title\n${alt_title ?: ""}".trim()
 | 
			
		||||
 | 
			
		||||
    val pages get() = (1..total_pages).map { "/$id/$it.$image_extension" }
 | 
			
		||||
 | 
			
		||||
    val genres get() = tags.Parody +
 | 
			
		||||
        tags.Contents +
 | 
			
		||||
        tags.Category +
 | 
			
		||||
        tags.Character +
 | 
			
		||||
        tags.Convention
 | 
			
		||||
 | 
			
		||||
    val artists get() = tags.Artist
 | 
			
		||||
 | 
			
		||||
    val authors get() = tags.Circle.ifEmpty { tags.Artist }
 | 
			
		||||
 | 
			
		||||
    val scanlators get() = tags.Scanlator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class TagList(
 | 
			
		||||
    val Artist: List<Tag>,
 | 
			
		||||
    val Circle: List<Tag>,
 | 
			
		||||
    val Parody: List<Tag>,
 | 
			
		||||
    val Contents: List<Tag>,
 | 
			
		||||
    val Category: List<Tag>,
 | 
			
		||||
    val Character: List<Tag>,
 | 
			
		||||
    val Scanlator: List<Tag>,
 | 
			
		||||
    val Convention: List<Tag>,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Tag(private val name: String) {
 | 
			
		||||
    override fun toString() = name
 | 
			
		||||
}
 | 
			
		||||
@ -1,76 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.pururin
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.json.add
 | 
			
		||||
import kotlinx.serialization.json.addJsonObject
 | 
			
		||||
import kotlinx.serialization.json.buildJsonObject
 | 
			
		||||
import kotlinx.serialization.json.put
 | 
			
		||||
import kotlinx.serialization.json.putJsonArray
 | 
			
		||||
import kotlinx.serialization.json.putJsonObject
 | 
			
		||||
import okhttp3.MediaType.Companion.toMediaType
 | 
			
		||||
import okhttp3.RequestBody.Companion.toRequestBody
 | 
			
		||||
 | 
			
		||||
object Search {
 | 
			
		||||
    private val jsonMime = "application/json".toMediaType()
 | 
			
		||||
 | 
			
		||||
    enum class Sort(private val label: String, val id: String) {
 | 
			
		||||
        NEWEST("Newest", "newest"),
 | 
			
		||||
        POPULAR("Most Popular", "most-popular"),
 | 
			
		||||
        RATING("Highest Rated", "highest-rated"),
 | 
			
		||||
        VIEWS("Most Viewed", "most-viewed"),
 | 
			
		||||
        TITLE("Title", "title"),
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        override fun toString() = label
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enum class TagMode(val id: String) {
 | 
			
		||||
        AND("1"), OR("2");
 | 
			
		||||
 | 
			
		||||
        override fun toString() = name
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    operator fun invoke(
 | 
			
		||||
        sort: Sort,
 | 
			
		||||
        page: Int = 1,
 | 
			
		||||
        query: String = "",
 | 
			
		||||
        whitelist: List<Int> = emptyList(),
 | 
			
		||||
        blacklist: List<Int> = emptyList(),
 | 
			
		||||
        mode: TagMode = TagMode.AND,
 | 
			
		||||
        range: IntRange = 0..100,
 | 
			
		||||
    ) = buildJsonObject {
 | 
			
		||||
        putJsonObject("search") {
 | 
			
		||||
            put("sort", sort.id)
 | 
			
		||||
            put("PageNumber", page)
 | 
			
		||||
            putJsonObject("manga") {
 | 
			
		||||
                put("string", query)
 | 
			
		||||
                put("sort", "1")
 | 
			
		||||
            }
 | 
			
		||||
            putJsonObject("tag") {
 | 
			
		||||
                putJsonObject("items") {
 | 
			
		||||
                    putJsonArray("whitelisted") {
 | 
			
		||||
                        whitelist.forEach {
 | 
			
		||||
                            addJsonObject { put("id", it) }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    putJsonArray("blacklisted") {
 | 
			
		||||
                        blacklist.forEach {
 | 
			
		||||
                            addJsonObject { put("id", it) }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                put("sort", mode.id)
 | 
			
		||||
            }
 | 
			
		||||
            putJsonObject("page") {
 | 
			
		||||
                putJsonArray("range") {
 | 
			
		||||
                    add(range.first)
 | 
			
		||||
                    add(range.last)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }.toString().toRequestBody(jsonMime)
 | 
			
		||||
 | 
			
		||||
    fun info(id: String) = buildJsonObject {
 | 
			
		||||
        put("id", id)
 | 
			
		||||
        put("type", "1")
 | 
			
		||||
    }.toString().toRequestBody(jsonMime)
 | 
			
		||||
}
 | 
			
		||||