uakino: New UA Extension (#1278)

This commit is contained in:
CakesTwix
2023-02-14 23:36:40 +02:00
committed by GitHub
parent 23f2c1210b
commit c08cbf1d57
24 changed files with 352 additions and 0 deletions

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.animeextension" />

View File

@ -0,0 +1,13 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'UAKino'
pkgNameSuffix = 'uk.uakino'
extClass = '.UAKino'
extVersionCode = 1
libVersion = '13'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#2B2B2B</color>
</resources>

View File

@ -0,0 +1,24 @@
package eu.kanade.tachiyomi.animeextension.uk.uakino
import kotlinx.serialization.Serializable
@Serializable
data class AshdiModel(
val title: String,
val folder: List<Ashdi>
)
@Serializable
data class Ashdi(
val title: String,
val folder: List<Video>
)
@Serializable
data class Video(
val title: String,
val file: String,
val id: String,
val poster: String,
val subtitle: String
)

View File

@ -0,0 +1,225 @@
package eu.kanade.tachiyomi.animeextension.uk.uakino
import android.util.Log
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
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.POST
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import org.json.JSONTokener
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
class UAKino : ParsedAnimeHttpSource() {
override val lang = "uk"
override val name = "UAKino"
override val supportsLatest = true
private val animeSelector = "div.movie-item"
private val nextPageSelector = "a:contains(Далі)"
override val baseUrl = "https://uakino.club"
private val animeUrl = "/animeukr"
private val popularUrl = "/f/c.year=1921,2023/sort=rating;desc"
private val episodesAPI = "https://uakino.club/engine/ajax/playlists.php?news_id=%s&xfield=playlist" // %s - ID title
override val client: OkHttpClient = network.client
// =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.title = document.select("h1 span.solototle").text()
// Poster can be /upload... or https://...
val posterUrl = document.select("a[data-fancybox=gallery]").attr("href")
if (posterUrl.contains("https://uakino.club")) {
anime.thumbnail_url = posterUrl
} else {
anime.thumbnail_url = baseUrl + posterUrl
}
anime.description = document.select("div.full-text[itemprop=description]").text()
Log.d("animeDetailsParse", anime.thumbnail_url!!)
return anime
}
// ============================== Popular ===============================
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a.movie-title").attr("href"))
anime.thumbnail_url = baseUrl + element.select("div.movie-img img").attr("src")
anime.title = element.select("a.movie-title").text()
return anime
}
override fun popularAnimeNextPageSelector() = nextPageSelector
override fun popularAnimeRequest(page: Int): Request {
return GET("$baseUrl$animeUrl$popularUrl/page/$page")
}
override fun popularAnimeSelector(): String = animeSelector
// =============================== Latest ===============================
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = nextPageSelector
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl + animeUrl)
override fun latestUpdatesSelector() = animeSelector
// =============================== Search ===============================
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector() = nextPageSelector
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val body = FormBody.Builder()
.add("do", "search")
.add("subaction", "search")
.add("story", query)
.build()
return POST(baseUrl, body = body)
}
override fun searchAnimeSelector() = animeSelector
// ============================== Episode ===============================
override fun episodeFromElement(element: Element) = throw Exception("not used")
override fun episodeListSelector() = throw Exception("not used")
private val json = Json { ignoreUnknownKeys = true }
override fun episodeListParse(response: Response): List<SEpisode> {
val animePage = response.asJsoup()
// Get ID title
var titleID = animePage.select("input[id=post_id]").attr("value")
// Do call
val episodesList = client.newCall(GET(episodesAPI.format(titleID)))
.execute()
.body!!.string()
// Parse JSON
Log.d("episodeListParse", episodesAPI.format(titleID))
val jsonObject = JSONTokener(episodesList).nextValue() as JSONObject
// List episodes
val episodeList = mutableListOf<SEpisode>()
// If "success" is false - is not anime serial(or another player)
if (jsonObject.getBoolean("success")) {
Jsoup.parse(jsonObject.getString("response")).select("div.playlists-videos li").forEach {
val episode = SEpisode.create()
episode.name = it.text() + " " + it.select("li").attr("data-voice")
var episodeUrl = it.select("li").attr("data-file")
// Can be without https:
if (episodeUrl.contains("https://")) {
episode.url = episodeUrl
} else {
episode.url = "https:" + episodeUrl
}
episodeList.add(episode)
}
} else {
val playerUrl = animePage.select("iframe#pre").attr("src")
// Another player
if (playerUrl.contains("/serial/")) {
Log.d("episodeListParse", playerUrl)
val playerScript = client.newCall(GET(playerUrl))
.execute()
.asJsoup()
.select("script")
.html()
// Get m3u8 url
val regexM3u8 = """file:'(.*?)'""".toRegex()
val m3u8JSONString = regexM3u8.find(playerScript)!!.value.substring(6).dropLast(1) // Drop file:"..."
Log.d("episodeListParse", m3u8JSONString)
val episodesJSON = json.decodeFromString<List<AshdiModel>>(m3u8JSONString)
for (itemVoice in episodesJSON) { // Voice
for (itemSeason in itemVoice.folder) { // Season
for (itemVideo in itemSeason.folder) { // Video
val episode = SEpisode.create()
episode.name = "${itemSeason.title} ${itemVideo.title} ${itemVoice.title}" // "Сезон 1 Серія 1 Озвучення"
episode.url = itemVideo.file
episodeList.add(episode)
}
}
}
}
// Search as one video
else {
val episode = SEpisode.create()
episode.name = animePage.select("span.solototle").text()
episode.url = playerUrl
episodeList.add(episode)
}
}
return episodeList.reversed()
}
// ============================ Video ===============================
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
Log.d("fetchVideoList", episode.url)
val videoList = mutableListOf<Video>()
var m3u8Episode = episode.url
if (!episode.url.contains(".m3u8")) { // If not from another player
// Get player script
val playerScript = client.newCall(GET(episode.url)).execute().asJsoup().select("script").html()
// Get m3u8 url
val regexM3u8 = """file:"(.*?)"""".toRegex()
m3u8Episode = regexM3u8.find(playerScript)!!.value.substring(6).dropLast(1) // Drop file:"..."
}
// Parse m3u (480p/720p/1080p)
// GET Calll m3u8 url
val masterPlaylist = client.newCall(GET(m3u8Episode)).execute().body!!.string()
// Parse quality and videoUrl from m3u8 file
masterPlaylist.substringAfter("#EXT-X-STREAM-INF:").split("#EXT-X-STREAM-INF:").forEach {
val quality = it.substringAfter("RESOLUTION=").substringAfter("x").substringBefore(",") + "p"
val videoUrl = it.substringAfter("\n").substringBefore("\n")
videoList.add(Video(videoUrl, quality, videoUrl))
}
return Observable.just(videoList)
}
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun videoListSelector() = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
}