Compare commits

...

8 commits

Author SHA1 Message Date
fbcf1c7bb8
repo: Change to raw url
All checks were successful
Build / build (push) Successful in 5m34s
2024-07-30 21:31:17 +03:00
d016ad7e40
gradle: setRepo to Forgejo 2024-07-30 21:29:24 +03:00
9618dedb27
workflows: Add distribution
All checks were successful
Build / build (push) Successful in 5m45s
2024-07-30 21:03:05 +03:00
9c548c142c
workflows: Fix for forgejo
Some checks failed
Build / build (push) Failing after 1m34s
2024-07-30 20:52:16 +03:00
3d445e7cc6
repo: Go to Forgejo
Some checks failed
Build / build (push) Failing after 1m21s
2024-07-30 20:23:43 +03:00
b85fdc5da4
uaflix: Implement search 2024-07-30 16:36:09 +03:00
cc025599be uaflix: Done 2024-07-30 15:36:24 +03:00
fbd642352c
uakino: Add headers to ajax 2024-07-29 18:36:12 +03:00
11 changed files with 384 additions and 10 deletions

View file

@ -33,12 +33,13 @@ jobs:
run: rm $GITHUB_WORKSPACE/builds/*.cs3 || true run: rm $GITHUB_WORKSPACE/builds/*.cs3 || true
- name: Setup JDK 11 - name: Setup JDK 11
uses: actions/setup-java@v1 uses: https://github.com/actions/setup-java@v3
with: with:
java-version: 11 java-version: 11
distribution: 'temurin'
- name: Setup Android SDK - name: Setup Android SDK
uses: android-actions/setup-android@v2 uses: https://github.com/android-actions/setup-android@v2
- name: Build Plugins - name: Build Plugins
run: | run: |

View file

@ -20,7 +20,7 @@
## ⚙️ Інсталяція ## ⚙️ Інсталяція
Скопіюйте посилання нижче і перейдіть в Додаток -> Параметри -> Розширення -> Додати репозиторій Скопіюйте посилання нижче і перейдіть в Додаток -> Параметри -> Розширення -> Додати репозиторій
``` ```
https://raw.githubusercontent.com/CakesTwix/cloudstream-extensions-uk/master/repo.json https://git.cakestwix.com/CakesTwix/cloudstream-extensions-uk/raw/branch/master/repo.json
``` ```
<!-- Support --> <!-- Support -->

View file

@ -0,0 +1,25 @@
// use an integer for version numbers
version = 2
dependencies {
implementation("com.google.code.gson:gson:2.9.0")
}
cloudstream {
language = "uk"
// All of these properties are optional, you can safely remove them
description = "UAFLIX - фільми і серіали NETFLIX українською"
authors = listOf("CakesTwix")
/**
* Status int as the following:
* 0: Down
* 1: Ok
* 2: Slow
* 3: Beta only
* */
status = 1 // will be 3 if unspecified
iconUrl = "https://www.google.com/s2/favicons?domain=uafix.net&sz=%size%"
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.lagradost"/>

View file

@ -0,0 +1,292 @@
package com.lagradost
import com.lagradost.cloudstream3.AnimeSearchResponse
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.Episode
import com.lagradost.cloudstream3.HomePageResponse
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.MainPageRequest
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.addDubStatus
import com.lagradost.cloudstream3.addEpisodes
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.fixUrl
import com.lagradost.cloudstream3.mainPageOf
import com.lagradost.cloudstream3.newAnimeLoadResponse
import com.lagradost.cloudstream3.newAnimeSearchResponse
import com.lagradost.cloudstream3.newHomePageResponse
import com.lagradost.cloudstream3.newMovieLoadResponse
import com.lagradost.cloudstream3.toRatingInt
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.models.PlayerJson
import org.jsoup.nodes.Element
class UAFlixProvider : MainAPI() {
// Basic Info
override var mainUrl = "https://uafix.net"
override var name = "UAFlix"
override val hasMainPage = true
override var lang = "uk"
override val hasDownloadSupport = true
override val supportedTypes = setOf(
TvType.TvSeries,
TvType.Cartoon,
TvType.Movie,
TvType.Anime
)
// Sections
override val mainPage = mainPageOf(
"$mainUrl/serials/page/" to "Серіали",
"$mainUrl/serials/multseial/page/" to "Мультсеріали",
"$mainUrl/cartoons/page/" to "Мультфільми",
"$mainUrl/anime/page/" to "Аніме",
"$mainUrl/dorama/page/" to "Дорами",
"$mainUrl/film/page/" to "Фільми"
)
// Main Page
private val animeSelector = ".video-item"
private val titleSelector = ".vi-img"
// private val engTitleSelector = "div.th-title-oname.truncate"
private val hrefSelector = titleSelector
private val posterSelector = ".img-resp-h img"
// Load info
// private val titleLoadSelector = ".page__subcol-main h1"
// private val genresSelector = "li span:contains(Жанр:) a"
// private val yearSelector = "a[href*=https://uaserials.pro/year/]"
// private val playerSelector = "iframe"
private val descriptionSelector = "div[id=serial-kratko]"
private val ratingSelector = ".mediablock .rat-imdb"
override suspend fun getMainPage(
page: Int,
request: MainPageRequest
): HomePageResponse {
val document = app.get(request.data + page).document
val home = document.select(animeSelector).map {
it.toSearchResponse()
}
return newHomePageResponse(request.name, home)
}
private fun Element.toSearchResponse(): AnimeSearchResponse {
val title = this.selectFirst("$titleSelector,.sres-wrap")?.attr("alt")?.trim().toString()
// val engTitle = this.selectFirst(engTitleSelector)?.text()?.trim().toString()
val href = this.selectFirst("$hrefSelector,.sres-wrap")?.attr("href").toString()
val posterUrl = fixUrl(this.select("$posterSelector,.sres-img img").attr("src"))
return newAnimeSearchResponse(title, href, TvType.Anime) {
// this.otherName = engTitle
this.posterUrl = posterUrl
addDubStatus(isDub = true)
}
}
override suspend fun search(query: String): List<SearchResponse> {
val document = app.get(
url = "$mainUrl/index.php?do=search&subaction=search&search_start=1&story=$query",
).document
return document.select(".sres-wrap").map {
it.toSearchResponse()
}
}
// Detailed information
override suspend fun load(url: String): LoadResponse {
val document = app.get(url).document
// Parse info
val title = document.select(".fright h1").text().trim().replace("дивитись онлайн", "")
val engTitle = document.select("span.eng-rus").text()
var poster = fixUrl(document.select(".img-box img").attr("data-src"))
if(poster.isNullOrBlank()){
poster = fixUrl(document.select(".img-box img").attr("src"))
}
val tags = mutableListOf<String>()
val actors = mutableListOf<String>()
var year = "f".toIntOrNull()
document.select(".fcols4 .finfo li").forEach { menu ->
with(menu){
when{
this.selectFirst("span").text() == "Жанр:" -> menu.select("span[itemprop=genre]").map { tags.add(it.text()) }
this.selectFirst("span").text() == "В ролях:" -> menu.select("span[itemprop=actor]").map { actors.add(it.text()) }
this.selectFirst("span").text() == "Рік виходу:" -> year = menu.select(".year").text().toIntOrNull()
}
}
}
var tvType = with(url){
when{
contains("serials") -> TvType.TvSeries
contains("serials/multseial") -> TvType.Cartoon
contains("film") -> TvType.Movie
contains("cartoons") -> TvType.Movie
contains("anime") -> TvType.Anime
else -> TvType.TvSeries
}
}
val description = document.selectFirst(descriptionSelector)?.text()?.trim()
val rating = document.select(ratingSelector).text().toRatingInt()
// Parse episodes
val episodes = mutableListOf<Episode>()
val playerUrl = document.select(".video-box iframe").attr("src")
if(playerUrl.isNullOrBlank()){ // Need parse episode list from site
val pagination = if (document.select(".pagination li").size == 0) 1 else document.select(".pagination li").size
for(i in 1..pagination){
var episodesList = document
if(i != 1){
episodesList = app.get("$url?page=$i").document
}
episodesList.select(".video-item").map { video_item ->
episodes.add(
Episode(
video_item.select(".vi-img").attr("href"),
video_item.select(".vi-rate").text(),
extractIntsFromString(video_item.select(".vi-title").text())[0].value.toIntOrNull(),
extractIntsFromString(video_item.select(".vi-title").text())[1].value.toIntOrNull(),
posterUrl = fixUrl(video_item.select(".img-resp-h img").attr("data-src"))
)
)
}
}
} else { // Player in site
val playerRawJson = app.get(playerUrl).document.select("script").html()
.substringAfterLast("file:\'")
.substringBefore("\',")
tryParseJson<List<PlayerJson>>(playerRawJson)?.map { dubs -> // Dubs
for(season in dubs.folder){ // Seasons
for(episode in season.folder){ // Episodes
episodes.add(
Episode(
"${season.title}, ${episode.title}, $playerUrl",
episode.title,
season.title.replace(" Сезон ","").toIntOrNull(),
episode.title.replace("Серія ","").toIntOrNull(),
episode.poster
)
)
}
}
}
}
// Parse Episodes as Series
return if (tvType != TvType.Movie) {
newAnimeLoadResponse(title, url, tvType) {
this.posterUrl = poster
this.engName = engTitle
this.year = year
this.plot = description
this.tags = tags
this.rating = rating
addEpisodes(DubStatus.Dubbed, episodes.sortedBy { it.episode })
addActors(actors)
}
} else { // Parse as Movie.
newMovieLoadResponse(title, url, tvType, url) {
this.posterUrl = poster
this.name = engTitle
this.year = year
this.plot = description
this.tags = tags
this.rating = rating
addActors(actors)
}
}
}
// It works when I click to view the series
override suspend fun loadLinks(
data: String, // (Serial) [Season Index, Episode Name, url] | (Film) [Title, Player Url]
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val dataList = data.split(", ")
if(dataList.size == 1){
var playerUrl = app.get(data).document.select(".video-box iframe").attr("src")
if(playerUrl.startsWith("//")){
playerUrl = "https:$playerUrl"
}
if(playerUrl.contains("/vod/")){
var playerRawJson = app.get(playerUrl,
headers = mapOf(
"Referer" to "https://uafix.net/",
)).document.select("script").html()
.substringAfterLast("file:\"")
.substringBefore("\",")
M3u8Helper.generateM3u8(
source = "UAFlix",
streamUrl = playerRawJson,
referer = "https://tortuga.wtf/"
).forEach(callback)
return true
}
val playerRawJson = app.get(playerUrl).document.select("script").html()
.substringAfterLast("file:\'")
.substringBefore("\',")
tryParseJson<List<PlayerJson>>(playerRawJson)?.map { dubs -> // Dubs
for(season in dubs.folder){ // Seasons
// Add as source
M3u8Helper.generateM3u8(
source = dubs.title,
streamUrl = dubs.folder[0].folder[0].file,
referer = "https://tortuga.wtf/"
).forEach(callback)
}
}
return true
}
val playerRawJson = app.get(dataList[2]).document.select("script").html()
.substringAfterLast("file:\'")
.substringBefore("\',")
tryParseJson<List<PlayerJson>>(playerRawJson)?.map { dubs -> // Dubs
for(season in dubs.folder){ // Seasons
if(season.title == dataList[0]){
for(episode in season.folder){ // Episodes
if(episode.title == dataList[1]){
// Add as source
M3u8Helper.generateM3u8(
source = dubs.title,
streamUrl = episode.file.replace("https://", "http://"),
referer = "https://tortuga.wtf/"
).forEach(callback)
}
}
}
}
}
return true
}
private fun extractIntsFromString(string: String): List<MatchResult> {
return Regex("(\\d+)").findAll(string).toList()
}
}

View file

@ -0,0 +1,13 @@
package com.lagradost
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
import com.lagradost.cloudstream3.plugins.Plugin
import android.content.Context
@CloudstreamPlugin
class UAFlixProviderPlugin: Plugin() {
override fun load(context: Context) {
// All providers should be added in this manner. Please don't edit the providers list directly.
registerMainAPI(UAFlixProvider())
}
}

View file

@ -0,0 +1,24 @@
package com.lagradost.models
import com.google.gson.annotations.SerializedName
data class PlayerJson (
val title : String,
val folder : List<Season>
)
data class Season (
val title : String,
val folder : List<Episode>
)
data class Episode (
val title : String,
val file : String,
val id : String,
val poster : String,
val subtitle : String,
)

View file

@ -1,5 +1,5 @@
// use an integer for version numbers // use an integer for version numbers
version = 9 version = 10
cloudstream { cloudstream {

View file

@ -1,5 +1,6 @@
package com.lagradost package com.lagradost
import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
@ -155,7 +156,12 @@ class UakinoProvider : MainAPI() {
val id = url.split("/").last().split("-").first() val id = url.split("/").last().split("-").first()
val episodes = val episodes =
app.get( app.get(
"$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}" "$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}",
headers = mapOf(
"Referer" to "https://uakino.me",
"X-Requested-With" to "XMLHttpRequest",
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:126.0) Gecko/20100101 Firefox/126.0",
)
) )
.parsedSafe<Responses>() .parsedSafe<Responses>()
?.response ?.response
@ -210,11 +216,17 @@ class UakinoProvider : MainAPI() {
): Boolean { ): Boolean {
val dataList = data.split(",") val dataList = data.split(",")
// TODO: OPTIMIZE code!!! Remove this shitty code as soon as possible!!!!!! // TODO: OPTIMIZE code!!! Remove this shitty code as soon as possible!!!!!!
Log.d("CakesTwix-Debug", data)
if (dataList.size == 1) { if (dataList.size == 1) {
val id = data.split("/").last().split("-").first() val id = data.split("/").last().split("-").first()
val responseGet = val responseGet =
app.get( app.get(
"$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}" "$mainUrl/engine/ajax/playlists.php?news_id=$id&xfield=playlist&time=${Date().time}",
headers = mapOf(
"Referer" to "https://uakino.me",
"X-Requested-With" to "XMLHttpRequest",
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:126.0) Gecko/20100101 Firefox/126.0",
)
) )
.parsedSafe<Responses>() .parsedSafe<Responses>()
if (responseGet?.success == true) { // Its serial if (responseGet?.success == true) { // Its serial
@ -279,7 +291,12 @@ class UakinoProvider : MainAPI() {
return true return true
} }
val responseGet = app.get(dataList[0]).parsedSafe<Responses>() // ajax link val responseGet = app.get(dataList[0],
headers = mapOf(
"Referer" to "https://uakino.me",
"X-Requested-With" to "XMLHttpRequest",
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:126.0) Gecko/20100101 Firefox/126.0",
)).parsedSafe<Responses>() // ajax link
if (responseGet?.success == true) { // Its serial if (responseGet?.success == true) { // Its serial
responseGet?.response?.let { responseGet?.response?.let {
Jsoup.parse(it) Jsoup.parse(it)

View file

@ -37,7 +37,7 @@ subprojects {
cloudstream { cloudstream {
// when running through github workflow, GITHUB_REPOSITORY should contain current repository name // when running through github workflow, GITHUB_REPOSITORY should contain current repository name
// you can modify it to use other git hosting services, like gitlab // you can modify it to use other git hosting services, like gitlab
setRepo(System.getenv("GITHUB_REPOSITORY") ?: "https://github.com/CakesTwix/cloudstream-extensions-uk") setRepo("CakesTwix", "cloudstream-extensions-uk", "gitea-git.cakestwix.com")
} }
android { android {

View file

@ -1,8 +1,8 @@
{ {
"name": "CakesTwix Providers Repository", "name": "CakesTwix Providers Repository Forgejo",
"description": "Cloudstream Ukraine Plugin Repository", "description": "Cloudstream Ukraine Plugin Repository",
"manifestVersion": 1, "manifestVersion": 1,
"pluginLists": [ "pluginLists": [
"https://raw.githubusercontent.com/CakesTwix/cloudstream-extensions-uk/builds/plugins.json" "https://git.cakestwix.com/CakesTwix/cloudstream-extensions-uk/raw/branch/builds/plugins.json"
] ]
} }