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 000000000..6a0309e94 Binary files /dev/null and b/src/pt/puraymoe/res/mipmap-hdpi/ic_launcher.png differ 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 000000000..c0b840f45 Binary files /dev/null and b/src/pt/puraymoe/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/puraymoe/res/mipmap-xhdpi/ic_launcher.png b/src/pt/puraymoe/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..8d7f710d7 Binary files /dev/null and b/src/pt/puraymoe/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/puraymoe/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/puraymoe/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d45510f84 Binary files /dev/null and b/src/pt/puraymoe/res/mipmap-xxhdpi/ic_launcher.png differ 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 000000000..49aa0032b Binary files /dev/null and b/src/pt/puraymoe/res/mipmap-xxxhdpi/ic_launcher.png differ 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