add MacIPTVProvider (tested lang : English, Arabic, French)

This commit is contained in:
Eddy 2022-10-23 00:04:35 +02:00
parent 33d15586e9
commit b29fed2c69
21 changed files with 1162 additions and 0 deletions

View File

@ -0,0 +1,29 @@
dependencies {
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("com.google.android.material:material:1.4.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
}
// use an integer for version numbers
version = 1
cloudstream {
// All of these properties are optional, you can safely remove them
description = "Add your IPTV account or alternatively, the automatic account will allow you to watch live (Sport, movies, ...)."
authors = listOf("Eddy")
/**
* Status int as the following:
* 0: Down
* 1: Ok
* 2: Slow
* 3: Beta only
* */
status = 1 // will be 3 if unspecified
tvTypes = listOf("Live")
iconUrl = "https://www.google.com/s2/favicons?domain=franceiptv.fr/&sz=%size%"
requiresResources = true
}

View File

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

View File

@ -0,0 +1,746 @@
package com.lagradost
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import okhttp3.Interceptor
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.nicehttp.NiceResponse
import kotlinx.coroutines.runBlocking
import me.xdrop.fuzzywuzzy.FuzzySearch
class MacIPTVProvider(override var lang: String) : MainAPI() {
private val defaulmac_adresse =
"mac=00:1a:79:ae:2a:30"
private val defaultmainUrl =
"http://ultra-box.club/c/"
var defaultname = "BoxIPTV-MatrixOTT |${lang.uppercase()}|"
override var name = "Box Iptv |${lang.uppercase()}|"
override val hasQuickSearch = false
override val hasMainPage = true
override val supportedTypes =
setOf(TvType.Live) // live
private var init = false
private var key: String? = ""
companion object {
var companionName: String? = null
var loginMac: String? = null
var overrideUrl: String? = null
}
private suspend fun getAuthHeader(): Map<String, String> {
mainUrl = overrideUrl.toString()
val main = mainUrl.uppercase().trim()
val localCredentials = loginMac
val mac = localCredentials?.uppercase()?.trim()
if (main == "NONE" || main.isBlank() || mac == "NONE" || mac.isNullOrBlank()) {
mainUrl = defaultmainUrl
name = defaultname
if (!init) {
val url_key = "$mainUrl/portal.php?type=stb&action=handshake&JsHttpRequest=1-xml"
val reponseGetkey = app.get(
url_key, headers = mapOf(
"Cookie" to defaulmac_adresse,
"User-Agent" to "Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3",
)
)
val keyJson = reponseGetkey.parsed<Getkey>()
key = keyJson.js?.token
}
init = true
return mapOf(
"Cookie" to defaulmac_adresse,
"User-Agent" to "Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3",
"Authorization" to "Bearer $key",
)
}
name = (companionName ?: name) + " |${lang.uppercase()}|"
if (!init) {
val url_key = "$mainUrl/portal.php?type=stb&action=handshake&JsHttpRequest=1-xml"
val reponseGetkey = app.get(
url_key, headers = mapOf(
"Cookie" to "mac=$localCredentials",
"User-Agent" to "Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3",
)
)
val keyJson = reponseGetkey.parsed<Getkey>()
key = keyJson.js?.token
}
init = true
return mapOf(
"Cookie" to "mac=$localCredentials",
"User-Agent" to "Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 2 rev: 250 Safari/533.3",
"Authorization" to "Bearer $key",
)
}
private fun List<Channel>.sortByname(query: String?): List<Channel> {
return if (query == null) {
// Return list to base state if no query
this.sortedBy { it.title }
} else {
this.sortedBy {
val name = cleanTitle(it.title)
-FuzzySearch.ratio(name.lowercase(), query.lowercase())
}
}
}
/**
Cherche le site pour un titre spécifique
La recherche retourne une SearchResponse, qui peut être des classes suivants: AnimeSearchResponse, MovieSearchResponse, TorrentSearchResponse, TvSeriesSearchResponse
Chaque classes nécessite des données différentes, mais a en commun le nom, le poster et l'url
**/
private val resultsSearchNbr = 50
override suspend fun search(query: String): List<SearchResponse> {
val searchResutls = ArrayList<SearchResponse>()
arraymediaPlaylist.sortByname(query).take(resultsSearchNbr).forEach { media ->
searchResutls.add(
LiveSearchResponse(
media.title,
media.url,
name,
TvType.Live,
media.url_image,
)
)
}
return searchResutls
}
data class Root_epg(
@JsonProperty("js") var js: ArrayList<Js_epg> = arrayListOf()
)
data class Js_epg(
/* @JsonProperty("id") var id: String? = null,
@JsonProperty("ch_id") var chId: String? = null,
@JsonProperty("correct") var correct: String? = null,
@JsonProperty("time") var time: String? = null,
@JsonProperty("time_to") var timeTo: String? = null,
@JsonProperty("duration") var duration: Int? = null,*/
@JsonProperty("name") var name: String? = null,
@JsonProperty("descr") var descr: String? = null,
/* @JsonProperty("real_id") var realId: String? = null,
@JsonProperty("category") var category: String? = null,
@JsonProperty("director") var director: String? = null,
@JsonProperty("actor") var actor: String? = null,
@JsonProperty("start_timestamp") var startTimestamp: Int? = null,
@JsonProperty("stop_timestamp") var stopTimestamp: Int? = null,*/
@JsonProperty("t_time") var tTime: String? = null,
@JsonProperty("t_time_to") var tTimeTo: String? = null,
/* @JsonProperty("mark_memo") var markMemo: Int? = null,
@JsonProperty("mark_archive") var markArchive: Int? = null*/
)
private fun getEpg(response: String): String {
val reponseJSON_0 = tryParseJson<Root_epg>(response)
var description = ""
reponseJSON_0?.js?.forEach { epg_i ->
val name = epg_i.name
val descr = epg_i.descr
val t_time = epg_i.tTime
val t_time_to = epg_i.tTimeTo
val new_descr = "||($t_time -> $t_time_to) $name : $descr"
if (!description.contains(new_descr)) {
description = "$description\n $new_descr"
}
}
return description
}
override suspend fun load(url: String): LoadResponse {
var link = ""
var title = ""
var posterUrl = ""
var description = ""
val header = getAuthHeader()
val allresultshome: MutableList<SearchResponse> = mutableListOf()
for (media in arraymediaPlaylist) {
val keyId = "/-${media.id}-"
if (url.contains(keyId) || url == media.url) {
val epg_url =
"$mainUrl/portal.php?type=itv&action=get_short_epg&ch_id=${media.ch_id}&size=10&JsHttpRequest=1-xml" // descriptif
val response = app.get(epg_url, headers = header)
description = getEpg(response.text)
link = media.url
title = media.title
val a = cleanTitle(title)
posterUrl = media.url_image.toString()
var b_new: String
arraymediaPlaylist.forEach { channel ->
val b = cleanTitle(channel.title)
b_new = b.take(6)
if (channel.id != media.id && a.take(6)
.contains(b_new) && media.tv_genre_id == channel.tv_genre_id
) {
val streamurl = channel.url
val channelname = channel.title
val posterurl = channel.url_image.toString()
val uppername = channelname.uppercase()
val quality = getQualityFromString(
when (!channelname.isNullOrBlank()) {
uppername.contains(findKeyWord("UHD")) -> {
"UHD"
}
uppername.contains(findKeyWord("HD")) -> {
"HD"
}
uppername.contains(findKeyWord("SD")) -> {
"SD"
}
uppername.contains(findKeyWord("FHD")) -> {
"HD"
}
uppername.contains(findKeyWord("4K")) -> {
"FourK"
}
else -> {
null
}
}
)
allresultshome.add(
LiveSearchResponse(
name = cleanTitleButKeepNumber(channelname),
url = streamurl,
name,
TvType.Live,
posterUrl = posterurl,
quality = quality,
)
)
}
}
break
}
}
if (allresultshome.size >= 1) {
val recommendation = allresultshome
return LiveStreamLoadResponse(
name = title,
url = link,
apiName = this.name,
dataUrl = link,
posterUrl = posterUrl,
plot = description,
recommendations = recommendation
)
} else {
return LiveStreamLoadResponse(
name = title,
url = link,
apiName = this.name,
dataUrl = link,
posterUrl = posterUrl,
plot = description,
)
}
}
/** get the channel id */
val regexCode_ch = Regex("""\/(\d*)\?""")
/**
* Use new token.
* */
override fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor {
// Needs to be object instead of lambda to make it compile correctly
return object : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val request = chain.request()
if (request.url.toString().contains("token")
) {
val chID = regexCode_ch.find(request.url.toString())!!.groupValues[1] + "_"
val TokenLink =
"$mainUrl/portal.php?type=itv&action=create_link&cmd=ffmpeg%20http://localhost/ch/$chID&series=&forced_storage=0&disable_ad=0&download=0&force_ch_link_check=0&JsHttpRequest=1-xml"
var link: String
var lien: String
runBlocking {
val header = getAuthHeader()
val getTokenLink = app.get(TokenLink, headers = header).text
val regexGetLink = Regex("""(http.*)\"\},""")
link = regexGetLink.find(getTokenLink)?.groupValues?.get(1).toString()
.replace(
"""\""",
""
)
lien = link
if (link.contains("extension")) {
val headerlocation = app.get(
link,
allowRedirects = false
).headers
val redirectlink = headerlocation.get("location")
.toString()
if (redirectlink != "null") {
lien = redirectlink
}
}
}
val newRequest = chain.request()
.newBuilder().url(lien).build()
return chain.proceed(newRequest)
} else {
return chain.proceed(chain.request())
}
}
}
}
/** récupere les liens .mp4 ou m3u8 directement à partir du paramètre data généré avec la fonction load()**/
override suspend fun loadLinks(
data: String, // fournit par load()
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit,
): Boolean {
val header = getAuthHeader()
val TokenLink =
"$mainUrl/portal.php?type=itv&action=create_link&cmd=ffmpeg%20$data&series=&forced_storage=0&disable_ad=0&download=0&force_ch_link_check=0&JsHttpRequest=1-xml"
val getTokenLink = app.get(TokenLink, headers = header).text
val regexGetLink = Regex("""(http.*)\"\},""")
val link =
regexGetLink.find(getTokenLink)?.groupValues?.get(1).toString().replace("""\""", "")
val head = mapOf(
"Accept" to "*/*",
"Accept-Language" to "en_US",
"User-Agent" to "VLC/3.0.18 LibVLC/3.0.18",
"Range" to "bytes=0-"
)
var lien = link
if (link.contains("extension")) {
val headerlocation = app.get(
link,
allowRedirects = false
).headers
val redirectlink = headerlocation.get("location")
.toString()
if (redirectlink != "null") {
lien = redirectlink
}
}
val isM3u8 = false// lien.contains("extension=ts")
callback.invoke(
ExtractorLink(
name,
name,
lien,
mainUrl,
Qualities.Unknown.value,
isM3u8 = isM3u8,
headers = head
)
)
return true
}
private val arraymediaPlaylist = ArrayList<Channel>()
data class Channel(
var title: String,
var url: String,
val url_image: String?,
val lang: String?,
var id: String?,
var tv_genre_id: String?,
var ch_id: String?,
)
data class Cmds(
// @JsonProperty("id") var id: String? = null,
@JsonProperty("ch_id") var chId: String? = null,
/* @JsonProperty("priority") var priority: String? = null,
@JsonProperty("url") var url: String? = null,
@JsonProperty("status") var status: String? = null,
@JsonProperty("use_http_tmp_link") var useHttpTmpLink: String? = null,
@JsonProperty("wowza_tmp_link") var wowzaTmpLink: String? = null,
@JsonProperty("user_agent_filter") var userAgentFilter: String? = null,
@JsonProperty("use_load_balancing") var useLoadBalancing: String? = null,
@JsonProperty("changed") var changed: String? = null,
@JsonProperty("enable_monitoring") var enableMonitoring: String? = null,
@JsonProperty("enable_balancer_monitoring") var enableBalancerMonitoring: String? = null,
@JsonProperty("nginx_secure_link") var nginxSecureLink: String? = null,
@JsonProperty("flussonic_tmp_link") var flussonicTmpLink: String? = null*/
)
data class Data(
@JsonProperty("id") var id: String? = null,
@JsonProperty("name") var name: String? = null,
@JsonProperty("number") var number: String? = null,
/* @JsonProperty("censored") var censored: Int? = null,
@JsonProperty("cmd") var cmd: String? = null,
@JsonProperty("cost") var cost: String? = null,
@JsonProperty("count") var count: String? = null,
@JsonProperty("status") var status: Int? = null,
@JsonProperty("hd") var hd: String? = null,*/
@JsonProperty("tv_genre_id") var tvGenreId: String? = null,
/* @JsonProperty("base_ch") var baseCh: String? = null,
@JsonProperty("xmltv_id") var xmltvId: String? = null,
@JsonProperty("service_id") var serviceId: String? = null,
@JsonProperty("bonus_ch") var bonusCh: String? = null,
@JsonProperty("volume_correction") var volumeCorrection: String? = null,
@JsonProperty("mc_cmd") var mcCmd: String? = null,
@JsonProperty("enable_tv_archive") var enableTvArchive: Int? = null,
@JsonProperty("wowza_tmp_link") var wowzaTmpLink: String? = null,
@JsonProperty("wowza_dvr") var wowzaDvr: String? = null,
@JsonProperty("use_http_tmp_link") var useHttpTmpLink: String? = null,
@JsonProperty("monitoring_status") var monitoringStatus: String? = null,
@JsonProperty("enable_monitoring") var enableMonitoring: String? = null,
@JsonProperty("enable_wowza_load_balancing") var enableWowzaLoadBalancing: String? = null,
@JsonProperty("cmd_1") var cmd1: String? = null,
@JsonProperty("cmd_2") var cmd2: String? = null,
@JsonProperty("cmd_3") var cmd3: String? = null,*/
@JsonProperty("logo") var logo: String? = null,
/* @JsonProperty("correct_time") var correctTime: String? = null,
@JsonProperty("nimble_dvr") var nimbleDvr: String? = null,
@JsonProperty("allow_pvr") var allowPvr: Int? = null,
@JsonProperty("allow_local_pvr") var allowLocalPvr: Int? = null,
@JsonProperty("allow_remote_pvr") var allowRemotePvr: Int? = null,
@JsonProperty("modified") var modified: String? = null,
@JsonProperty("allow_local_timeshift") var allowLocalTimeshift: String? = null,
@JsonProperty("nginx_secure_link") var nginxSecureLink: String? = null,
@JsonProperty("tv_archive_duration") var tvArchiveDuration: Int? = null,
@JsonProperty("locked") var locked: Int? = null,
@JsonProperty("lock") var lock: Int? = null,
@JsonProperty("fav") var fav: Int? = null,
@JsonProperty("archive") var archive: Int? = null,
@JsonProperty("genres_str") var genresStr: String? = null,
@JsonProperty("cur_playing") var curPlaying: String? = null,
@JsonProperty("epg") var epg: ArrayList<String> = arrayListOf(),
@JsonProperty("open") var open: Int? = null,*/
@JsonProperty("cmds") var cmds: ArrayList<Cmds> = arrayListOf(),
/* @JsonProperty("use_load_balancing") var useLoadBalancing: Int? = null,
@JsonProperty("pvr") var pvr: Int? = null*/
)
data class JsKey(
@JsonProperty("token") var token: String? = null
)
data class Getkey(
@JsonProperty("js") var js: JsKey? = JsKey()
)
data class JsInfo(
@JsonProperty("mac") var mac: String? = null,
@JsonProperty("phone") var phone: String? = null
)
data class GetExpiration(
@JsonProperty("js") var js: JsInfo? = JsInfo()
)
data class Js(
@JsonProperty("total_items") var totalItems: Int? = null,
@JsonProperty("max_page_items") var maxPageItems: Int? = null,
/* @JsonProperty("selected_item") var selectedItem: Int? = null,
@JsonProperty("cur_page") var curPage: Int? = null,*/
@JsonProperty("data") var data: ArrayList<Data> = arrayListOf()
)
data class JsonGetGenre(
@JsonProperty("js") var js: ArrayList<Js_category> = arrayListOf()
)
data class Js_category(
@JsonProperty("id") var id: String? = null,
@JsonProperty("title") var title: String? = null,
/* @JsonProperty("alias") var alias: String? = null,
@JsonProperty("active_sub") var activeSub: Boolean? = null,
@JsonProperty("censored") var censored: Int? = null*/
)
data class Root(
@JsonProperty("js") var js: Js? = Js()
)
private var codeCountry = lang
private fun findKeyWord(str: String): Regex {
val upperSTR = str.uppercase()
val sequence = when (true) {
(upperSTR == "EN") -> {
"US|UK"
}
else -> upperSTR
}
return """(?:^|\W+|\s)+($sequence)(?:\s|\W+|${'$'}|\|)+""".toRegex()
}
private fun String.isContainsTargetCountry(): Boolean {
val getLang = lang.uppercase()
var resp = false
when (true) {
(getLang == "FR") -> {
arrayListOf("FRENCH", "FRANCE").forEach {
if (this.uppercase().contains(findKeyWord(it))) {
resp = true
}
}
}
(getLang == "EN") -> {
arrayListOf("ENGLISH", "USA").forEach {
if (this.uppercase().contains(findKeyWord(it))) {
resp = true
}
}
}
(getLang == "AR") -> {
arrayListOf("ARABIC", "ARAB", "ARABIA").forEach {
if (this.uppercase().contains(findKeyWord(it))) {
resp = true
}
}
}
else -> resp = false
}
return resp
}
private fun cleanTitle(title: String): String {
return title.uppercase().replace("""(\s\d{1,}${'$'}|\s\d{1,}\s)""".toRegex(), " ")
.replace("""FHD""", "")
.replace(findKeyWord("VIP"), "")
.replace("""UHD""", "").replace(rgxcodeCountry, "").replace("""HEVC""", "")
.replace("""HDR""", "").replace("""SD""", "").replace("""4K""", "")
.replace("""HD""", "").replace(rgxcodeCountry, "").trim()
}
private fun getFlag(sequence: String): String {
val FR = findKeyWord("FR|FRANCE|FRENCH")
val US = findKeyWord("US|USA")
val AR = findKeyWord("AR|ARAB|ARABIC|ARABIA")
val UK = findKeyWord("UK")
val flag: String
flag = when (true) {
sequence.uppercase()
.contains(FR) -> "\uD83C\uDDE8\uD83C\uDDF5"
sequence.uppercase()
.contains(US) -> "\uD83C\uDDFA\uD83C\uDDF8"
sequence.uppercase()
.contains(UK) -> "\uD83C\uDDEC\uD83C\uDDE7"
sequence.uppercase()
.contains(AR) -> " نظرة"
else -> ""
}
return flag
}
val rgxcodeCountry = findKeyWord(codeCountry)
val arrayHomepage = arrayListOf<HomePageList>()
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
if (!init) {
val header = getAuthHeader()
val url_info =
"$mainUrl/portal.php?type=account_info&action=get_main_info&JsHttpRequest=1-xml"
val urlGetGenre =
"$mainUrl/portal.php?type=itv&action=get_genres&JsHttpRequest=1-xml"
val urlGetallchannels =
"$mainUrl/portal.php?type=itv&action=get_all_channels&JsHttpRequest=1-xml"
var reponseGetInfo: NiceResponse? = null
var responseGetgenre: NiceResponse? = null
var responseAllchannels: NiceResponse? = null
listOf(
url_info,
urlGetGenre,
urlGetallchannels
).apmap { url ->
val response = app.get(url, headers = header)
when (true) {
url.contains("action=get_main_info") -> {
reponseGetInfo = response
}
url.contains("action=get_genre") -> {
responseGetgenre = response
}
url.contains("action=get_all_channels") -> {
responseAllchannels = response
}
else -> {
""
}
}
}
///////////// GET EXPIRATION
val infoExpirationJson = reponseGetInfo!!.parsed<GetExpiration>()
val expiration = infoExpirationJson.js?.phone
////////////////////////// GET ALL GENRES
val responseGetGenretoJSON = responseGetgenre!!.parsed<JsonGetGenre>()
////////////////////////// GET ALL CHANNELS
val responseAllchannelstoJSON = responseAllchannels!!.parsed<Root>()
val AllchannelstoJSON = responseAllchannelstoJSON.js!!.data.sortByTitleNumber()
var firstCat = true
responseGetGenretoJSON.js.forEach { js ->
val idGenre = js.id
val categoryTitle = js.title.toString()
if (idGenre!!.contains("""\d""".toRegex()) && (categoryTitle.uppercase()
.contains(rgxcodeCountry) ||
categoryTitle.isContainsTargetCountry()
)
) {
AllchannelstoJSON.forEach { data ->
val genre = data.tvGenreId
if (genre != null) {
if (genre == idGenre) {
val name = data.name.toString()
val tv_genre_id = data.tvGenreId
val idCH = data.id
val link = "http://localhost/ch/$idCH" + "_"
val logo = data.logo?.replace("""\""", "")
val ch_id = data.cmds[0].chId
arraymediaPlaylist.add(
Channel(
name,
link,
logo,
"",
idCH,
tv_genre_id,
ch_id
)
)
}
}
}
}
/***************************************** */
val groupMedia = ArrayList<String>()
var b_new: String
var newgroupMedia: Boolean
val home = arraymediaPlaylist.mapNotNull { media ->
val b = cleanTitle(media.title)//
b_new = b.take(6)
newgroupMedia = true
for (nameMedia in groupMedia) {
if (nameMedia.contains(b_new) && (media.tv_genre_id == idGenre)) {
newgroupMedia = false
break
}
}
groupMedia.contains(b_new)
if (page == 1 && (media.tv_genre_id == idGenre) && newgroupMedia
) {
groupMedia.add(b_new)
val groupName = cleanTitle(media.title)
LiveSearchResponse(
groupName,
"$mainUrl/-${media.id}-",
name,
TvType.Live,
media.url_image,
)
} else {
null
}
}
val flag: String
if ((categoryTitle.uppercase()
.contains(rgxcodeCountry) || categoryTitle.isContainsTargetCountry())
) {
flag = getFlag(categoryTitle)
val nameGenre = if (firstCat) {
firstCat = false
"$flag ${cleanTitle(categoryTitle)} \uD83D\uDCFA $name \uD83D\uDCFA (⏳ $expiration)"
} else {
"$flag ${cleanTitle(categoryTitle)}"
}
arrayHomepage.add(HomePageList(nameGenre, home, isHorizontalImages = true))
}
}
}
return HomePageResponse(
arrayHomepage
)
}
private fun cleanTitleButKeepNumber(title: String): String {
return title.uppercase().replace("""FHD""", "")
.replace(findKeyWord("VIP"), "")
.replace("""UHD""", "").replace("""HEVC""", "")
.replace("""HDR""", "").replace("""SD""", "").replace("""4K""", "")
.replace("""HD""", "").replace(rgxcodeCountry, "").trim()
}
private fun ArrayList<Data>.sortByTitleNumber(): ArrayList<Data> {
val regxNbr = Regex("""(\s\d{1,}${'$'}|\s\d{1,}\s)""")
return ArrayList(this.sortedBy {
val str = it.name.toString()
regxNbr.find(str)?.groupValues?.get(0)?.trim()?.toInt() ?: 1000
})
}
}

View File

@ -0,0 +1,37 @@
package com.lagradost
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
import com.lagradost.cloudstream3.plugins.Plugin
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
import com.lagradost.cloudstream3.ui.settings.SettingsAccount
@CloudstreamPlugin
class MacIPTVProviderPlugin : Plugin() {
val iptvboxApi = MacIptvAPI(0)
override fun load(context: Context) {
// All providers should be added in this manner. Please don't edit the providers list directly.
iptvboxApi.init()
registerMainAPI(MacIPTVProvider("fr"))
registerMainAPI(MacIPTVProvider("en"))
registerMainAPI(MacIPTVProvider("ar"))
ioSafe {
iptvboxApi.initialize()
}
}
init {
this.openSettings = {
val activity = it as? AppCompatActivity
if (activity != null) {
val frag = MacIptvSettingsFragment(this, iptvboxApi)
frag.show(activity.supportFragmentManager, iptvboxApi.name)
}
}
}
}

View File

@ -0,0 +1,64 @@
package com.lagradost
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager
class MacIptvAPI(index: Int) : InAppAuthAPIManager(index) {
override val name = "Iptvbox"
override val idPrefix = "iptvbox"
override val icon = R.drawable.ic_baseline_extension_24
override val requiresUsername = true
override val requiresPassword = true
override val requiresServer = true
override val createAccountUrl = ""
companion object {
const val IPTVBOX_USER_KEY: String = "iptvbox_user"
}
override fun getLatestLoginData(): InAppAuthAPI.LoginData? {
return getKey(accountId, IPTVBOX_USER_KEY)
}
override fun loginInfo(): AuthAPI.LoginInfo? {
val data = getLatestLoginData() ?: return null
return AuthAPI.LoginInfo(name = data.username ?: data.server, accountIndex = accountIndex)
}
override suspend fun login(data: InAppAuthAPI.LoginData): Boolean {
if (data.server.isNullOrBlank()) return false // we require a server
switchToNewAccount()
setKey(accountId, IPTVBOX_USER_KEY, data)
registerAccount()
initialize()
AccountManager.inAppAuths
return true
}
override fun logOut() {
removeAccountKeys()
initializeData()
}
private fun initializeData() {
val data = getLatestLoginData() ?: run {
MacIPTVProvider.overrideUrl = null
MacIPTVProvider.loginMac = null
MacIPTVProvider.companionName = null
return
}
MacIPTVProvider.overrideUrl = data.server?.removeSuffix("/")
MacIPTVProvider.loginMac = data.password ?: ""
MacIPTVProvider.companionName = data.username
}
override suspend fun initialize() {
initializeData()
}
}

View File

@ -0,0 +1,93 @@
package com.lagradost
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import com.lagradost.cloudstream3.R
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
import com.lagradost.cloudstream3.plugins.Plugin
import com.lagradost.cloudstream3.ui.settings.SettingsAccount.Companion.showLoginInfo
import com.lagradost.cloudstream3.ui.settings.SettingsAccount.Companion.addAccount
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
class MacIptvSettingsFragment(private val plugin: Plugin, val maciptvAPI: MacIptvAPI) :
BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val id = plugin.resources!!.getIdentifier("nginx_settings", "layout", BuildConfig.LIBRARY_PACKAGE_NAME)
val layout = plugin.resources!!.getLayout(id)
return inflater.inflate(layout, container, false)
}
private fun <T : View> View.findView(name: String): T {
val id = plugin.resources!!.getIdentifier(name, "id", BuildConfig.LIBRARY_PACKAGE_NAME)
return this.findViewById(id)
}
private fun getDrawable(name: String): Drawable? {
val id = plugin.resources!!.getIdentifier(name, "drawable", BuildConfig.LIBRARY_PACKAGE_NAME)
return ResourcesCompat.getDrawable(plugin.resources!!, id, null)
}
private fun getString(name: String): String? {
val id = plugin.resources!!.getIdentifier(name, "string", BuildConfig.LIBRARY_PACKAGE_NAME)
return plugin.resources!!.getString(id)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val infoView = view.findView<LinearLayout>("nginx_info")
val infoTextView = view.findView<TextView>("info_main_text")
val infoSubTextView = view.findView<TextView>("info_sub_text")
val infoImageView = view.findView<ImageView>("nginx_info_imageview")
infoTextView.text = getString("nginx_info_title") ?: "Nginx"
infoSubTextView.text = getString("nginx_info_summary") ?: ""
infoImageView.setImageDrawable(getDrawable("nginx_question"))
infoImageView.imageTintList =
ColorStateList.valueOf(view.context.colorFromAttribute(R.attr.white))
val loginView = view.findView<LinearLayout>("nginx_login")
val loginTextView = view.findView<TextView>("main_text")
val loginImageView = view.findView<ImageView>("nginx_login_imageview")
loginImageView.setImageDrawable(getDrawable("nginx"))
loginImageView.imageTintList =
ColorStateList.valueOf(view.context.colorFromAttribute(R.attr.white))
// object : View.OnClickListener is required to make it compile because otherwise it used invoke-customs
infoView.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
openBrowser(maciptvAPI.createAccountUrl)
}
})
loginTextView.text = view.context.resources.getString(R.string.login_format).format(
maciptvAPI.name,
view.context.resources.getString(R.string.account))
loginView.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
val info = maciptvAPI.loginInfo()
if (info != null) {
showLoginInfo(activity, maciptvAPI, info)
} else {
addAccount(activity, maciptvAPI)
}
}
})
}
}

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="283"
android:viewportHeight="283">
<path
android:name="path"
android:pathData="M 253.41 62.61 L 154.22 5.34 C 150.42 3.146 146.108 1.991 141.72 1.991 C 137.332 1.991 133.02 3.146 129.22 5.34 L 30 62.61 C 26.202 64.807 23.049 67.966 20.858 71.768 C 18.668 75.57 17.516 79.882 17.52 84.27 L 17.52 198.8 C 17.516 203.188 18.668 207.5 20.858 211.302 C 23.049 215.104 26.202 218.263 30 220.46 L 129.19 277.72 C 132.99 279.914 137.302 281.069 141.69 281.069 C 146.078 281.069 150.39 279.914 154.19 277.72 L 253.38 220.46 C 257.183 218.266 260.343 215.109 262.539 211.307 C 264.735 207.505 265.891 203.191 265.89 198.8 L 265.89 84.27 C 265.894 79.882 264.742 75.57 262.552 71.768 C 260.361 67.966 257.208 64.807 253.41 62.61 Z M 203.28 185.33 Q 203.28 200.61 187.03 200.61 C 184.56 200.637 182.098 200.331 179.71 199.7 C 177.529 199.086 175.467 198.109 173.61 196.81 C 171.687 195.463 169.917 193.91 168.33 192.18 Q 165.9 189.52 163.45 186.76 L 106.86 119.16 L 106.86 187.16 Q 106.86 193.81 102.86 197.22 C 100.004 199.558 96.388 200.768 92.7 200.62 Q 86.3 200.62 82.44 197.18 Q 78.58 193.74 78.58 187.18 L 78.58 97.63 C 78.438 94.563 78.992 91.503 80.2 88.68 C 81.685 86.126 83.925 84.093 86.61 82.86 C 89.603 81.356 92.911 80.585 96.26 80.61 C 98.633 80.541 101.001 80.879 103.26 81.61 C 105.096 82.243 106.813 83.179 108.34 84.38 C 109.979 85.728 111.477 87.239 112.81 88.89 C 114.33 90.74 115.91 92.66 117.53 94.67 L 175.53 163.06 L 175.53 94.06 Q 175.53 87.34 179.24 83.97 Q 182.95 80.6 189.24 80.61 C 193.57 80.61 197 81.73 199.5 83.97 C 202 86.21 203.26 89.58 203.26 94.06 Z"
android:fillColor="#000"
android:strokeWidth="1" />
</vector>

View File

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="35dp"
android:height="26dp"
android:viewportWidth="379"
android:viewportHeight="279">
<path
android:name="path"
android:pathData="M 235.89 60.62 L 136.7 3.35 C 132.9 1.156 128.588 0.001 124.2 0.001 C 119.812 0.001 115.5 1.156 111.7 3.35 L 12.48 60.62 C 8.682 62.817 5.529 65.976 3.338 69.778 C 1.148 73.58 -0.004 77.892 0 82.28 L 0 196.81 C -0.004 201.198 1.148 205.51 3.338 209.312 C 5.529 213.114 8.682 216.273 12.48 218.47 L 111.67 275.73 C 115.47 277.924 119.782 279.079 124.17 279.079 C 128.558 279.079 132.87 277.924 136.67 275.73 L 235.86 218.47 C 239.663 216.276 242.823 213.119 245.019 209.317 C 247.215 205.515 248.371 201.201 248.37 196.81 L 248.37 82.28 C 248.374 77.892 247.222 73.58 245.032 69.778 C 242.841 65.976 239.688 62.817 235.89 60.62 Z M 185.76 183.34 Q 185.76 198.62 169.51 198.62 C 167.04 198.647 164.578 198.341 162.19 197.71 C 160.009 197.096 157.947 196.119 156.09 194.82 C 154.167 193.473 152.397 191.92 150.81 190.19 Q 148.38 187.53 145.93 184.77 L 89.34 117.17 L 89.34 185.17 Q 89.34 191.82 85.34 195.23 C 82.484 197.568 78.868 198.778 75.18 198.63 Q 68.78 198.63 64.92 195.19 Q 61.06 191.75 61.06 185.19 L 61.06 95.64 C 60.918 92.573 61.472 89.513 62.68 86.69 C 64.165 84.136 66.405 82.103 69.09 80.87 C 72.083 79.366 75.391 78.595 78.74 78.62 C 81.113 78.551 83.481 78.889 85.74 79.62 C 87.576 80.253 89.293 81.189 90.82 82.39 C 92.459 83.738 93.957 85.249 95.29 86.9 C 96.81 88.75 98.39 90.67 100.01 92.68 L 158.01 161.07 L 158.01 92.07 Q 158.01 85.35 161.72 81.98 Q 165.43 78.61 171.72 78.62 C 176.05 78.62 179.48 79.74 181.98 81.98 C 184.48 84.22 185.74 87.59 185.74 92.07 Z"
android:fillColor="#000"
android:strokeWidth="1" />
<path
android:name="path_1"
android:pathData="M 312.84 143.37 C 320.84 128.98 336.13 120.49 345.04 107.75 C 354.48 94.4 349.18 69.45 322.48 69.45 C 304.98 69.45 296.39 82.7 292.77 93.67 L 265.94 82.39 C 273.29 60.39 293.27 41.39 322.37 41.39 C 346.69 41.39 363.37 52.47 371.85 66.34 C 379.1 78.25 383.34 100.51 372.16 117.07 C 359.74 135.39 347.83 140.98 341.41 152.79 C 338.83 157.55 337.79 160.65 337.79 175.98 L 307.87 175.98 C 307.77 167.9 306.53 154.75 312.84 143.37 Z M 343.17 217.37 C 343.175 222.063 341.584 226.62 338.661 230.291 C 335.737 233.962 331.651 236.533 327.077 237.579 C 322.502 238.625 317.705 238.086 313.477 236.05 C 309.249 234.015 305.835 230.601 303.8 226.373 C 301.764 222.145 301.225 217.348 302.271 212.773 C 303.317 208.199 305.888 204.113 309.559 201.189 C 313.23 198.266 317.787 196.675 322.48 196.68 C 327.963 196.698 333.221 198.888 337.097 202.767 C 340.972 206.646 343.157 211.907 343.17 217.39 Z"
android:fillColor="#ffffff"
android:strokeWidth="1" />
</vector>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/settings_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/nginx_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:id="@+id/nginx_login_imageview"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="start|center_vertical"
android:layout_marginEnd="20dp"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<!-- <LinearLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:orientation="horizontal">-->
<TextView
android:id="@+id/main_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<!-- </LinearLayout>-->
<!-- <TextView-->
<!-- android:id="@+id/sub_text"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="@string/nginx_info_summary"-->
<!-- android:textSize="12sp" />-->
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/nginx_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:id="@+id/nginx_info_imageview"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="start|center_vertical"
android:layout_marginEnd="20dp"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/info_main_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
</LinearLayout>
<TextView
android:id="@+id/info_sub_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">O que é Nginx ?</string>
<string name="nginx_info_summary">Nginx é um software que pode ser usado para exibir arquivos de um servidor que você possui. Clique para ver um guia de configuração do Nginx</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Co je to Nginx?</string>
<string name="nginx_info_summary">Nginx je software, který může být použit ke zobrazení souborů na serveru, který vlastníte. Klikněte pro zobrazení návodu k nastavení Nginx</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">¿Qué es Nginx?</string>
<string name="nginx_info_summary">Nginx es un software que se puede usar para mostrar archivos de un servidor de su propiedad. Pulse para ver una guía de configuración de Nginx</string>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">C\'est quoi Maciptv ?</string>
<string name="example_username">NomIPTV</string>
<string name="nginx_info_summary">Maciptv est un emulateur qui va vous permettre de profiter de vos comptes IPTV. Donner l\'url portail du fournisseur http:\/\/exemple.com\/c\/ et votre mac 00:1A:79:XX:XX:XX comme password123. Pour sélectionner/revenir au compte par défaut, créer un compte avec \'none\' à la place de \'127.0.0.1\' et pas besoin de renseigner les autres infos</string>
<string name="nginx_url_pref">Addresse du serveur IPTV</string>
<string name="example_password">00:1A:79:XX:XX</string>
<string name="example_ip">http:\/\/exemple\-portal.com:8080\/c\/</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Apa itu Nginx ?</string>
<string name="nginx_info_summary">Nginx adalah sebuah software yang dapat digunakan untuk menampilkan file dari server yang anda miliki. Klik untuk melihat panduan pengaturan Nginx</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Cos\'è Nginx?</string>
<string name="nginx_info_summary">Nginx è un software che può essere utilizzato per visualizzare i file da un server di proprietà. Fare clic per vedere la guida all\'installazione di Nginx</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Wat is Nginx ?</string>
<string name="nginx_info_summary">Nginx is een software die kan worden gebruikt om bestanden weer te geven van een server die u bezit. Klik om een Nginx installatiegids te zien</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Co to Nginx?</string>
<string name="nginx_info_summary">Nginx to oprogramowanie, które może być używane do wyświetlania plików z serwera, którego jesteś właścicielem. Kliknij, aby zobaczyć przewodnik konfiguracji Nginx</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Ce este Nginx?</string>
<string name="nginx_info_summary">Nginx este un software care poate fi utilizat pentru a vizualiza fișiere de pe un server proprietar. Faceți clic pentru a vedea ghidul de instalare Nginx</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Nginx nedir?</string>
<string name="nginx_info_summary">Nginx, sahip olduğunuz bir sunucudan dosyaları görüntülemek için kullanılabilecek bir yazılımdır. Nginx kurulum kılavuzunu görmek için tıklayın</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">Nginx là gì?</string>
<string name="nginx_info_summary">Nginx được dùng để hiển thị các tệp từ máy chủ mà bạn sở hữu. Chọn để xem hướng dẫn thiết lập Nginx</string>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nginx_info_title">What is Maciptv ?</string>
<string name="example_password">00:1A:79:XX:XX</string>
<string name="example_ip">http:\/\/exemple\-portal.com:8080\/c\/</string>
<string name="example_username">MyCoolIPTVname</string>
<string name="nginx_info_summary">Maciptv is an extension that will allow you to enjoy your IPTV accounts. Give the provider\'s portal url http:\/\/exemple.com\/c\/ and your mac 00:1A:79:XX:XX as password123. To select/revert to the default account, create an account with \'none\' instead of \'127.0.0.1\' and no need to fill in the other info</string>
</resources>