diff --git a/.github/workflows/issue_closer.yml b/.github/workflows/issue_closer.yml index d0f7673e1..f926f59d4 100644 --- a/.github/workflows/issue_closer.yml +++ b/.github/workflows/issue_closer.yml @@ -38,7 +38,7 @@ jobs: }, { "type": "both", - "regex": ".*(teamx|tqneplus|manga\\s*disk|komiktap|gourmet\\s*scans|manga\\s*crimson|mangawow|voidscans|hikari\\s*scans|mangagegecesi|piedpiperfb|knightnoscanlations|ahstudios|mangagecesi|flamescans|nartag|xxx\\s*yaoi|yaoi\\s*fan\\s*clube).*", + "regex": ".*(teamx|tqneplus|manga\\s*disk|komiktap|gourmet\\s*scans|manga\\s*crimson|mangawow|voidscans|hikari\\s*scans|mangagegecesi|piedpiperfb|knightnoscanlations|ahstudios|mangagecesi|nartag|xxx\\s*yaoi|yaoi\\s*fan\\s*clube).*", "ignoreCase": true, "message": "{match} will not be added back as the Scanlator team has requested it to be removed. Read #3475 for more information" } diff --git a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index fd380e7a7..000000000 Binary files a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 4efc74758..000000000 Binary files a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 137566eb1..000000000 Binary files a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 668ffece9..000000000 Binary files a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4ea8becd3..000000000 Binary files a/multisrc/overrides/wpmangareader/arflamescans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/multisrc/overrides/wpmangareader/arflamescans/res/web_hi_res_512.png b/multisrc/overrides/wpmangareader/arflamescans/res/web_hi_res_512.png deleted file mode 100644 index ed6b35e1e..000000000 Binary files a/multisrc/overrides/wpmangareader/arflamescans/res/web_hi_res_512.png and /dev/null differ diff --git a/multisrc/overrides/wpmangareader/arflamescans/src/ARFlameScans.kt b/multisrc/overrides/wpmangareader/arflamescans/src/ARFlameScans.kt deleted file mode 100644 index 030f1570b..000000000 --- a/multisrc/overrides/wpmangareader/arflamescans/src/ARFlameScans.kt +++ /dev/null @@ -1,130 +0,0 @@ -package eu.kanade.tachiyomi.extension.ar.arflamescans - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.multisrc.wpmangareader.WPMangaReader -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Protocol -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import org.jsoup.nodes.Document -import java.io.ByteArrayOutputStream -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit - -class ARFlameScans : WPMangaReader( - "AR FlameScans", - "https://ar.flamescans.org", - "ar", - mangaUrlDirectory = "/series", - dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US) -) { - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addInterceptor(::composedImageIntercept) - .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) - .build() - - override fun parseStatus(status: String) = when { - status.contains("مستمر") -> SManga.ONGOING - status.contains("مكتمل") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - private val composedSelector: String = "#readerarea div.figure_container div.composed_figure" - - override fun pageListParse(document: Document): List { - val hasSplitImages = document - .select(composedSelector) - .firstOrNull() != null - - if (!hasSplitImages) { - return super.pageListParse(document) - } - - return document.select("#readerarea p:has(img), $composedSelector") - .filter { - it.select("img").all { imgEl -> - imgEl.attr("abs:src").isNullOrEmpty().not() - } - } - .mapIndexed { i, el -> - if (el.tagName() == "p") { - Page(i, "", el.select("img").attr("abs:src")) - } else { - val imageUrls = el.select("img") - .joinToString("|") { it.attr("abs:src") } - - Page(i, "", imageUrls + COMPOSED_SUFFIX) - } - } - } - - private fun composedImageIntercept(chain: Interceptor.Chain): Response { - if (!chain.request().url.toString().endsWith(COMPOSED_SUFFIX)) { - return chain.proceed(chain.request()) - } - - val imageUrls = chain.request().url.toString() - .removeSuffix(COMPOSED_SUFFIX) - .split("%7C") - - var width = 0 - var height = 0 - - val imageBitmaps = imageUrls.map { imageUrl -> - val request = chain.request().newBuilder().url(imageUrl).build() - val response = chain.proceed(request) - - val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream()) - - width += bitmap.width - height = bitmap.height - - bitmap - } - - val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(result) - - var left = 0 - - imageBitmaps.forEach { bitmap -> - val srcRect = Rect(0, 0, bitmap.width, bitmap.height) - val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) - - canvas.drawBitmap(bitmap, srcRect, dstRect, null) - - left += bitmap.width - } - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - - val responseBody = output.toByteArray().toResponseBody(MEDIA_TYPE) - - return Response.Builder() - .code(200) - .protocol(Protocol.HTTP_1_1) - .request(chain.request()) - .message("OK") - .body(responseBody) - .build() - } - - companion object { - - private const val COMPOSED_SUFFIX = "?comp" - private val MEDIA_TYPE = "image/png".toMediaType() - } -} diff --git a/multisrc/overrides/wpmangareader/flamescans/additional.gradle.kts b/multisrc/overrides/wpmangareader/flamescans/additional.gradle.kts new file mode 100644 index 000000000..27b5b96f7 --- /dev/null +++ b/multisrc/overrides/wpmangareader/flamescans/additional.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + implementation project(':lib-ratelimit') +} diff --git a/multisrc/overrides/wpmangareader/flamescans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..4c8864dfb Binary files /dev/null and b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/wpmangareader/flamescans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..faa9e822c Binary files /dev/null and b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..d0f74de29 Binary files /dev/null and b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..470aa668b Binary files /dev/null and b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..7d1e9008b Binary files /dev/null and b/multisrc/overrides/wpmangareader/flamescans/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/wpmangareader/flamescans/res/web_hi_res_512.png b/multisrc/overrides/wpmangareader/flamescans/res/web_hi_res_512.png new file mode 100644 index 000000000..ccbde4a5b Binary files /dev/null and b/multisrc/overrides/wpmangareader/flamescans/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/wpmangareader/flamescans/src/FlameScans.kt b/multisrc/overrides/wpmangareader/flamescans/src/FlameScans.kt new file mode 100644 index 000000000..e36682d8d --- /dev/null +++ b/multisrc/overrides/wpmangareader/flamescans/src/FlameScans.kt @@ -0,0 +1,254 @@ +package eu.kanade.tachiyomi.extension.all.flamescans + +import android.app.Application +import android.content.SharedPreferences +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Rect +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.multisrc.wpmangareader.WPMangaReader +import eu.kanade.tachiyomi.source.ConfigurableSource +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 okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import org.jsoup.nodes.Document +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.ByteArrayOutputStream +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +open class FlameScans( + override val baseUrl: String, + override val lang: String, + mangaUrlDirectory: String, + dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US) +) : WPMangaReader( + "Flame Scans", + baseUrl, + lang, + mangaUrlDirectory = mangaUrlDirectory, + dateFormat = dateFormat + ), + ConfigurableSource { + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(::composedImageIntercept) + .build() + + // Split Image Fixer Start + private val composedSelector: String = "#readerarea div.figure_container div.composed_figure" + + override fun pageListParse(document: Document): List { + val hasSplitImages = document + .select(composedSelector) + .firstOrNull() != null + + if (!hasSplitImages) { + return super.pageListParse(document) + } + + return document.select("#readerarea p:has(img), $composedSelector") + .filter { + it.select("img").all { imgEl -> + imgEl.attr("abs:src").isNullOrEmpty().not() + } + } + .mapIndexed { i, el -> + if (el.tagName() == "p") { + Page(i, "", el.select("img").attr("abs:src")) + } else { + val imageUrls = el.select("img") + .joinToString("|") { it.attr("abs:src") } + + Page(i, "", imageUrls + COMPOSED_SUFFIX) + } + } + } + + private fun composedImageIntercept(chain: Interceptor.Chain): Response { + if (!chain.request().url.toString().endsWith(COMPOSED_SUFFIX)) { + return chain.proceed(chain.request()) + } + + val imageUrls = chain.request().url.toString() + .removeSuffix(COMPOSED_SUFFIX) + .split("%7C") + + var width = 0 + var height = 0 + + val imageBitmaps = imageUrls.map { imageUrl -> + val request = chain.request().newBuilder().url(imageUrl).build() + val response = chain.proceed(request) + + val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream()) + + width += bitmap.width + height = bitmap.height + + bitmap + } + + val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(result) + + var left = 0 + + imageBitmaps.forEach { bitmap -> + val srcRect = Rect(0, 0, bitmap.width, bitmap.height) + val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) + + canvas.drawBitmap(bitmap, srcRect, dstRect, null) + + left += bitmap.width + } + + val output = ByteArrayOutputStream() + result.compress(Bitmap.CompressFormat.PNG, 100, output) + + val responseBody = output.toByteArray().toResponseBody(MEDIA_TYPE) + + return Response.Builder() + .code(200) + .protocol(Protocol.HTTP_1_1) + .request(chain.request()) + .message("OK") + .body(responseBody) + .build() + } + // Split Image Fixer End + + override fun fetchPopularManga(page: Int): Observable { + return super.fetchPopularManga(page).tempUrlToPermIfNeeded() + } + + override fun fetchLatestUpdates(page: Int): Observable { + return super.fetchLatestUpdates(page).tempUrlToPermIfNeeded() + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded() + } + + private fun Observable.tempUrlToPermIfNeeded(): Observable { + return this.map { mangasPage -> + MangasPage( + mangasPage.mangas.map { it.tempUrlToPermIfNeeded() }, + mangasPage.hasNextPage + ) + } + } + + private fun SManga.tempUrlToPermIfNeeded(): SManga { + val sManga = this + + val turnTempUrlToPerm = preferences.getBoolean(getPermanentMangaUrlPreferenceKey(), false) + if (!turnTempUrlToPerm) return sManga + + val sMangaUrl = sManga.url + val sMangaTitleFirstWord = sManga.title.split(" ")[0] + return if (sMangaUrl.startsWith("$mangaUrlDirectory/$sMangaTitleFirstWord")) { + sManga + } else { + sManga.url = sMangaUrl.replaceFirst(TEMP_TO_PERM_URL_REGEX, "$1") + sManga + } + } + + override fun fetchChapterList(manga: SManga): Observable> { + return super.fetchChapterList(manga).map { sChapterList -> + sChapterList.map { it.tempUrlToPermIfNeeded() } + } + } + + private fun SChapter.tempUrlToPermIfNeeded(): SChapter { + val sChapter = this + + val turnTempUrlToPerm = preferences.getBoolean(getPermanentChapterUrlPreferenceKey(), false) + if (!turnTempUrlToPerm) return sChapter + + val sChapterUrl = sChapter.url + val sChapterNameFirstWord = sChapter.name.split(" ")[0] + return if (sChapterUrl.startsWith("/$sChapterNameFirstWord")) { + sChapter + } else { + sChapter.url = sChapterUrl.replaceFirst(TEMP_TO_PERM_URL_REGEX, "$1") + sChapter + } + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val permanentMangaUrlPref = SwitchPreferenceCompat(screen.context).apply { + key = getPermanentMangaUrlPreferenceKey() + title = PREF_PERM_MANGA_URL_TITLE + summary = PREF_PERM_MANGA_URL_SUMMARY + setDefaultValue(false) + + setOnPreferenceChangeListener { _, newValue -> + val checkValue = newValue as Boolean + preferences.edit() + .putBoolean(getPermanentMangaUrlPreferenceKey(), checkValue) + .commit() + } + } + val permanentChapterUrlPref = SwitchPreferenceCompat(screen.context).apply { + key = getPermanentChapterUrlPreferenceKey() + title = PREF_PERM_CHAPTER_URL_TITLE + summary = PREF_PERM_CHAPTER_URL_SUMMARY + setDefaultValue(false) + + setOnPreferenceChangeListener { _, newValue -> + val checkValue = newValue as Boolean + preferences.edit() + .putBoolean(getPermanentChapterUrlPreferenceKey(), checkValue) + .commit() + } + } + screen.addPreference(permanentMangaUrlPref) + screen.addPreference(permanentChapterUrlPref) + } + + private fun getPermanentMangaUrlPreferenceKey(): String { + return PREF_PERM_MANGA_URL_KEY_PREFIX + lang + } + + private fun getPermanentChapterUrlPreferenceKey(): String { + return PREF_PERM_CHAPTER_URL_KEY_PREFIX + lang + } + // Permanent Url for Manga/Chapter End + + companion object { + private const val COMPOSED_SUFFIX = "?comp" + + private const val PREF_PERM_MANGA_URL_KEY_PREFIX = "pref_permanent_manga_url_" + private const val PREF_PERM_MANGA_URL_TITLE = "Permanent Manga Url" + private const val PREF_PERM_MANGA_URL_SUMMARY = "Turns all manga urls into permanent ones." + + private const val PREF_PERM_CHAPTER_URL_KEY_PREFIX = "pref_permanent_chapter_url" + private const val PREF_PERM_CHAPTER_URL_TITLE = "Permanent Chapter URl" + private const val PREF_PERM_CHAPTER_URL_SUMMARY = "Turns all chapter urls into permanent one." + + private val TEMP_TO_PERM_URL_REGEX = Regex("""(/)\d+-""") + + private val MEDIA_TYPE = "image/png".toMediaType() + } +} diff --git a/multisrc/overrides/wpmangareader/flamescans/src/FlameScansFactory.kt b/multisrc/overrides/wpmangareader/flamescans/src/FlameScansFactory.kt new file mode 100644 index 000000000..b33941da4 --- /dev/null +++ b/multisrc/overrides/wpmangareader/flamescans/src/FlameScansFactory.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.all.flamescans + +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit + +class FlameScansFactory : SourceFactory { + override fun createSources() = listOf( + FlameScansAr(), + FlameScansEn() + ) +} + +class FlameScansAr : FlameScans("https://ar.flamescans.org", "ar", "/series") { + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS)) + .build() + + override val id: Long = 6053688312544266540 + + override fun parseStatus(status: String) = when { + status.contains("مستمر") -> SManga.ONGOING + status.contains("مكتمل") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } +} + +class FlameScansEn : FlameScans("https://flamescans.org", "en", "/series") { + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor(RateLimitInterceptor(1, 3, TimeUnit.SECONDS)) + .build() +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt index 76748a0ba..480512e50 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.multisrc.wpmangareader +import generator.ThemeSourceData.MultiLang import generator.ThemeSourceData.SingleLang import generator.ThemeSourceGenerator @@ -12,9 +13,9 @@ class WPMangaReaderGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 11 override val sources = listOf( + MultiLang("Flame Scans", "https://flamescans.org", listOf("ar", "en"), className = "FlameScansFactory", pkgName = "flamescans"), SingleLang("Anitation Arts", "https://anitationarts.org", "en", overrideVersionCode = 1), SingleLang("Alpha Scans", "https://alpha-scans.org", "en"), - SingleLang("AR FlameScans", "https://ar.flamescans.org", "ar", overrideVersionCode = 1), SingleLang("BeastScans", "https://beastscans.com", "en"), SingleLang("iiMANGA", "https://iimanga.com", "ar"), SingleLang("Magus Manga", "https://magusmanga.com", "ar"),