diff --git a/src/pt/hinatasoul/AndroidManifest.xml b/src/pt/hinatasoul/AndroidManifest.xml new file mode 100644 index 000000000..b529a3beb --- /dev/null +++ b/src/pt/hinatasoul/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/src/pt/hinatasoul/build.gradle b/src/pt/hinatasoul/build.gradle new file mode 100644 index 000000000..cc26fe11d --- /dev/null +++ b/src/pt/hinatasoul/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Hinata Soul' + pkgNameSuffix = 'pt.hinatasoul' + extClass = '.HinataSoul' + extVersionCode = 1 + libVersion = '13' +} + + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/hinatasoul/res/mipmap-hdpi/ic_launcher.png b/src/pt/hinatasoul/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..d311d948e Binary files /dev/null and b/src/pt/hinatasoul/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/hinatasoul/res/mipmap-mdpi/ic_launcher.png b/src/pt/hinatasoul/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..4aa0b17a3 Binary files /dev/null and b/src/pt/hinatasoul/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/hinatasoul/res/mipmap-xhdpi/ic_launcher.png b/src/pt/hinatasoul/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..87fed1cab Binary files /dev/null and b/src/pt/hinatasoul/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/hinatasoul/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/hinatasoul/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d480b746b Binary files /dev/null and b/src/pt/hinatasoul/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/hinatasoul/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/hinatasoul/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..b6dc907aa Binary files /dev/null and b/src/pt/hinatasoul/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HSUrlActivity.kt b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HSUrlActivity.kt new file mode 100644 index 000000000..ddd1f1b8f --- /dev/null +++ b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HSUrlActivity.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.animeextension.pt.hinatasoul + +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://www.hinatasoul.com/animes/ intents + * and redirects them to the main Aniyomi process. + */ +class HSUrlActivity : Activity() { + + private val TAG = "HSUrlActivity" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val slug = pathSegments[1] + val searchQuery = HinataSoul.PREFIX_SEARCH + slug + 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/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HinataSoul.kt b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HinataSoul.kt new file mode 100644 index 000000000..6fee34735 --- /dev/null +++ b/src/pt/hinatasoul/src/eu/kanade/tachiyomi/animeextension/pt/hinatasoul/HinataSoul.kt @@ -0,0 +1,267 @@ +package eu.kanade.tachiyomi.animeextension.pt.hinatasoul + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animeextension.pt.hinatasoul.extractors.HinataSoulExtractor +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.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.text.SimpleDateFormat +import java.util.Locale + +class HinataSoul : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + + override val name = "Hinata Soul" + + override val baseUrl = "https://www.hinatasoul.com" + + override val lang = "pt-BR" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("Referer", baseUrl) + + // ============================== Popular =============================== + override fun popularAnimeSelector() = "div.FsssItem:contains(Mais Vistos) > a" + override fun popularAnimeRequest(page: Int): Request = GET(baseUrl) + override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { + setUrlWithoutDomain(element.attr("href")) + title = element.text() + } + override fun popularAnimeNextPageSelector(): String? = null + + // ============================== Episodes ============================== + override fun episodeListSelector() = "div.aniContainer a" + override fun episodeListParse(response: Response): List { + val totalEpisodes = mutableListOf() + var doc = getRealDoc(response.asJsoup()) + val originalUrl = doc.location() + var pageNum = 1 + do { + if (pageNum > 1) { + doc = client.newCall(GET(originalUrl + "/page/$pageNum")) + .execute() + .asJsoup() + } + doc.select(episodeListSelector()).forEach { + totalEpisodes.add(episodeFromElement(it)) + } + pageNum++ + } while (hasNextPage(doc)) + return totalEpisodes.reversed() + } + + override fun episodeFromElement(element: Element) = SEpisode.create().apply { + val title = element.attr("title") + setUrlWithoutDomain(element.attr("href")) + name = title + episode_number = runCatching { title.substringAfterLast(" ").toFloat() } + .getOrNull() ?: 0F + date_upload = element.selectFirst("div.lancaster_episodio_info_data") + .text() + .toDate() + } + + // ============================ Video Links ============================= + override fun videoListParse(response: Response): List