Temple Scan: migrate to HeanCms (#18899)
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
@ -0,0 +1,35 @@
|
|||
package eu.kanade.tachiyomi.extension.en.templescan
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.heancms.Genre
|
||||
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
|
||||
class TempleScan : HeanCms("Temple Scan", "https://templescan.net", "en") {
|
||||
|
||||
override val versionId = 3
|
||||
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(1)
|
||||
.build()
|
||||
|
||||
override val useNewQueryEndpoint = true
|
||||
override val coverPath = ""
|
||||
override val mangaSubDirectory = "comic"
|
||||
|
||||
override fun getGenreList() = listOf(
|
||||
Genre("Drama", 1),
|
||||
Genre("Josei", 2),
|
||||
Genre("Romance", 3),
|
||||
Genre("Girls Love", 4),
|
||||
Genre("Reincarnation", 5),
|
||||
Genre("Fantasia", 6),
|
||||
Genre("Ecchi", 7),
|
||||
Genre("Adventure", 8),
|
||||
Genre("Boys Love", 9),
|
||||
Genre("School Life", 10),
|
||||
Genre("Action", 11),
|
||||
Genre("Military", 13),
|
||||
Genre("Comedy", 14),
|
||||
Genre("Apocalypse", 15),
|
||||
)
|
||||
}
|
|
@ -63,6 +63,8 @@ abstract class HeanCms(
|
|||
|
||||
protected open val coverPath: String = "cover/"
|
||||
|
||||
protected open val mangaSubDirectory: String = "series"
|
||||
|
||||
protected open val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ", Locale.US)
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||
|
@ -116,7 +118,7 @@ abstract class HeanCms(
|
|||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, slugStrategy)
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
@ -130,7 +132,7 @@ abstract class HeanCms(
|
|||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, slugStrategy)
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
@ -186,9 +188,9 @@ abstract class HeanCms(
|
|||
val manga = SManga.create().apply {
|
||||
url = if (slugStrategy != SlugStrategy.NONE) {
|
||||
val mangaId = getIdBySlug(slug)
|
||||
"/series/${slug.toPermSlugIfNeeded()}#$mangaId"
|
||||
"/$mangaSubDirectory/${slug.toPermSlugIfNeeded()}#$mangaId"
|
||||
} else {
|
||||
"/series/$slug"
|
||||
"/$mangaSubDirectory/$slug"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,7 +287,7 @@ abstract class HeanCms(
|
|||
.filter { it.type == "Comic" }
|
||||
.map {
|
||||
it.slug = it.slug.toPermSlugIfNeeded()
|
||||
it.toSManga(apiUrl, coverPath, seriesSlugMap.orEmpty(), slugStrategy)
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, seriesSlugMap.orEmpty(), slugStrategy)
|
||||
}
|
||||
|
||||
return MangasPage(mangaList, false)
|
||||
|
@ -298,7 +300,7 @@ abstract class HeanCms(
|
|||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, slugStrategy)
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
@ -312,7 +314,7 @@ abstract class HeanCms(
|
|||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, slugStrategy)
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
@ -332,7 +334,7 @@ abstract class HeanCms(
|
|||
seriesSlug
|
||||
}
|
||||
|
||||
return "$baseUrl/series/$currentSlug"
|
||||
return "$baseUrl/$mangaSubDirectory/$currentSlug"
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
|
@ -380,7 +382,7 @@ abstract class HeanCms(
|
|||
.also { it[seriesResult.slug.toPermSlugIfNeeded()] = seriesResult.slug }
|
||||
}
|
||||
|
||||
val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, slugStrategy)
|
||||
val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
|
||||
return seriesDetails.apply {
|
||||
status = status.takeUnless { it == SManga.UNKNOWN }
|
||||
|
@ -404,13 +406,13 @@ abstract class HeanCms(
|
|||
return result.seasons.orEmpty()
|
||||
.flatMap { it.chapters.orEmpty() }
|
||||
.filterNot { it.price == 1 }
|
||||
.map { it.toSChapter(result.slug, dateFormat, slugStrategy) }
|
||||
.map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) }
|
||||
.filter { it.date_upload <= currentTimestamp }
|
||||
}
|
||||
|
||||
return result.chapters.orEmpty()
|
||||
.filterNot { it.price == 1 }
|
||||
.map { it.toSChapter(result.slug, dateFormat, slugStrategy) }
|
||||
.map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) }
|
||||
.filter { it.date_upload <= currentTimestamp }
|
||||
.reversed()
|
||||
}
|
||||
|
@ -419,7 +421,7 @@ abstract class HeanCms(
|
|||
if (slugStrategy == SlugStrategy.NONE) return baseUrl + chapter.url
|
||||
|
||||
val seriesSlug = chapter.url
|
||||
.substringAfter("/series/")
|
||||
.substringAfter("/$mangaSubDirectory/")
|
||||
.substringBefore("/")
|
||||
.toPermSlugIfNeeded()
|
||||
|
||||
|
@ -432,7 +434,7 @@ abstract class HeanCms(
|
|||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
if (useNewQueryEndpoint) {
|
||||
if (slugStrategy != SlugStrategy.NONE) {
|
||||
val seriesPermSlug = chapter.url.substringAfter("/series/").substringBefore("/")
|
||||
val seriesPermSlug = chapter.url.substringAfter("/$mangaSubDirectory/").substringBefore("/")
|
||||
val seriesSlug = preferences.slugMap[seriesPermSlug] ?: seriesPermSlug
|
||||
val chapterUrl = chapter.url.replaceFirst(seriesPermSlug, seriesSlug)
|
||||
return GET(baseUrl + chapterUrl, headers)
|
||||
|
|
|
@ -36,6 +36,7 @@ data class HeanCmsSearchDto(
|
|||
fun toSManga(
|
||||
apiUrl: String,
|
||||
coverPath: String,
|
||||
mangaSubDirectory: String,
|
||||
slugMap: Map<String, HeanCms.HeanCmsTitle>,
|
||||
slugStrategy: SlugStrategy,
|
||||
): SManga = SManga.create().apply {
|
||||
|
@ -44,7 +45,7 @@ data class HeanCmsSearchDto(
|
|||
title = this@HeanCmsSearchDto.title
|
||||
thumbnail_url = thumbnail?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
|
||||
?: thumbnailFileName?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
|
||||
url = "/series/$slugOnly"
|
||||
url = "/$mangaSubDirectory/$slugOnly"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +68,7 @@ data class HeanCmsSeriesDto(
|
|||
fun toSManga(
|
||||
apiUrl: String,
|
||||
coverPath: String,
|
||||
mangaSubDirectory: String,
|
||||
slugStrategy: SlugStrategy,
|
||||
): SManga = SManga.create().apply {
|
||||
val descriptionBody = this@HeanCmsSeriesDto.description?.let(Jsoup::parseBodyFragment)
|
||||
|
@ -85,9 +87,9 @@ data class HeanCmsSeriesDto(
|
|||
?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
|
||||
status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN
|
||||
url = if (slugStrategy != SlugStrategy.NONE) {
|
||||
"/series/$slugOnly#$id"
|
||||
"/$mangaSubDirectory/$slugOnly#$id"
|
||||
} else {
|
||||
"/series/$slug"
|
||||
"/$mangaSubDirectory/$slug"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +114,7 @@ data class HeanCmsChapterDto(
|
|||
) {
|
||||
fun toSChapter(
|
||||
seriesSlug: String,
|
||||
mangaSubDirectory: String,
|
||||
dateFormat: SimpleDateFormat,
|
||||
slugStrategy: SlugStrategy,
|
||||
): SChapter = SChapter.create().apply {
|
||||
|
@ -119,7 +122,7 @@ data class HeanCmsChapterDto(
|
|||
name = this@HeanCmsChapterDto.name.trim()
|
||||
date_upload = runCatching { dateFormat.parse(createdAt)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
url = "/series/$seriesSlugOnly/$slug#$id"
|
||||
url = "/$mangaSubDirectory/$seriesSlugOnly/$slug#$id"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ class HeanCmsGenerator : ThemeSourceGenerator {
|
|||
SingleLang("Omega Scans", "https://omegascans.org", "en", isNsfw = true, overrideVersionCode = 18),
|
||||
SingleLang("Perf Scan", "https://perf-scan.fr", "fr"),
|
||||
SingleLang("Reaper Scans", "https://reaperscans.net", "pt-BR", overrideVersionCode = 36),
|
||||
SingleLang("Temple Scan", "https://templescan.net", "en", isNsfw = true, overrideVersionCode = 16),
|
||||
SingleLang("YugenMangas", "https://yugenmangas.net", "es", isNsfw = true, overrideVersionCode = 9),
|
||||
)
|
||||
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".en.templescan.TempleScanUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="templescan.net"
|
||||
android:pathPattern="/comic/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -1,12 +0,0 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'Temple Scan'
|
||||
pkgNameSuffix = 'en.templescan'
|
||||
extClass = '.TempleScan'
|
||||
extVersionCode = 33
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
|
@ -1,217 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.templescan
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
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.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Calendar
|
||||
import kotlin.math.min
|
||||
|
||||
class TempleScan : HttpSource() {
|
||||
|
||||
override val name = "Temple Scan"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val baseUrl = "https://templescan.net"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val versionId = 2
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(1)
|
||||
.build()
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val homeDocument: Document by lazy {
|
||||
val response = client.newCall(GET(baseUrl, headers)).execute()
|
||||
|
||||
if (response.isSuccessful.not()) {
|
||||
response.close()
|
||||
throw Exception("Http Error ${response.code}")
|
||||
}
|
||||
|
||||
response.use { it.asJsoup() }
|
||||
}
|
||||
|
||||
private val seriesCache: List<Series> by lazy {
|
||||
homeDocument.selectFirst("script:containsData(proyectos)")
|
||||
?.data()
|
||||
?.let { proyectosRegex.find(it)?.groupValues?.get(1) }
|
||||
?.let(json::decodeFromString)
|
||||
?: throw Exception("Unable to extract series information")
|
||||
}
|
||||
|
||||
private lateinit var filteredSeriesCache: List<Series>
|
||||
|
||||
private fun List<Series>.toMangasPage(page: Int): MangasPage {
|
||||
val end = min(page * limit, this.size)
|
||||
val entries = this.subList((page - 1) * limit, end)
|
||||
.map(Series::toSManga)
|
||||
|
||||
return MangasPage(entries, end < this.size)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Series(
|
||||
@SerialName("nombre") val name: String,
|
||||
val slug: String,
|
||||
@SerialName("portada") val cover: String,
|
||||
) {
|
||||
fun toSManga() = SManga.create().apply {
|
||||
url = "/comic/$slug"
|
||||
title = name
|
||||
thumbnail_url = cover
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
val mangasPage = seriesCache.toMangasPage(page)
|
||||
|
||||
return Observable.just(mangasPage)
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
val slugs = homeDocument.select("section:contains(new release) figure")
|
||||
.mapNotNull { element ->
|
||||
element.selectFirst("a")?.attr("abs:href")
|
||||
?.substringAfter("/comic/")
|
||||
?.substringBefore("/")
|
||||
}
|
||||
|
||||
val entries = slugs.mapNotNull { slug ->
|
||||
seriesCache.firstOrNull { it.slug == slug }?.toSManga()
|
||||
}
|
||||
|
||||
val mangasPage = MangasPage(entries, false)
|
||||
|
||||
return Observable.just(mangasPage)
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
if (query.startsWith(SEARCH_PREFIX)) {
|
||||
val url = "/comic/${query.substringAfter(SEARCH_PREFIX)}"
|
||||
val manga = SManga.create().apply { this.url = url }
|
||||
return fetchMangaDetails(manga).map {
|
||||
val newManga = it.apply { this.url = url }
|
||||
MangasPage(listOf(newManga), false)
|
||||
}
|
||||
}
|
||||
|
||||
if (page == 1) {
|
||||
filteredSeriesCache = seriesCache.filter {
|
||||
it.name.contains(query.trim(), true)
|
||||
}
|
||||
}
|
||||
|
||||
val mangasPage = filteredSeriesCache.toMangasPage(page)
|
||||
|
||||
return Observable.just(mangasPage)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val document = response.asJsoup()
|
||||
|
||||
return SManga.create().apply {
|
||||
thumbnail_url = document.select(".max-w-80 > img").attr("abs:src")
|
||||
description = document.select("section[id=section-sinopsis] p").text()
|
||||
title = document.select("h1").text()
|
||||
genre = document.select("div.flex div:contains(gen) + div a").joinToString { it.text().trim() }
|
||||
author = document.selectFirst("div.flex div:contains(aut) + div")?.text()
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val elements = response.asJsoup()
|
||||
.select("div.contenedor-capitulo-miniatura")
|
||||
|
||||
return elements.map { element ->
|
||||
SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||
name = element.select("div[id=name]").text()
|
||||
date_upload = element.select("time").text().let {
|
||||
runCatching { it.parseRelativeDate() }.getOrDefault(0L)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val elements = response.asJsoup()
|
||||
.select("main div img")
|
||||
|
||||
return elements.mapIndexed { index, element ->
|
||||
Page(index, "", element.attr("abs:src"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseRelativeDate(): Long {
|
||||
val now = Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}
|
||||
|
||||
var parsedDate = 0L
|
||||
|
||||
val relativeDate = this.split(" ")[0].trim().toInt()
|
||||
|
||||
when {
|
||||
"second" in this -> {
|
||||
parsedDate = now.apply { add(Calendar.SECOND, -relativeDate) }.timeInMillis
|
||||
}
|
||||
"minute" in this -> {
|
||||
parsedDate = now.apply { add(Calendar.MINUTE, -relativeDate) }.timeInMillis
|
||||
}
|
||||
"hour" in this -> {
|
||||
parsedDate = now.apply { add(Calendar.HOUR, -relativeDate) }.timeInMillis
|
||||
}
|
||||
"day" in this -> {
|
||||
parsedDate = now.apply { add(Calendar.DAY_OF_YEAR, -relativeDate) }.timeInMillis
|
||||
}
|
||||
"week" in this -> {
|
||||
parsedDate = now.apply { add(Calendar.WEEK_OF_YEAR, -relativeDate) }.timeInMillis
|
||||
}
|
||||
"month" in this -> {
|
||||
parsedDate = now.apply { add(Calendar.MONTH, -relativeDate) }.timeInMillis
|
||||
}
|
||||
"year" in this -> {
|
||||
parsedDate = now.apply { add(Calendar.YEAR, -relativeDate) }.timeInMillis
|
||||
}
|
||||
}
|
||||
return parsedDate
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val proyectosRegex by lazy {
|
||||
Regex("""proyectos\s*=\s*([^;]+)""")
|
||||
}
|
||||
|
||||
private const val limit = 20
|
||||
const val SEARCH_PREFIX = "slug:"
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Not Used")
|
||||
override fun popularMangaParse(response: Response) = throw UnsupportedOperationException("Not Used")
|
||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not Used")
|
||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not Used")
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not Used")
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Not Used")
|
||||
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Not Used")
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.templescan
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class TempleScanUrlActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val slug = pathSegments[1]
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.SEARCH"
|
||||
putExtra("query", "${TempleScan.SEARCH_PREFIX}$slug")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("TempleScanUrlActivity", e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e("TempleScanUrlActivity", "could not parse uri from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|