From 0cf5ec6bae5d12baa94061b1b03a0288b34c1ad4 Mon Sep 17 00:00:00 2001
From: Claudemirovsky <63046606+Claudemirovsky@users.noreply.github.com>
Date: Mon, 18 Jul 2022 12:27:20 -0300
Subject: [PATCH] New source: Puray.moe (#669)
---
src/pt/puraymoe/AndroidManifest.xml | 24 ++
src/pt/puraymoe/build.gradle | 18 +
.../puraymoe/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 693 bytes
.../puraymoe/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 538 bytes
.../puraymoe/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 862 bytes
.../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1191 bytes
.../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1556 bytes
.../pt/puraymoe/PMUrlActivity.kt | 42 +++
.../animeextension/pt/puraymoe/PurayMoe.kt | 324 ++++++++++++++++++
.../pt/puraymoe/dto/PurayMoeDto.kt | 82 +++++
10 files changed, 490 insertions(+)
create mode 100644 src/pt/puraymoe/AndroidManifest.xml
create mode 100644 src/pt/puraymoe/build.gradle
create mode 100644 src/pt/puraymoe/res/mipmap-hdpi/ic_launcher.png
create mode 100644 src/pt/puraymoe/res/mipmap-mdpi/ic_launcher.png
create mode 100644 src/pt/puraymoe/res/mipmap-xhdpi/ic_launcher.png
create mode 100644 src/pt/puraymoe/res/mipmap-xxhdpi/ic_launcher.png
create mode 100644 src/pt/puraymoe/res/mipmap-xxxhdpi/ic_launcher.png
create mode 100644 src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PMUrlActivity.kt
create mode 100644 src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PurayMoe.kt
create mode 100644 src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/dto/PurayMoeDto.kt
diff --git a/src/pt/puraymoe/AndroidManifest.xml b/src/pt/puraymoe/AndroidManifest.xml
new file mode 100644
index 000000000..eab6844c7
--- /dev/null
+++ b/src/pt/puraymoe/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pt/puraymoe/build.gradle b/src/pt/puraymoe/build.gradle
new file mode 100644
index 000000000..8b9053aa8
--- /dev/null
+++ b/src/pt/puraymoe/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'Puray.moe'
+ pkgNameSuffix = 'pt.puraymoe'
+ extClass = '.PurayMoe'
+ extVersionCode = 1
+ libVersion = '12'
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+}
+
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/pt/puraymoe/res/mipmap-hdpi/ic_launcher.png b/src/pt/puraymoe/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..6a0309e94974f5837c1b8c30fc39f686973de313
GIT binary patch
literal 693
zcmeAS@N?(olHy`uVBq!ia0vp^9w5vCBp3?X-pmG4Ea{HEjtmSN`?>!lvI6-E$sR$z
z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4~+Y|Ldnl*^^+GH#C4RW2xTh&pS0oFjQNL;ojc>F_EhP8an<|Z
zb?<-gd;fhIqXZ9I^TC8aj25w1bK1X~-*K*r)s}1vt(j%inv}DRrBLR0p-fS9)Z3S@
z84j46-uU*l)~8o^QS`UlzrsK5cAf8M7#PV3^#0Bhif3Qn-?#R7nAz_I+e1FO#mjtg
z|9vD=sH8T@JucJGvoi31!EME#K`LKcxsC6eZ``r^L1oAe;}=OsXRE(0UNikf)TR#}
z+e`O&Jz=i(dHUyCW`oqTh`me<*+q$|aWgpjgN;r!`|z9A`PvD$^6x2QVz4i9E-_TP
zP`9NjJV7Ztea`KbqQSeed+k=BZDXDTjQQ>pef@TR`|rZn#}mnZxE
z_#yKC-TFTn(2y;+V_?{^z@slZ_XIF*RZCnWN>UO_QmvAUQh^kMk%5tsu7SC(p+$&+
zv6YE|m9eq5fq|8Qfu3;@JBo(f{FKbJN~i`CT_d0dLn~7=Dp%?*p00i_
I>zopr0BedCUjP6A
literal 0
HcmV?d00001
diff --git a/src/pt/puraymoe/res/mipmap-mdpi/ic_launcher.png b/src/pt/puraymoe/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..c0b840f45ce1407604f6c20c101452af20badaf3
GIT binary patch
literal 538
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZANS%G|oWRD45dJguM!v-tY$DUh!@P+6=(yLU`q0KcVYP7-hXC4kjGx)
z>Fdh=j9rLXN74BCl}QW?j7pv^jv*e$lM^IZ7bl2Z;5S(OLE`M!`F{?s>U(Lwip?#q
z3<$z{QzEa;=HW?NxIcdJD)H6-e+nE*Y7DqzZ1Ug8;Lr1)HNT9RnVVDMZ-$C|?fR>C
zoqu7*kK#uk?b+C#8T6&9OGrrk`}4DV&78ZN{Zx`I&DyzQW6vA_f(3uICN=`W^j09@
zc%%OC_wX`~^9tDo;WwI&1jjx&ao|8ySpwt#qu5%{_kDJWHum3b7O1!XDuYLDpF_nd|RHzqA&OE)>bZe
zV$rjFsOQAY-Sbd%|Kj&dQ~v(aIRDF+UtrnapQk!rD%kOcv9!;ZcJ+&!-kT`W(-df+
z!oU!}(l2Q0#Xnzx0jOHy8c~vxSdwa$T$Bo=7>o>zjC2jmbqy^-42-Qz46KZewG9lc
y3=H&)i`Y>#SkfJR9T^xl_H+M9WCijSl0AZa
z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{Xiaj
ziKnkC`!jYSW*wzf?^-p0@@bwfjv*QM-cF762u_qZKEKxb^rcis?P(f8T1(QxCU+S|
z#gzn$s|JRrDx|1BX%!PJ6=YF#5;(GGcV3hk=U1f(S6iR@ZnJaeZdOg}F>?HUUbZCb(KGM4tC>6hZ~wb>
z-}QO<=|<6pGkaz&otE=jH&|c8Fv~Ra#Me^uoyUDOPg;JfSWwdW@9X)ax2N9Jto_y)
zdqdFKs$uOK#TZllB~FvQ*9%_xz5U)TzxMb2t3zJ2O>nJwcK6#xNX8*sGv
zKe_Ux<>JnxXNogfkFK-(sS(Kd^jpfycPkh0?^RwUXF7Y^l_e{8@l~i^-=f>seyaT3
z4Ldz2nd~#dn`9MKIM=wGchTw)`n{5Q^^4~Z5|(VJQc~K)D!A4Akq-N3Vh+POPhlbUS$+By34T#K
zH5q;wU%V-I@bn^?lDCTvY}m8qQ%SsK>tu5;8QaWxwXZ&&H4iG3`N_9XU3+rjg(sd4
zJ0{m#<(H{VSNTs;`KhM>6Z&ekK8%
zdG(uIc{Y^%U`beIs-D~b`0QPOhNO2^Gedn@`3$Vq`RFk{%BWrUD29RI&xc(+MclKM
zXJ};{;ANPxb4L8@9)_e7Z%t0?-;r#t_jP11@t+}gLreKZAFJE0EYbf9`(}K&r#1Qf
zfA_WbKPN@)YtPMdtW16#wPnA0Xh~b51QbQG{bA)l^flI`qOBH~2vtj5BT7;dOH!?p
zi&B9UgOP!ek*8-29Zxv`VN36I~;q216@T
ZGbRNbWH_hC*!o-xlb;e&PJsjGxyVT^DT&
zziJ;R`*D5pAF(ot`>X0I%q5&Q-q^w!8=o9<`Sz6;ti4y6j~s0gX+JEuw0i9s|Ern1
z)MofJZgtgA;M)CEN?q@nnW@Q@rLWE!FO~W+L%06ttd!N3ljok5%RKpDLB;LsH}8M?>x!L$>GL8hH@f^av#(iPdrIy^p?Kbo-bEgTk9*1D8n!Pd2{IV{9ll7CrVfnd{TpbIJbs*SxfL)fXM!o+i<;BIl}YY~v~J
zS$8HeRh@t57_#l;QuBr>ssC;OkwC$d$NQLX$X%GR?)j9N$HccXI~<-C
zr{%jTG~OY#f+g_w-*q+C&!(teUhC5GDR+A9saz@juX&3*UL2l$?s%uqO#7{m*`iF}
zw?-{K=Fq9>5)!p~<#n?cj7vn54;@)EQSAS*>;0=Xo3fqSAYAFSCn`$W%>PuC-Yv7+
z?o0ao1Ufn%7z#@|eKhE)WO)#o^T6SOtL(<}y~@w-&vq(H+=}9HSmS-pUx|kxx!WbClY3`S6RSC#xWM}wv_zJVDpMEAY
z!~LrZPi40%OfdMU$M9gzq3o<(+$*GgYM;erGcwfNT=mWF_EU$p#Gif)4ijS+D&J+b
znEq}>20KGVo{!L#Yp;_Q%zMm#je%j$@#HC0mtq>WpRHeAx$DlVeT$|!c=h+aOFrHs
zcg^r?^u2#-M=!W3DfV=5ocEtsC;vvGHGx;_{H8Oavhy|WMy~m@P2m0i*B7D+m%VVl
zFqiZ0iqGdaz2IhOnEpEX%C1M}eleV%y;w3#H@i)oSz~jlp1{%A_SH7!2@~|P|1JM)
z*(v46Dmd|}T|;=;Rk^9}^)o*<-~Q@Yeo6Ub>FjICJm))_7C!B|_rDN5tAKKj&prR&
zIxi~@z14OA=4RCr*NBpo#FA92>xsKL<6)Xd7r9HPOxTk|?l1B0ilpUXO@geCwMI233A
literal 0
HcmV?d00001
diff --git a/src/pt/puraymoe/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/puraymoe/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..49aa0032b13b8843c5821333bb0df04a88995a73
GIT binary patch
literal 1556
zcmaKsdpOg39LK*d5elWq+;WMBg)zjE+5FIG)N)D3Wn!dIxy=xEN~z>hlw2mqrF5d>
zQsz$OPMDC5ri(JAg;;6h>^$fEbN)Ez{XCz~`+0vp&+Fg!mBTKMTcu&r0D!HygAhUJ
z?SIW?;R57(Bui-G!5BOSfa(kxzW*j+-W_m|fCnH>7l7o80IUg5$#VciBLJ8u0)WZ|
zKARsc4s;vkG$EOTKp{!k7{
zRr0H`s(g%mDU`BBbTMzh0)sh6XADc2d2I*U_SY#a=!paadO$69{muf02}BgV-uc(O^R!5y_1{`j
zLko(}MV5pY(1~p<KPAn;r!~@62oBtpe2rkZVK+~W>tIblHc#>^V%pmh>&J*20-B*L#UR$MgbJHN
zzg3y-vIf-~Jymb+@!=3u`gHD+~yyYbr-T)1K&CNa#mvFwz`C)1vSz1xwcVBj~x`Jiy^eBs)aLj>wNFw>B4X4d2USQWZzQQP+wrRlT#`;3fYo-Q6w1<*I
zI$ryBe?{k11eI5OI*l~k4yxi*Xv|GIHiZX#Cz~6{EQ-5k91|zIDH(a&f&Pdw6&<3B7*JhM8~MVn-F;^9BDJNqXthFS^k(}21WxXMpl!D2eQN)^l5(R#
zCsR2Tk>}}ELKCm)5^3kuq6lm%3IC#{sE!!Y_q@2#
z`En#t@~wN(T@op(KbFoBywQK=oX_(bO3GF`@Rhk3CR%$cCowzEdPjs4F?58~
zZP&=|6eY)mPt7nB^4cH5$~4Rs4&=aY+0NX!;IU_5$-k6mVWxYRqvqudWE;gqdRZGSf^>9#0AVz3=!@+@<5L8%P2w2k
zX!`3@l~>cuvAeE$kUuQYg%EI;sw=lY!t56=hCPSsjV
z3GC2^k=IUeyE(#gp)%HaGH@*HI$vJ8=uVc2_HgxRSw33i(MBoK3&`TVE+=Ki+Sl
zVr+Gkn8_?lbIS6YQtufLuAt^#{nzv-gaE+VyFgV~;;(-K`H01c
literal 0
HcmV?d00001
diff --git a/src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PMUrlActivity.kt b/src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PMUrlActivity.kt
new file mode 100644
index 000000000..79c19cbfd
--- /dev/null
+++ b/src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PMUrlActivity.kt
@@ -0,0 +1,42 @@
+package eu.kanade.tachiyomi.animeextension.pt.puraymoe
+
+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://puray.moe/anime/ intents
+ * and redirects them to the main Aniyomi process.
+ */
+class PMUrlActivity : Activity() {
+
+ private val TAG = "PMUrlActivity"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size > 1) {
+ val id = pathSegments[1]
+ val searchQuery = PurayMoe.PREFIX_SEARCH + id
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.ANIMESEARCH"
+ putExtra("query", searchQuery)
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e(TAG, e.toString())
+ }
+ } else {
+ Log.e(TAG, "could not parse uri from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}
diff --git a/src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PurayMoe.kt b/src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PurayMoe.kt
new file mode 100644
index 000000000..43b20eb0b
--- /dev/null
+++ b/src/pt/puraymoe/src/eu/kanade/tachiyomi/animeextension/pt/puraymoe/PurayMoe.kt
@@ -0,0 +1,324 @@
+package eu.kanade.tachiyomi.animeextension.pt.puraymoe
+
+import android.app.Application
+import android.content.SharedPreferences
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animeextension.pt.puraymoe.dto.AnimeDto
+import eu.kanade.tachiyomi.animeextension.pt.puraymoe.dto.EpisodeDataDto
+import eu.kanade.tachiyomi.animeextension.pt.puraymoe.dto.MinimalEpisodeDto
+import eu.kanade.tachiyomi.animeextension.pt.puraymoe.dto.SearchDto
+import eu.kanade.tachiyomi.animeextension.pt.puraymoe.dto.SeasonInfoDto
+import eu.kanade.tachiyomi.animeextension.pt.puraymoe.dto.SeasonListDto
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+import eu.kanade.tachiyomi.animesource.model.AnimesPage
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import okhttp3.Headers
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.lang.Exception
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class PurayMoe : ConfigurableAnimeSource, AnimeHttpSource() {
+
+ override val name = "Puray.moe"
+
+ override val baseUrl = "https://puray.moe"
+
+ override val lang = "pt-BR"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ private val json = Json {
+ ignoreUnknownKeys = true
+ }
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ override fun headersBuilder(): Headers.Builder = Headers.Builder()
+ .add("Referer", baseUrl)
+ .add("Accept-Language", ACCEPT_LANGUAGE)
+
+ // ============================== Popular ===============================
+
+ override fun popularAnimeRequest(page: Int): Request =
+ GET("$API_URL/animes/genero/25/")
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val animeList = response.parseAs>()
+ val animes = animeList.map(::animeDetailsFromObject).toList()
+ return AnimesPage(animes, false)
+ }
+
+ // ============================== Episodes ==============================
+
+ private fun getSeasonList(anime: SAnime): SeasonListDto {
+ val id = anime.url.getId()
+ val request = GET("$API_URL/temporadas/?anime__id_animes=$id")
+ val response = client.newCall(request).execute()
+ return response.parseAs()
+ }
+
+ override fun fetchEpisodeList(anime: SAnime): Observable> {
+ val seasonsList: SeasonListDto = getSeasonList(anime)
+
+ val showOnly = preferences.getString(CONF_SHOW_ONLY, null) ?: ""
+ val dub_item = ANIME_TYPES_VALUES.elementAt(1)
+ val sub_item = ANIME_TYPES_VALUES.last()
+ var filteredSeasons = seasonsList.seasons.filter {
+ val lowerName = it.name.lowercase()
+ when (showOnly) {
+ dub_item -> lowerName.contains(dub_item)
+ sub_item -> !lowerName.contains(dub_item)
+ else -> true
+ }
+ }
+ if (filteredSeasons.size < 1) filteredSeasons = seasonsList.seasons
+
+ val episodeList = mutableListOf()
+ filteredSeasons.reversed().forEach {
+ val request: Request = episodeListRequest(it.id)
+ val response: Response = client.newCall(request).execute()
+ val season_episodes = episodeListParse(response, it)
+ episodeList.addAll(season_episodes.reversed())
+ }
+ return Observable.just(episodeList)
+ }
+
+ override fun episodeListRequest(anime: SAnime): Request =
+ throw Exception("not used")
+
+ private fun episodeListRequest(season_id: Int): Request =
+ GET("$API_URL/episodios/?temporada__id_temporadas=$season_id")
+
+ override fun episodeListParse(response: Response) = throw Exception("not used")
+
+ private fun episodeListParse(response: Response, season: SeasonInfoDto): List {
+ val episodesData = response.parseAs()
+ val seasonNumber = if (season.number.equals("0")) "1" else season.number
+ val format = if ("dub" in season.name.lowercase()) "DUBLADO" else "LEGENDADO"
+ return episodesData.episodes.map {
+ val episode = SEpisode.create()
+ episode.name = "Temp $seasonNumber ($format) EP ${it.ep_number}: ${it.name}"
+ episode.episode_number = try {
+ it.ep_number.toFloat()
+ } catch (e: NumberFormatException) { 0F }
+ episode.url = it.id.toString()
+ episode.date_upload = it.release_date.toDate()
+ episode
+ }.toList()
+ }
+
+ // ============================ Video Links =============================
+
+ override fun videoListRequest(episode: SEpisode): Request =
+ GET("$API_URL/episodios/${episode.url}/m3u8/mp4/")
+
+ override fun videoListParse(response: Response): List