From 6071f598f41af4ccd469b8c4c0af64497157c36f Mon Sep 17 00:00:00 2001
From: CriosChan <36739192+CriosChan@users.noreply.github.com>
Date: Tue, 28 Oct 2025 15:36:10 +0100
Subject: [PATCH] BigSolo : Fix extension #11226 (#11233)
* BigSolo: Revert changes of #11156
* BigSolo: Fix for recent backend changes (Closes #11226) + Open link in app
* BigSolo: Change name to "BigSolo" -> override id
* BigSolo: Use enpoint "/data/series/{slug}" + optimization
* BigSolo: Add www subdomain to Manifest
* BigSolo: data class to class + remove unused variables in serializable
* BigSolo: Remove unused variable
* BigSolo: Changes asked by vetleledaal
* BigSolo: Changes asked by AwkwardPeak7
---
src/fr/bigsolo/AndroidManifest.xml | 27 +++
src/fr/bigsolo/build.gradle | 4 +-
.../tachiyomi/extension/fr/bigsolo/BigSolo.kt | 168 +++++++++++++++++-
.../extension/fr/bigsolo/BigSoloDto.kt | 77 ++++++++
.../fr/bigsolo/BigSoloUrlActivity.kt | 38 ++++
5 files changed, 304 insertions(+), 10 deletions(-)
create mode 100644 src/fr/bigsolo/AndroidManifest.xml
create mode 100644 src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloDto.kt
create mode 100644 src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloUrlActivity.kt
diff --git a/src/fr/bigsolo/AndroidManifest.xml b/src/fr/bigsolo/AndroidManifest.xml
new file mode 100644
index 000000000..03a4d85ef
--- /dev/null
+++ b/src/fr/bigsolo/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fr/bigsolo/build.gradle b/src/fr/bigsolo/build.gradle
index 485e9b810..c68cc1018 100644
--- a/src/fr/bigsolo/build.gradle
+++ b/src/fr/bigsolo/build.gradle
@@ -1,9 +1,7 @@
ext {
extName = 'BigSolo'
extClass = '.BigSolo'
- themePkg = 'scanr'
- baseUrl = 'https://www.bigsolo.org'
- overrideVersionCode = 1
+ extVersionCode = 3
isNsfw = false
}
diff --git a/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSolo.kt b/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSolo.kt
index 8ce0c995f..36b1ea8a1 100644
--- a/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSolo.kt
+++ b/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSolo.kt
@@ -1,10 +1,164 @@
package eu.kanade.tachiyomi.extension.fr.bigsolo
-import eu.kanade.tachiyomi.multisrc.scanr.ScanR
+import eu.kanade.tachiyomi.network.GET
+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 keiyoushi.utils.parseAs
+import okhttp3.Request
+import okhttp3.Response
+import java.net.URI
-class BigSolo : ScanR(
- name = "Big Solo",
- baseUrl = "https://bigsolo.org",
- lang = "fr",
- useHighLowQualityCover = true,
-)
+class BigSolo : HttpSource() {
+
+ override val name = "BigSolo"
+ override val baseUrl = "https://bigsolo.org"
+ override val lang = "fr"
+ override val supportsLatest = true
+ override val id = 4410528266393104437
+
+ // Popular
+ override fun popularMangaRequest(page: Int): Request {
+ return GET("$baseUrl/data/series", headers)
+ }
+
+ override fun popularMangaParse(response: Response): MangasPage {
+ val series = response.parseAs()
+ val mangaList = mutableListOf()
+
+ for (serie in series.reco) {
+ mangaList.add(serie.toDetailedSManga())
+ }
+
+ return MangasPage(mangaList, false)
+ }
+
+ // Latest
+ override fun latestUpdatesRequest(page: Int): Request {
+ return GET("$baseUrl/data/series", headers)
+ }
+
+ override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
+
+ // Search
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val url = if (query.isNotBlank()) {
+ "$baseUrl/data/series#$query"
+ } else {
+ "$baseUrl/data/series"
+ }
+ return GET(url, headers)
+ }
+
+ override fun searchMangaParse(response: Response): MangasPage {
+ val series = response.parseAs()
+ val allSeries = (series.series + series.os).sortedByDescending { it.lastChapter?.timestamp }
+ val mangaList = mutableListOf()
+
+ val fragment = response.request.url.fragment
+ val searchQuery = fragment ?: ""
+
+ if (searchQuery.startsWith("SLUG:")) {
+ val serie = allSeries.find { it.slug == searchQuery.removePrefix("SLUG:") }
+ if (serie != null) {
+ mangaList.add(serie.toDetailedSManga())
+ }
+ return MangasPage(mangaList, false)
+ }
+
+ for (serie in allSeries) {
+ if (searchQuery.isBlank() ||
+ serie.title.contains(searchQuery, ignoreCase = true) ||
+ serie.alternativeTitles.any { it.contains(searchQuery, ignoreCase = true) } ||
+ serie.jaTitle.contains(searchQuery, ignoreCase = true)
+ ) {
+ mangaList.add(serie.toDetailedSManga())
+ }
+ }
+
+ return MangasPage(mangaList, false)
+ }
+
+ // Details
+ override fun mangaDetailsRequest(manga: SManga): Request {
+ val splitedPath = URI(manga.url).path.split("/")
+ val slug = splitedPath[1]
+ return GET("$baseUrl/data/series/$slug", headers)
+ }
+
+ override fun getMangaUrl(manga: SManga): String {
+ return "$baseUrl${manga.url}"
+ }
+
+ override fun mangaDetailsParse(response: Response): SManga {
+ val serie = response.parseAs()
+ return serie.toDetailedSManga()
+ }
+
+ // Pages
+ override fun pageListRequest(chapter: SChapter): Request {
+ val splitedPath = URI(chapter.url).path.split("/")
+ val slug = splitedPath[1]
+ val chapterId = splitedPath[2]
+ return GET("$baseUrl/data/series/$slug/$chapterId", headers)
+ }
+
+ override fun pageListParse(response: Response): List {
+ val chapterDetails = response.parseAs()
+ return chapterDetails.images.mapIndexed { index, pageData ->
+ Page(index, imageUrl = pageData)
+ }
+ }
+
+ // Chapters
+ override fun chapterListRequest(manga: SManga): Request {
+ val slug = URI(manga.url).path.split("/")[1]
+ return GET("$baseUrl/data/series/$slug", headers)
+ }
+
+ override fun chapterListParse(response: Response): List {
+ val seriesData = response.parseAs()
+ return buildChapterList(seriesData)
+ }
+ private fun buildChapterList(serie: Serie): List {
+ val chapters = serie.chapters
+ val chapterList = mutableListOf()
+ val multipleChapters = chapters.size > 1
+
+ for ((chapterNumber, chapterData) in chapters) {
+ if (chapterData.licencied) continue
+
+ val title = chapterData.title
+ val volumeNumber = chapterData.volume
+
+ val baseName = if (multipleChapters) {
+ buildString {
+ if (volumeNumber.isNotBlank()) append("Vol. $volumeNumber ")
+ append("Ch. $chapterNumber")
+ if (title.isNotBlank()) append(" – $title")
+ }
+ } else {
+ if (title.isNotBlank()) "One Shot – $title" else "One Shot"
+ }
+
+ val chapter = SChapter.create().apply {
+ name = baseName
+ setUrlWithoutDomain("$baseUrl/${serie.slug}/$chapterNumber")
+ chapter_number = chapterNumber.toFloatOrNull() ?: -1f
+ scanlator = chapterData.teams.joinToString(" & ")
+ date_upload = chapterData.timestamp * 1000L
+ }
+ chapterList.add(chapter)
+ }
+
+ return chapterList.sortedByDescending { it.chapter_number }
+ }
+
+ // Unsupported Stuff
+ override fun imageUrlParse(response: Response): String {
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloDto.kt b/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloDto.kt
new file mode 100644
index 000000000..d5bda3f30
--- /dev/null
+++ b/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloDto.kt
@@ -0,0 +1,77 @@
+package eu.kanade.tachiyomi.extension.fr.bigsolo
+
+import eu.kanade.tachiyomi.source.model.SManga
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+/**
+ * Data Transfer Objects for BigSolo extension
+ */
+
+@Serializable
+class SeriesResponse(
+ val series: List,
+ val os: List,
+ val reco: List,
+)
+
+@Serializable
+class Serie(
+ val slug: String = "",
+ val title: String,
+ val description: String,
+ val artist: String,
+ val author: String,
+ val tags: List,
+ @SerialName("ja_title")
+ val jaTitle: String,
+ @SerialName("alternative_titles")
+ val alternativeTitles: List,
+ val status: String,
+ val cover: Cover? = null,
+ val chapters: Map,
+ @SerialName("last_chapter")
+ val lastChapter: lastChapter? = null,
+)
+
+@Serializable
+class lastChapter(
+ val timestamp: Int,
+)
+
+@Serializable
+class Chapter(
+ val title: String,
+ val volume: String = "",
+ val timestamp: Int,
+ val teams: List,
+ @SerialName("licensed")
+ val licencied: Boolean = false,
+)
+
+@Serializable
+class ChapterDetails(
+ val images: List,
+)
+
+@Serializable
+class Cover(
+ @SerialName("url_hq")
+ val urlHq: String,
+)
+
+// DTO to SManga extension functions
+fun Serie.toDetailedSManga(): SManga = SManga.create().apply {
+ title = this@toDetailedSManga.title
+ description = this@toDetailedSManga.description
+ artist = this@toDetailedSManga.artist
+ author = this@toDetailedSManga.author
+ genre = this@toDetailedSManga.tags.joinToString()
+ status = when (this@toDetailedSManga.status) {
+ "En cours" -> SManga.ONGOING
+ "Finis", "Fini" -> SManga.COMPLETED
+ else -> SManga.UNKNOWN
+ }
+ thumbnail_url = this@toDetailedSManga.cover?.urlHq
+ url = "/${this@toDetailedSManga.slug}"
+}
diff --git a/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloUrlActivity.kt b/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloUrlActivity.kt
new file mode 100644
index 000000000..6a3fbf9d1
--- /dev/null
+++ b/src/fr/bigsolo/src/eu/kanade/tachiyomi/extension/fr/bigsolo/BigSoloUrlActivity.kt
@@ -0,0 +1,38 @@
+package eu.kanade.tachiyomi.extension.fr.bigsolo
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import kotlin.system.exitProcess
+
+/**
+ * Springboard that accepts https://bigsolo.org/xxxxxx intents and redirects them to
+ * the main Tachiyomi process.
+ */
+class BigSoloUrlActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size >= 1) {
+ val slug = pathSegments[0]
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.SEARCH"
+ putExtra("query", "SLUG:$slug")
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("BigSoloUrlActivity", e.toString())
+ }
+ } else {
+ Log.e("BigSoloUrlActivity", "could not parse uri from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}