Add SadScans and make APairOf2 a multisrc (#1046)
* Add SadScans and make APairOf2 a multisrc * Formatting * Trailing comma * optimize icons * newline --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
| After Width: | Height: | Size: 2.2 KiB | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 2.8 KiB | 
| After Width: | Height: | Size: 5.2 KiB | 
| After Width: | Height: | Size: 7.6 KiB | 
							
								
								
									
										12
									
								
								multisrc/overrides/po2scans/apairof2/src/APairOf2.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | ||||
| package eu.kanade.tachiyomi.extension.en.apairof2 | ||||
| 
 | ||||
| import eu.kanade.tachiyomi.multisrc.po2scans.PO2Scans | ||||
| import eu.kanade.tachiyomi.network.interceptor.rateLimit | ||||
| 
 | ||||
| class APairOf2 : PO2Scans("A Pair Of 2+", "https://po2scans.com", "en") { | ||||
|     override val versionId = 2 | ||||
| 
 | ||||
|     override val client = super.client.newBuilder() | ||||
|         .rateLimit(4) | ||||
|         .build() | ||||
| } | ||||
| @ -3,7 +3,7 @@ | ||||
| 
 | ||||
|     <application> | ||||
|         <activity | ||||
|             android:name=".en.apairof2.APairOf2UrlActivity" | ||||
|             android:name="eu.kanade.tachiyomi.multisrc.po2scans.PO2ScansUrlActivity" | ||||
|             android:excludeFromRecents="true" | ||||
|             android:exported="true" | ||||
|             android:theme="@android:style/Theme.NoDisplay"> | ||||
| @ -14,9 +14,9 @@ | ||||
|                 <category android:name="android.intent.category.BROWSABLE" /> | ||||
| 
 | ||||
|                 <data | ||||
|                     android:host="po2scans.com" | ||||
|                     android:host="${SOURCEHOST}" | ||||
|                     android:pathPattern="/series/..*" | ||||
|                     android:scheme="https" /> | ||||
|                     android:scheme="${SOURCESCHEME}" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|     </application> | ||||
							
								
								
									
										3
									
								
								multisrc/overrides/po2scans/default/additional.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | ||||
| dependencies { | ||||
|     implementation(project(":lib:dataimage")) | ||||
| } | ||||
| After Width: | Height: | Size: 1.5 KiB | 
| After Width: | Height: | Size: 816 B | 
| After Width: | Height: | Size: 2.3 KiB | 
| After Width: | Height: | Size: 4.4 KiB | 
| After Width: | Height: | Size: 6.5 KiB | 
| @ -1,80 +1,64 @@ | ||||
| package eu.kanade.tachiyomi.extension.en.apairof2 | ||||
| package eu.kanade.tachiyomi.multisrc.po2scans | ||||
| 
 | ||||
| import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor | ||||
| import eu.kanade.tachiyomi.lib.dataimage.dataImageAsUrl | ||||
| 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.ParsedHttpSource | ||||
| import okhttp3.Headers | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import rx.Observable | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.Locale | ||||
| 
 | ||||
| class APairOf2 : ParsedHttpSource() { | ||||
| 
 | ||||
|     override val name = "A Pair of 2+" | ||||
| 
 | ||||
|     override val baseUrl = "https://po2scans.com" | ||||
| 
 | ||||
|     override val lang = "en" | ||||
| abstract class PO2Scans( | ||||
|     override val name: String, | ||||
|     override val baseUrl: String, | ||||
|     override val lang: String, | ||||
|     private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM, yy", Locale.ENGLISH), | ||||
| ) : ParsedHttpSource() { | ||||
| 
 | ||||
|     override val supportsLatest = true | ||||
| 
 | ||||
|     override val versionId = 2 | ||||
| 
 | ||||
|     override val client: OkHttpClient = network.cloudflareClient.newBuilder() | ||||
|     override val client = network.cloudflareClient.newBuilder() | ||||
|         .addInterceptor(DataImageInterceptor()) | ||||
|         .rateLimit(4) | ||||
|         .build() | ||||
| 
 | ||||
|     override fun headersBuilder(): Headers.Builder = super.headersBuilder() | ||||
|     override fun headersBuilder() = super.headersBuilder() | ||||
|         .add("Referer", "$baseUrl/") | ||||
| 
 | ||||
|     // popular | ||||
|     override fun popularMangaRequest(page: Int): Request { | ||||
|         return GET("$baseUrl/series", headers) | ||||
|     } | ||||
|     override fun popularMangaRequest(page: Int) = GET("$baseUrl/series", headers) | ||||
| 
 | ||||
|     override fun popularMangaSelector() = "div.series-list" | ||||
| 
 | ||||
|     override fun popularMangaFromElement(element: Element): SManga { | ||||
|         return SManga.create().apply { | ||||
|             title = element.select("div > h2").text() | ||||
|             url = element.select("div > a").attr("href").let { "/$it" } | ||||
|             thumbnail_url = element.select("img").attr("abs:data-src") | ||||
|         } | ||||
|     override fun popularMangaFromElement(element: Element) = SManga.create().apply { | ||||
|         setUrlWithoutDomain(element.selectFirst("div > a")!!.absUrl("href")) | ||||
|         title = element.selectFirst("div > h2")!!.text() | ||||
|         thumbnail_url = element.selectFirst("img")?.absUrl("data-src") | ||||
|     } | ||||
| 
 | ||||
|     // TODO: add page selectors & url parameters when site have enough series for pagination | ||||
|     override fun popularMangaNextPageSelector() = null | ||||
| 
 | ||||
|     // latest | ||||
|     override fun latestUpdatesRequest(page: Int): Request { | ||||
|         return GET(baseUrl, headers) | ||||
|     } | ||||
|     override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) | ||||
| 
 | ||||
|     override fun latestUpdatesSelector() = "div.chap" | ||||
| 
 | ||||
|     override fun latestUpdatesFromElement(element: Element): SManga { | ||||
|         return SManga.create().apply { | ||||
|             element.select("div.chap-title a").let { it -> | ||||
|                 url = it.attr("href").let { "/$it" } | ||||
|                 title = it.text() | ||||
|             } | ||||
|             thumbnail_url = element.select("img").attr("abs:data-src") | ||||
|     override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { | ||||
|         element.selectFirst("div.chap-title a")!!.let { | ||||
|             setUrlWithoutDomain(it.absUrl("href")) | ||||
|             title = it.text() | ||||
|         } | ||||
|         thumbnail_url = element.selectFirst("img")?.absUrl("data-src") | ||||
|     } | ||||
| 
 | ||||
|     override fun latestUpdatesNextPageSelector() = popularMangaSelector() | ||||
|     override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() | ||||
| 
 | ||||
|     // search | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { | ||||
| @ -83,15 +67,15 @@ class APairOf2 : ParsedHttpSource() { | ||||
|         } | ||||
| 
 | ||||
|         val url = "/series/${query.substringAfter(SLUG_SEARCH_PREFIX)}" | ||||
|         return fetchMangaDetails(SManga.create().apply { this.url = url }).map { | ||||
|             it.url = url | ||||
|             MangasPage(listOf(it), false) | ||||
|         } | ||||
|         return fetchMangaDetails(SManga.create().apply { this.url = url }) | ||||
|             .map { | ||||
|                 it.url = url | ||||
|                 MangasPage(listOf(it), false) | ||||
|             } | ||||
|     } | ||||
| 
 | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         return GET("$baseUrl/series?search=$query", headers) | ||||
|     } | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = | ||||
|         GET("$baseUrl/series?search=$query", headers) | ||||
| 
 | ||||
|     override fun searchMangaSelector() = popularMangaSelector() | ||||
| 
 | ||||
| @ -111,39 +95,33 @@ class APairOf2 : ParsedHttpSource() { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun String.parseStatus(): Int { | ||||
|         return when { | ||||
|             this.contains("ongoing", true) -> SManga.ONGOING | ||||
|             this.contains("complete", true) -> SManga.COMPLETED | ||||
|             else -> SManga.UNKNOWN | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // chapter list | ||||
|     override fun chapterListSelector() = "div.chap" | ||||
| 
 | ||||
|     override fun chapterFromElement(element: Element): SChapter { | ||||
|         return SChapter.create().apply { | ||||
|             element.select("a").let { a -> | ||||
|                 url = a.attr("href").let { "/$it" } | ||||
|                 name = a.text() | ||||
|             } | ||||
|             date_upload = parseDate(element.select("div > div > span:nth-child(2)").text()) | ||||
|     override fun chapterFromElement(element: Element) = SChapter.create().apply { | ||||
|         element.selectFirst("a")!!.let { | ||||
|             setUrlWithoutDomain(it.absUrl("href")) | ||||
|             name = it.text() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun parseDate(dateStr: String): Long { | ||||
|         return runCatching { DATE_FORMATTER.parse(dateStr)?.time } | ||||
|             .getOrNull() ?: 0L | ||||
|         date_upload = parseDate(element.select("div > div > span:nth-child(2)").text()) | ||||
|     } | ||||
| 
 | ||||
|     // page list | ||||
|     override fun pageListParse(document: Document): List<Page> { | ||||
|         return document.select(".swiper-slide img").mapIndexed { index, img -> | ||||
|             Page( | ||||
|                 index = index, | ||||
|                 imageUrl = img.imgAttr(), | ||||
|             ) | ||||
|     override fun pageListParse(document: Document) = | ||||
|         document.select(".swiper-slide img").mapIndexed { index, img -> | ||||
|             Page(index, imageUrl = img.imgAttr()) | ||||
|         } | ||||
| 
 | ||||
|     override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() | ||||
| 
 | ||||
|     private val statusOngoing = listOf("ongoing", "devam ediyor") | ||||
|     private val statusCompleted = listOf("complete", "tamamlandı", "bitti") | ||||
| 
 | ||||
|     private fun String.parseStatus(): Int { | ||||
|         return when (this.lowercase()) { | ||||
|             in statusOngoing -> SManga.ONGOING | ||||
|             in statusCompleted -> SManga.COMPLETED | ||||
|             else -> SManga.UNKNOWN | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -153,15 +131,11 @@ class APairOf2 : ParsedHttpSource() { | ||||
|         else -> dataImageAsUrl("src") | ||||
|     } | ||||
| 
 | ||||
|     override fun imageUrlParse(document: Document): String { | ||||
|         throw UnsupportedOperationException() | ||||
|     } | ||||
|     private fun parseDate(dateStr: String) = | ||||
|         runCatching { dateFormat.parse(dateStr)!!.time } | ||||
|             .getOrDefault(0L) | ||||
| 
 | ||||
|     companion object { | ||||
|         private val DATE_FORMATTER by lazy { | ||||
|             SimpleDateFormat("dd MMMM, yy", Locale.ENGLISH) | ||||
|         } | ||||
| 
 | ||||
|         const val SLUG_SEARCH_PREFIX = "slug:" | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,24 @@ | ||||
| package eu.kanade.tachiyomi.multisrc.po2scans | ||||
| 
 | ||||
| import generator.ThemeSourceData.SingleLang | ||||
| import generator.ThemeSourceGenerator | ||||
| 
 | ||||
| class PO2ScansGenerator : ThemeSourceGenerator { | ||||
|     override val themePkg = "po2scans" | ||||
| 
 | ||||
|     override val themeClass = "PO2Scans" | ||||
| 
 | ||||
|     override val baseVersionCode = 1 | ||||
| 
 | ||||
|     override val sources = listOf( | ||||
|         SingleLang("A Pair Of 2+", "https://po2scans.com", "en", className = "APairOf2", overrideVersionCode = 31), | ||||
|         SingleLang("Sadscans", "https://sadscans.com", "tr"), | ||||
|     ) | ||||
| 
 | ||||
|     companion object { | ||||
|         @JvmStatic | ||||
|         fun main(args: Array<String>) { | ||||
|             PO2ScansGenerator().createAll() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package eu.kanade.tachiyomi.extension.en.apairof2 | ||||
| package eu.kanade.tachiyomi.multisrc.po2scans | ||||
| 
 | ||||
| import android.app.Activity | ||||
| import android.content.ActivityNotFoundException | ||||
| @ -7,7 +7,7 @@ import android.os.Bundle | ||||
| import android.util.Log | ||||
| import kotlin.system.exitProcess | ||||
| 
 | ||||
| class APairOf2UrlActivity : Activity() { | ||||
| class PO2ScansUrlActivity : Activity() { | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         val pathSegments = intent?.data?.pathSegments | ||||
| @ -15,17 +15,17 @@ class APairOf2UrlActivity : Activity() { | ||||
|             val slug = pathSegments[1] | ||||
|             val mainIntent = Intent().apply { | ||||
|                 action = "eu.kanade.tachiyomi.SEARCH" | ||||
|                 putExtra("query", "${APairOf2.SLUG_SEARCH_PREFIX}$slug") | ||||
|                 putExtra("query", "${PO2Scans.SLUG_SEARCH_PREFIX}$slug") | ||||
|                 putExtra("filter", packageName) | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 startActivity(mainIntent) | ||||
|             } catch (e: ActivityNotFoundException) { | ||||
|                 Log.e("APairOf2UrlActivity", e.toString()) | ||||
|                 Log.e("PO2ScansUrlActivity", "Could not start activity", e) | ||||
|             } | ||||
|         } else { | ||||
|             Log.e("APairOf2UrlActivity", "could not parse uri from intent $intent") | ||||
|             Log.e("PO2ScansUrlActivity", "could not parse URI from intent $intent") | ||||
|         } | ||||
| 
 | ||||
|         finish() | ||||
| @ -1,11 +0,0 @@ | ||||
| ext { | ||||
|     extName = 'A Pair of 2+' | ||||
|     extClass = '.APairOf2' | ||||
|     extVersionCode = 31 | ||||
| } | ||||
| 
 | ||||
| apply from: "$rootDir/common.gradle" | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation project(':lib:dataimage') | ||||
| } | ||||
| Before Width: | Height: | Size: 2.9 KiB | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 4.0 KiB | 
| Before Width: | Height: | Size: 7.4 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
 beerpsi
						beerpsi