Move snowmlt to lib-multisrc and add Solarmtl (#7054)

* Move snowmlt to lib-multisrc

* Fix snowmtl

* Remove assets from src
This commit is contained in:
Chopper 2025-01-09 11:40:56 -03:00 committed by Draff
parent 690c553b6c
commit 580b2b1b16
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
22 changed files with 384 additions and 248 deletions

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslationsUrlActivity"
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="${SOURCEHOST}"
android:pathPattern="/.*/..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,5 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,211 @@
package eu.kanade.tachiyomi.multisrc.machinetranslations
import android.os.Build
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor
import eu.kanade.tachiyomi.network.GET
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.ParsedHttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@RequiresApi(Build.VERSION_CODES.O)
abstract class MachineTranslations(
override val name: String,
override val baseUrl: String,
val language: Language,
) : ParsedHttpSource() {
override val supportsLatest = true
private val json: Json by injectLazy()
override val lang = language.lang
override val client = network.cloudflareClient.newBuilder()
.addInterceptor(ComposedImageInterceptor(baseUrl, language))
.build()
// ============================== Popular ===============================
private val popularFilter = FilterList(SelectionList("", listOf(Option(value = "views", query = "sort_by"))))
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", popularFilter)
override fun popularMangaSelector() = searchMangaSelector()
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
// =============================== Latest ===============================
private val latestFilter = FilterList(SelectionList("", listOf(Option(value = "recent", query = "sort_by"))))
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", latestFilter)
override fun latestUpdatesSelector() = searchMangaSelector()
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
// =========================== Search ============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
if (query.isNotBlank()) {
url.addQueryParameter("query", query)
}
filters.forEach { filter ->
when (filter) {
is SelectionList -> {
val selected = filter.selected()
if (selected.value.isBlank()) {
return@forEach
}
url.addQueryParameter(selected.query, selected.value)
}
is GenreList -> {
filter.state.filter(GenreCheckBox::state).forEach { genre ->
url.addQueryParameter("genres", genre.id)
}
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(PREFIX_SEARCH)) {
val slug = query.removePrefix(PREFIX_SEARCH)
return fetchMangaDetails(SManga.create().apply { url = "/comics/$slug" }).map { manga ->
MangasPage(listOf(manga), false)
}
}
return super.fetchSearchManga(page, query, filters)
}
override fun searchMangaSelector() = "section h2 + div > div"
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
title = element.selectFirst("h3")!!.text()
thumbnail_url = element.selectFirst("img")?.absUrl("src")
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
}
override fun searchMangaNextPageSelector() = "a[href*=search]:contains(Next)"
// =========================== Manga Details ============================
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst("h1")!!.text()
description = document.selectFirst("p:has(span:contains(Synopsis))")?.ownText()
author = document.selectFirst("p:has(span:contains(Author))")?.ownText()
genre = document.select("h2:contains(Genres) + div span").joinToString { it.text() }
thumbnail_url = document.selectFirst("img.object-cover")?.absUrl("src")
document.selectFirst("p:has(span:contains(Status))")?.ownText()?.let {
status = when (it.lowercase()) {
"ongoing" -> SManga.ONGOING
"complete" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
setUrlWithoutDomain(document.location())
}
// ============================== Chapters ==============================
override fun chapterListSelector() = "section li"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
element.selectFirst("a")!!.let {
name = it.ownText()
setUrlWithoutDomain(it.absUrl("href"))
}
date_upload = parseChapterDate(element.selectFirst("span")?.text())
}
// =============================== Pages ================================
override fun pageListParse(document: Document): List<Page> {
val pages = document.selectFirst("div#json-data")
?.ownText()?.parseAs<List<PageDto>>()
?: throw Exception("Pages not found")
return pages.mapIndexed { index, dto ->
val imageUrl = when {
dto.imageUrl.startsWith("http") -> dto.imageUrl
else -> "https://${dto.imageUrl}"
}
val fragment = json.encodeToString<List<Dialog>>(
dto.dialogues.filter { it.getTextBy(language).isNotBlank() },
)
Page(index, imageUrl = "$imageUrl#$fragment")
}
}
override fun imageUrlParse(document: Document): String = ""
// ============================= Utilities ==============================
private fun parseChapterDate(date: String?): Long {
date ?: return 0
return try { dateFormat.parse(date)!!.time } catch (_: Exception) { parseRelativeDate(date) }
}
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
return when {
date.contains("day", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
date.contains("hour", true) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
date.contains("minute", true) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
date.contains("second", true) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
date.contains("week", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis
else -> 0
}
}
private inline fun <reified T> String.parseAs(): T {
return json.decodeFromString(this)
}
// =============================== Filters ================================
override fun getFilterList(): FilterList {
val filters = mutableListOf<Filter<*>>(
SelectionList("Sort", sortByList),
Filter.Separator(),
GenreList(title = "Genres", genres = genreList),
)
return FilterList(filters)
}
companion object {
val PAGE_REGEX = Regex(".*?\\.(webp|png|jpg|jpeg)#\\[.*?]", RegexOption.IGNORE_CASE)
const val PREFIX_SEARCH = "id:"
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.US)
}
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.snowmtl
package eu.kanade.tachiyomi.multisrc.machinetranslations
import android.graphics.Color
import android.os.Build
@ -8,6 +8,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonTransformingSerializer
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonArray
@ -31,14 +32,17 @@ data class Dialog(
val y1: Float,
val x2: Float,
val y2: Float,
val text: String,
val angle: Float = 0f,
val isBold: Boolean = false,
val isNewApi: Boolean = false,
val textByLanguage: Map<String, String> = emptyMap(),
val type: String = "normal",
private val fbColor: List<Int> = emptyList(),
private val bgColor: List<Int> = emptyList(),
) {
val text: String get() = textByLanguage["text"] ?: throw Exception("Dialog not found")
fun getTextBy(language: Language) = textByLanguage[language.target] ?: text
val width get() = x2 - x1
val height get() = y2 - y1
val centerY get() = (y2 + y1) / 2f
@ -62,14 +66,15 @@ private object DialogListSerializer :
override fun transformDeserialize(element: JsonElement): JsonElement {
return JsonArray(
element.jsonArray.map { jsonElement ->
val (coordinates, text) = getCoordinatesAndDialog(jsonElement)
val coordinates = getCoordinates(jsonElement)
val textByLanguage = getDialogs(jsonElement)
buildJsonObject {
put("x1", coordinates[0])
put("y1", coordinates[1])
put("x2", coordinates[2])
put("y2", coordinates[3])
put("text", text)
put("textByLanguage", textByLanguage)
try {
val obj = jsonElement.jsonObject
@ -85,13 +90,28 @@ private object DialogListSerializer :
)
}
private fun getCoordinatesAndDialog(element: JsonElement): Pair<JsonArray, JsonElement> {
private fun getCoordinates(element: JsonElement): JsonArray {
return try {
val arr = element.jsonArray
arr[0].jsonArray to arr[1]
element.jsonArray[0].jsonArray
} catch (_: Exception) {
val obj = element.jsonObject
obj["bbox"]!!.jsonArray to obj["text"]!!
element.jsonObject["bbox"]!!.jsonArray
}
}
private fun getDialogs(element: JsonElement): JsonObject {
return try {
buildJsonObject {
put("text", element.jsonArray[1])
}
} catch (_: Exception) {
buildJsonObject {
// There is a problem when the "angle" is processed
element.jsonObject.entries.forEach {
if (it.key in listOf("angle", "bbox")) {
return@forEach
}
put(it.key, it.value)
}
}
}
}
}

View File

@ -0,0 +1,5 @@
package eu.kanade.tachiyomi.multisrc.machinetranslations
class MachineTranslationsFactoryUtils
data class Language(val lang: String, val target: String = lang, val origin: String = "en")

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.snowmtl
package eu.kanade.tachiyomi.multisrc.machinetranslations
import eu.kanade.tachiyomi.source.model.Filter

View File

@ -1,13 +1,15 @@
package eu.kanade.tachiyomi.extension.all.snowmtl
package eu.kanade.tachiyomi.multisrc.machinetranslations
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.annotation.RequiresApi
import kotlin.system.exitProcess
class SnowmtlUrlActivity : Activity() {
@RequiresApi(Build.VERSION_CODES.O)
class MachineTranslationsUrlActivity : Activity() {
private val tag = javaClass.simpleName
@ -18,7 +20,7 @@ class SnowmtlUrlActivity : Activity() {
val item = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${Snowmtl.PREFIX_SEARCH}$item")
putExtra("query", "${MachineTranslations.PREFIX_SEARCH}$item")
putExtra("filter", packageName)
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.all.snowmtl.interceptors
package eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors
import android.graphics.Bitmap
import android.graphics.BitmapFactory
@ -12,14 +12,14 @@ import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.extension.all.snowmtl.Dialog
import eu.kanade.tachiyomi.extension.all.snowmtl.Snowmtl.Companion.PAGE_REGEX
import eu.kanade.tachiyomi.multisrc.machinetranslations.Dialog
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations.Companion.PAGE_REGEX
import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import uy.kohesive.injekt.injectLazy
@ -35,7 +35,7 @@ import kotlin.math.sqrt
@RequiresApi(Build.VERSION_CODES.O)
class ComposedImageInterceptor(
baseUrl: String,
private val client: OkHttpClient,
val language: Language,
) : Interceptor {
private val json: Json by injectLazy()
@ -61,14 +61,16 @@ class ComposedImageInterceptor(
.url(url)
.build()
// Load the fonts before opening the connection to load the image,
// so there aren't two open connections inside the interceptor.
loadAllFont(chain)
val response = chain.proceed(imageRequest)
if (response.isSuccessful.not()) {
return response
}
loadAllFont(chain)
val bitmap = BitmapFactory.decodeStream(response.body.byteStream())!!
.copy(Bitmap.Config.ARGB_8888, true)
@ -165,11 +167,17 @@ class ComposedImageInterceptor(
private fun loadRemoteFont(fontUrl: String, chain: Interceptor.Chain): Typeface? {
return try {
val request = GET(fontUrl, chain.request().headers)
val response = client
.newCall(request).execute()
.takeIf(Response::isSuccessful) ?: return null
val response = chain.proceed(request)
if (response.isSuccessful.not()) {
response.close()
return null
}
val fontName = request.url.pathSegments.last()
response.body.byteStream().toTypeface(fontName)
response.body.use {
it.byteStream().toTypeface(fontName)
}
} catch (e: Exception) {
null
}
@ -225,14 +233,17 @@ class ComposedImageInterceptor(
return dialogBox
}
private fun createBoxLayout(dialog: Dialog, textPaint: TextPaint) =
StaticLayout.Builder.obtain(dialog.text, 0, dialog.text.length, textPaint, dialog.width.toInt()).apply {
private fun createBoxLayout(dialog: Dialog, textPaint: TextPaint): StaticLayout {
val text = dialog.getTextBy(language)
return StaticLayout.Builder.obtain(text, 0, text.length, textPaint, dialog.width.toInt()).apply {
setAlignment(Layout.Alignment.ALIGN_CENTER)
setIncludePad(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED)
}
}.build()
}
// Invert color in black dialog box.
private fun TextPaint.adjustTextColor(dialog: Dialog, bitmap: Bitmap) {

View File

@ -1,7 +1,9 @@
ext {
extName = 'Snow Machine Translations'
extClass = '.SnowmtlFactory'
extVersionCode = 6
themePkg = 'machinetranslations'
baseUrl = 'https://snowmtl.ru'
overrideVersionCode = 6
isNsfw = true
}

View File

@ -2,226 +2,35 @@ package eu.kanade.tachiyomi.extension.all.snowmtl
import android.os.Build
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.extension.all.snowmtl.interceptors.ComposedImageInterceptor
import eu.kanade.tachiyomi.extension.all.snowmtl.interceptors.TranslationInterceptor
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.BingTranslator
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.TranslatorEngine
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor
import eu.kanade.tachiyomi.network.interceptor.rateLimit
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.ParsedHttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.concurrent.TimeUnit
@RequiresApi(Build.VERSION_CODES.O)
class Snowmtl(
source: Source,
) : ParsedHttpSource() {
language: Language,
) : MachineTranslations(
name = "Snow Machine Translations",
baseUrl = "https://snowmtl.ru",
language,
) {
override val lang = language.lang
override val name = "Snow Machine Translations"
override val baseUrl = "https://snowmtl.ru"
override val lang = source.lang
override val supportsLatest = true
private val json: Json by injectLazy()
private val translatorClient = network.cloudflareClient.newBuilder()
.rateLimit(1, 3, TimeUnit.SECONDS)
private val clientUtils = network.cloudflareClient.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()
private val translator: TranslatorEngine = BingTranslator(translatorClient, headers)
private val translator: TranslatorEngine = BingTranslator(clientUtils, headers)
override val client = network.cloudflareClient.newBuilder()
.rateLimit(2)
.readTimeout(2, TimeUnit.MINUTES)
.addInterceptor(TranslationInterceptor(source, translator))
.addInterceptor(ComposedImageInterceptor(baseUrl, super.client))
.addInterceptor(TranslationInterceptor(language, translator))
.addInterceptor(ComposedImageInterceptor(baseUrl, language))
.build()
// ============================== Popular ===============================
private val popularFilter = FilterList(SelectionList("", listOf(Option(value = "views", query = "sort_by"))))
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", popularFilter)
override fun popularMangaSelector() = searchMangaSelector()
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
// =============================== Latest ===============================
private val latestFilter = FilterList(SelectionList("", listOf(Option(value = "recent", query = "sort_by"))))
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", latestFilter)
override fun latestUpdatesSelector() = searchMangaSelector()
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
// =========================== Search ============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
if (query.isNotBlank()) {
url.addQueryParameter("query", query)
}
filters.forEach { filter ->
when (filter) {
is SelectionList -> {
val selected = filter.selected()
if (selected.value.isBlank()) {
return@forEach
}
url.addQueryParameter(selected.query, selected.value)
}
is GenreList -> {
filter.state.filter(GenreCheckBox::state).forEach { genre ->
url.addQueryParameter("genres", genre.id)
}
}
else -> {}
}
}
return GET(url.build(), headers)
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(PREFIX_SEARCH)) {
val slug = query.removePrefix(PREFIX_SEARCH)
return fetchMangaDetails(SManga.create().apply { url = "/comics/$slug" }).map { manga ->
MangasPage(listOf(manga), false)
}
}
return super.fetchSearchManga(page, query, filters)
}
override fun searchMangaSelector() = "section h2 + div > div"
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
title = element.selectFirst("h3")!!.text()
thumbnail_url = element.selectFirst("img")?.absUrl("src")
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
}
override fun searchMangaNextPageSelector() = "a[href*=search]:contains(Next)"
// =========================== Manga Details ============================
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst("h1")!!.text()
description = document.selectFirst("p:has(span:contains(Synopsis))")?.ownText()
author = document.selectFirst("p:has(span:contains(Author))")?.ownText()
genre = document.select("h2:contains(Genres) + div span").joinToString { it.text() }
thumbnail_url = document.selectFirst("img.object-cover")?.absUrl("src")
document.selectFirst("p:has(span:contains(Status))")?.ownText()?.let {
status = when (it.lowercase()) {
"ongoing" -> SManga.ONGOING
"complete" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
setUrlWithoutDomain(document.location())
}
// ============================== Chapters ==============================
override fun chapterListSelector() = "section li"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
element.selectFirst("a")!!.let {
name = it.ownText()
setUrlWithoutDomain(it.absUrl("href"))
}
date_upload = parseChapterDate(element.selectFirst("span")?.text())
}
// =============================== Pages ================================
override fun pageListParse(document: Document): List<Page> {
val pages = document.selectFirst("div#json-data")
?.ownText()?.parseAs<List<PageDto>>()
?: throw Exception("Pages not found")
return pages.mapIndexed { index, dto ->
val imageUrl = when {
dto.imageUrl.startsWith("http") -> dto.imageUrl
else -> "https://${dto.imageUrl}"
}
val fragment = json.encodeToString<List<Dialog>>(
dto.dialogues.filter { it.text.isNotBlank() },
)
Page(index, imageUrl = "$imageUrl#$fragment")
}
}
override fun imageUrlParse(document: Document): String = ""
// ============================= Utilities ==============================
private fun parseChapterDate(date: String?): Long {
date ?: return 0
return try { dateFormat.parse(date)!!.time } catch (_: Exception) { parseRelativeDate(date) }
}
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
return when {
date.contains("day", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
date.contains("hour", true) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
date.contains("minute", true) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
date.contains("second", true) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
date.contains("week", true) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis
else -> 0
}
}
private inline fun <reified T> String.parseAs(): T {
return json.decodeFromString(this)
}
// =============================== Filters ================================
override fun getFilterList(): FilterList {
val filters = mutableListOf<Filter<*>>(
SelectionList("Sort", sortByList),
Filter.Separator(),
GenreList(title = "Genres", genres = genreList),
)
return FilterList(filters)
}
companion object {
val PAGE_REGEX = Regex(".*?\\.(webp|png|jpg|jpeg)#\\[.*?]", RegexOption.IGNORE_CASE)
const val PREFIX_SEARCH = "id:"
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.US)
}
}

View File

@ -2,20 +2,18 @@ package eu.kanade.tachiyomi.extension.all.snowmtl
import android.os.Build
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.source.SourceFactory
@RequiresApi(Build.VERSION_CODES.O)
class SnowmtlFactory : SourceFactory {
override fun createSources(): List<Source> = languageList.map(::Snowmtl)
override fun createSources() = languageList.map(::Snowmtl)
}
data class Source(val lang: String, val target: String = lang, val origin: String = "en")
private val languageList = listOf(
Source("en"),
Source("es"),
Source("id"),
Source("it"),
Source("pt-BR", "pt"),
Language("en"),
Language("es"),
Language("id"),
Language("it"),
Language("pt-BR", "pt"),
)

View File

@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.extension.all.snowmtl.interceptors
import android.os.Build
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.extension.all.snowmtl.Dialog
import eu.kanade.tachiyomi.extension.all.snowmtl.Snowmtl.Companion.PAGE_REGEX
import eu.kanade.tachiyomi.extension.all.snowmtl.Source
import eu.kanade.tachiyomi.extension.all.snowmtl.translator.TranslatorEngine
import eu.kanade.tachiyomi.multisrc.machinetranslations.Dialog
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations.Companion.PAGE_REGEX
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@ -15,7 +15,7 @@ import uy.kohesive.injekt.injectLazy
@RequiresApi(Build.VERSION_CODES.O)
class TranslationInterceptor(
private val source: Source,
private val source: Language,
private val translator: TranslatorEngine,
) : Interceptor {
@ -66,7 +66,11 @@ class TranslationInterceptor(
val key = list.first()
val text = list.last()
mapping[key]?.second?.dialog?.copy(text = text)
mapping[key]?.second?.dialog?.copy(
textByLanguage = mapOf(
"text" to text,
),
)
}
/**

View File

@ -0,0 +1,10 @@
ext {
extName = 'Solar Machine Translations'
extClass = '.SolarmtlFactory'
themePkg = 'machinetranslations'
baseUrl = 'https://solarmtl.com'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.all.solarmtl
import android.os.Build
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations
import eu.kanade.tachiyomi.network.interceptor.rateLimit
@RequiresApi(Build.VERSION_CODES.O)
class Solarmtl(
language: Language,
) : MachineTranslations(
name = "Solar Machine Translations",
baseUrl = "https://solarmtl.com",
language,
) {
override val client = super.client.newBuilder()
.rateLimit(2)
.build()
}

View File

@ -0,0 +1,17 @@
package eu.kanade.tachiyomi.extension.all.solarmtl
import android.os.Build
import androidx.annotation.RequiresApi
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
import eu.kanade.tachiyomi.source.SourceFactory
@RequiresApi(Build.VERSION_CODES.O)
class SolarmtlFactory : SourceFactory {
override fun createSources() = languageList.map(::Solarmtl)
}
private val languageList = listOf(
Language("en"),
Language("fr"),
Language("pt-BR", "pt"),
)