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