diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 5ab95416..4859511a 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -5,6 +5,7 @@ on: branches: [ master ] paths-ignore: - '*.md' + - '*.json' concurrency: group: "pre-release" diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt index 326fd8c2..218650da 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AkwamProvider.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.movieproviders import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addActors -import com.lagradost.cloudstream3.network.AppResponse import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import org.jsoup.nodes.Element @@ -165,10 +164,10 @@ class AkwamProvider : MainAPI() { } - // Maybe possible to not use the url shortener but cba investigating that. - private suspend fun skipUrlShortener(url: String): AppResponse { - return app.get(app.get(url).document.select("a.download-link").attr("href")) - } +// // Maybe possible to not use the url shortener but cba investigating that. +// private suspend fun skipUrlShortener(url: String): AppResponse { +// return app.get(app.get(url).document.select("a.download-link").attr("href")) +// } private fun getQualityFromId(id: Int?): Qualities { return when (id) { @@ -190,14 +189,15 @@ class AkwamProvider : MainAPI() { val links = doc.select("div.tab-content.quality").map { val quality = getQualityFromId(it.attr("id").getIntFromText()) - it.select(".col-lg-6 > a").map { linkElement -> - linkElement.attr("href") to quality - // Only uses the download links, primarily to prevent unnecessary duplicate requests. - }.filter { link -> link.first.contains("/link/") } + it.select(".col-lg-6 > a:contains(تحميل)").map { linkElement -> + if(linkElement.attr("href").contains("/download/")) { linkElement.attr("href") to quality } else { + "$mainUrl/download${linkElement.attr("href").split("/link")[1]}${data.split("/movie|/episode|/show/episode".toRegex())[1]}" to quality // just in case if they add the shorts urls again + } + } }.flatten() links.map { - val linkDoc = skipUrlShortener(it.first).document + val linkDoc = app.get(it.first).document val button = linkDoc.select("div.btn-loader > a") val url = button.attr("href") diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt index 25b6e23c..3e65710f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MyCimaProvider.kt @@ -13,7 +13,7 @@ class MyCimaProvider : MainAPI() { override var name = "MyCima" override val usesWebView = false override val hasMainPage = true - override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie) + override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie, TvType.Anime) private fun String.getImageURL(): String? { return this.replace("--im(age|g):url\\(|\\);".toRegex(), "") @@ -63,9 +63,10 @@ class MyCimaProvider : MainAPI() { override suspend fun search(query: String): List { val q = query.replace(" ","%20") val result = arrayListOf() - listOf("$mainUrl/search/$q", "$mainUrl/search/$q/list/series/").apmap { url -> + listOf("$mainUrl/search/$q", + "$mainUrl/search/$q/list/series/", + "$mainUrl/search/$q/list/anime/").apmap { url -> val d = app.get(url).document - if(d.select("a.hoverable.active").text().contains("الانيمي و الكرتون")) return@apmap null d.select("div.Grid--MycimaPosts div.GridItem").mapNotNull { if(it.text().contains("اعلان")) return@mapNotNull null it.toSearchResponse()?.let { it1 -> result.add(it1) } @@ -88,7 +89,7 @@ class MyCimaProvider : MainAPI() { val year = doc.select("div.Title--Content--Single-begin h1 a.unline")?.text()?.getIntFromText() val title = doc.select("div.Title--Content--Single-begin h1").text() .replace("($year)", "") - .replace("مشاهدة|فيلم|مسلسل|مترجم".toRegex(), "") + .replace("مشاهدة|فيلم|مسلسل|مترجم|انمي".toRegex(), "") // A bit iffy to parse twice like this, but it'll do. val duration = doc.select("ul.Terms--Content--Single-begin li").firstOrNull { @@ -106,6 +107,9 @@ class MyCimaProvider : MainAPI() { ?: return@mapNotNull null Actor(name, image) } + val recommendations = doc.select("div.Grid--MycimaPosts div.GridItem")?.mapNotNull { element -> + element.toSearchResponse() + } return if (isMovie) { newMovieLoadResponse( @@ -119,6 +123,7 @@ class MyCimaProvider : MainAPI() { this.plot = synopsis this.tags = tags this.duration = duration + this.recommendations = recommendations addActors(actors) } } else { @@ -133,14 +138,16 @@ class MyCimaProvider : MainAPI() { if(moreButton.isNotEmpty()) { val n = doc.select("div.Seasons--Episodes div.Episodes--Seasons--Episodes a").size val totals = doc.select("div.Episodes--Seasons--Episodes a").first().text().getIntFromText() - arrayListOf(n, n+40, n+80, n+120, n+160, n+200, n+240, n+280, n+320, n+360) - .apmap { it -> - if(it > totals!!) return@apmap - val ajaxURL = "$mainUrl/AjaxCenter/MoreEpisodes/${moreButton.attr("data-term")}/$it" - val jsonResponse = app.get(ajaxURL) - val json = parseJson(jsonResponse.text) - val document = Jsoup.parse(json.output?.replace("""\""", "")) - document.select("a").map { episodes.add(TvSeriesEpisode(it.text(), season, it.text().getIntFromText(), it.attr("href"), null, null)) } + val mEPS = arrayListOf(n, n+40, n+80, n+120, n+160, n+200, n+240, n+280, n+320, n+360, n+400, n+440, n+480, n+520, n+660, n+700, n+740, n+780, n+820, n+860, n+900, n+940, n+980, n+1020, n+1060, n+1100, n+1140, n+1180, n+1220, totals) + mEPS.apmap { it -> + if (it != null) { + if(it > totals!!) return@apmap + val ajaxURL = "$mainUrl/AjaxCenter/MoreEpisodes/${moreButton.attr("data-term")}/$it" + val jsonResponse = app.get(ajaxURL) + val json = parseJson(jsonResponse.text) + val document = Jsoup.parse(json.output?.replace("""\""", "")) + document.select("a").map { episodes.add(TvSeriesEpisode(it.text(), season, it.text().getIntFromText(), it.attr("href"), null, null)) } + } } } if(seasons.isNotEmpty()) { @@ -154,14 +161,16 @@ class MyCimaProvider : MainAPI() { if(fmoreButton.isNotEmpty()) { val n = seasonsite.select("div.Seasons--Episodes div.Episodes--Seasons--Episodes a").size val totals = seasonsite.select("div.Episodes--Seasons--Episodes a").first().text().getIntFromText() - arrayListOf(n, n+40, n+80, n+120, n+160, n+200, n+240, n+280, n+320, n+360) - .apmap { it -> - if(it > totals!!) return@apmap - val ajaxURL = "$mainUrl/AjaxCenter/MoreEpisodes/${fmoreButton.attr("data-term")}/$it" - val jsonResponse = app.get(ajaxURL) - val json = parseJson(jsonResponse.text) - val document = Jsoup.parse(json.output?.replace("""\""", "")) - document.select("a").map { episodes.add(TvSeriesEpisode(it.text(), fseason, it.text().getIntFromText(), it.attr("href"), null, null)) } + val mEPS = arrayListOf(n, n+40, n+80, n+120, n+160, n+200, n+240, n+280, n+320, n+360, n+400, n+440, n+480, n+520, n+660, n+700, n+740, n+780, n+820, n+860, n+900, n+940, n+980, n+1020, n+1060, n+1100, n+1140, n+1180, n+1220, totals) + mEPS.apmap { it -> + if (it != null) { + if(it > totals!!) return@apmap + val ajaxURL = "$mainUrl/AjaxCenter/MoreEpisodes/${fmoreButton.attr("data-term")}/$it" + val jsonResponse = app.get(ajaxURL) + val json = parseJson(jsonResponse.text) + val document = Jsoup.parse(json.output?.replace("""\""", "")) + document.select("a").map { episodes.add(TvSeriesEpisode(it.text(), fseason, it.text().getIntFromText(), it.attr("href"), null, null)) } + } } } else return@apmap } @@ -172,6 +181,7 @@ class MyCimaProvider : MainAPI() { this.tags = tags this.year = year this.plot = synopsis + this.recommendations = recommendations addActors(actors) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SoaptwoDayProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SoaptwoDayProvider.kt index 6730f15e..90220961 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SoaptwoDayProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SoaptwoDayProvider.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.parseJson +import org.jsoup.Jsoup import java.util.* class SoaptwoDayProvider:MainAPI() { @@ -67,20 +68,44 @@ class SoaptwoDayProvider:MainAPI() { } override suspend fun load(url: String): LoadResponse? { - val soup = app.get(url, timeout = 120).document - val title = soup.selectFirst(".hidden-lg > div:nth-child(1) > h4").text() + val soup = app.get(url).document + val title = soup.selectFirst(".hidden-lg > div:nth-child(1) > h4")?.text() ?: "" val description = soup.selectFirst("p#wrap")?.text()?.trim() - val poster = soup.selectFirst(".col-md-5 > div:nth-child(1) > div:nth-child(1) > img").attr("src") - val episodes = soup.select("div.alert > div > div > a").map { - val link = it.attr("href") - val name = it.text().replace(Regex("(^(\\d+)\\.)"),"") + val poster = soup.selectFirst(".col-md-5 > div:nth-child(1) > div:nth-child(1) > img")?.attr("src") + val episodes = soup.select("div.alert > div > div > a").mapNotNull { + val link = fixUrlNull(it?.attr("href")) ?: return@mapNotNull null + val name = it?.text()?.replace(Regex("(^(\\d+)\\.)"),"") TvSeriesEpisode( - name, - null, - null, - fixUrl(link) + name = name, + data = link ) } + val otherInfoBody = soup.select("div.col-sm-8 div.panel-body")?.toString() + //Fetch casts + val casts = otherInfoBody?.substringAfter("Stars : ") + ?.substringBefore("Genre : ")?.let { + Jsoup.parse(it)?.select("a") + }?.mapNotNull { + val castName = it?.text() ?: return@mapNotNull null + ActorData( + Actor( + name = castName + ) + ) + } + //Fetch year + val year = otherInfoBody?.substringAfter("

Release :

") + ?.substringBefore(" year string: $it") + Jsoup.parse(it)?.select("p")?.get(1) + }?.text()?.take(4)?.toIntOrNull() + //Fetch genres + val genre = otherInfoBody?.substringAfter("

Genre :

") + ?.substringBefore("

Release :

")?.let { + //Log.i(this.name, "Result => genre string: $it") + Jsoup.parse(it)?.select("a") + }?.mapNotNull { it?.text()?.trim() ?: return@mapNotNull null } + val tvType = if (episodes.isEmpty()) TvType.Movie else TvType.TvSeries return when (tvType) { TvType.TvSeries -> { @@ -90,9 +115,11 @@ class SoaptwoDayProvider:MainAPI() { this.name, tvType, episodes.reversed(), - fixUrl(poster), - null, + fixUrlNull(poster), + year = year, description, + actors = casts, + tags = genre ) } TvType.Movie -> { @@ -102,9 +129,11 @@ class SoaptwoDayProvider:MainAPI() { this.name, tvType, url, - fixUrl(poster), - null, + fixUrlNull(poster), + year = year, description, + actors = casts, + tags = genre ) } else -> null @@ -181,10 +210,10 @@ class SoaptwoDayProvider:MainAPI() { ).text.replace("\\\"","\"").replace("\"{","{").replace("}\"","}") .replace("\\\\\\/","\\/") val json = parseJson(url) - listOf( + listOfNotNull( json.stream, json.streambackup - ).filterNotNull().apmap { stream -> + ).apmap { stream -> val cleanstreamurl = stream.replace("\\/","/").replace("\\\\\\","") if (cleanstreamurl.isNotBlank()) { callback(ExtractorLink( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 05139957..8f81e12c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -140,6 +140,7 @@ class SettingsFragment : PreferenceFragmentCompat() { Triple("\ud83c\udde7\ud83c\uddf7", "Portuguese (Brazil)", "pt"), Triple("\ud83c\uddf7\ud83c\uddf4", "Romanian", "ro"), Triple("\uD83C\uDDEE\uD83C\uDDF9", "Italian", "it"), + Triple("\uD83C\uDDE8\uD83C\uDDF3", "Chinese", "cn"), ).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top private fun showAccountSwitch(context: Context, api: AccountManager) { diff --git a/app/src/main/res/values-cn/strings.xml b/app/src/main/res/values-cn/strings.xml new file mode 100644 index 00000000..be0d8475 --- /dev/null +++ b/app/src/main/res/values-cn/strings.xml @@ -0,0 +1,430 @@ + + + + search_providers_list + app_locale + search_type_list + grid_format + auto_update + prerelease_update + manual_check_update + fast_forward_button_time + benene_count + subtitle_settings_key + subtitle_settings_chromecast_key + quality_pref_key + video_buffer_size_key + video_buffer_length_key + video_buffer_clear_key + video_buffer_disk_key + unknown_prerelease + use_system_brightness_key + swipe_enabled_key + playback_speed_enabled_key + player_resize_enabled_key + pip_enabled_key + double_tap_enabled_key + double_tap_pause_enabled_key + swipe_vertical_enabled_key + display_sub_key + show_fillers_key + provider_lang_key + dns_key + download_path_key + Cloudstream + app_layout_key + primary_color_key + restore_key + killswitch_key + backup_key + prefer_media_type_key + app_theme_key + + + + %d %s | %sMB + %s • %sGB + %sMB / %sMB + %dMB + %s %s + +%d + -%d + %d + %d + %.1f/10.0 + %d + %s 共%d集 + 演员:%s + + + 海报 + @string/result_poster_img_des + 剧集海报 + 主海报 + 随机下一个 + @string/play_episode + 返回 + @string/home_change_provider_img_des + 更改内容提供者 + 预览背景 + + + 速度(%.2fx) + 评分:%.1f + 发现新版本!\n%s -> %s + 填充 + %d分钟 + + CloudStream + 主页 + 搜索 + 下载 + 设置 + + 搜索… + 无数据 + 更多选项 + 下一集 + @string/synopsis + 类型 + 分享 + 在浏览器中打开 + 跳过加载 + 正在加载… + + 正在观看 + 暂时搁置 + 观看完毕 + 放弃观看 + 计划观看 + + 重新观看 + + 播放电影 + 串流Torrent + 来源 + 字幕 + 重试连接… + 返回 + 播放剧集 + + + 下载 + 已下载 + 正在下载 + 下载暂停 + 下载开始 + 下载失败 + 下载取消 + 下载完毕 + %s - %s + + 加载链接时出错 + 内部存储 + + 配音 + 字幕 + + 删除文件 + 播放文件 + 继续下载 + 暂停下载 + + 禁用自动错误报告 + 更多信息 + 隐藏 + 播放 + 信息 + 筛选书签 + 书签 + 移除 + 设置观看状态 + 应用 + 取消 + 播放速度 + + 字幕设置 + 文本颜色 + 轮廓颜色 + 背景颜色 + 窗口颜色 + 边缘类型 + 字幕高度 + 字体 + 字体大小 + + 按内容提供者搜索 + 按类型搜索 + + 送给开发者%d根香蕉 + 不送香蕉 + + 自动选择语言 + 下载语言 + 按住重置为默认值 + 继续观看 + + 移除 + 更多信息 + @string/home_play + + 此内容提供者可能需要VPN才能正常工作 + 此内容提供者是一个Torrent,建议使用VPN + + 站点不提供元数据,如果站点上不存在元数据,视频加载将失败。 + + 简介 + 没有找到简介 + 没有找到简介 + + 画中画 + 在其他应用之上的迷你播放器中继续播放 + 播放器画面调整按钮 + 移除黑色边框 + 字幕 + 播放器字幕设置 + 投屏字幕 + 投屏字幕设置 + + 本征模式 + 在播放器中添加播放速度选项 + 滑动控制进度 + 向屏幕左侧或右侧滑动来控制播放进度 + 滑动更改设置 + 在屏幕左侧或右侧滑动来更改亮度或音量 + 双击控制进度 + 双击暂停 + 在屏幕左侧或右侧双击来快进或快退 + + 双击屏幕中间暂停 + 使用系统亮度 + 在app player中使用系统亮度,而不是黑色遮罩 + + + 从备份中恢复数据 + 从github下载最新元数据 + 如果你想访问所有的内容提供者(即使是损坏的),请关闭此选项 + + 备份数据 + 已加载备份文件 + 无法从文件%s中还原数据 + 成功存储数据 + 缺少存储权限,请重试 + 备份%s时出错 + + 搜索 + 信息 + 高级搜索 + 给出按内容提供者分隔的搜索结果 + 只发送关于崩溃的数据 + 不发送数据 + 为动画显示下一集 + 显示应用更新 + 启动时自动搜索更新 + 更新至预览版 + 搜索预览版更新,而不是仅搜索完整版本 + Github + 由同一开发者开发的轻小说应用 + 由同一开发者开发的动漫应用 + 加入Discord + 送给开发者一根香蕉 + 送香蕉 + + 应用语言 + + 该内容提供者不支持投屏 + 没有找到链接 + 链接已复制到剪贴板 + 播放剧集 + 重置为默认值 + 抱歉,应用崩溃了,将向开发人员发送一份匿名Bug报告 + + + + 没有季 + + + S + E + 没有找到剧集 + + 删除文件 + 删除 + @string/sort_cancel + 暂停 + 继续 + -30 + +30 + 这将永久删除%s\n你确定吗? + 剩余%d分钟\n + + + 连载中 + 已完结 + 状态 + 年份 + 评分 + 持续时间 + 网站 + 简介 + + 队列 + 无字幕 + 默认 + + 空闲 + 已使用 + 应用 + + + 电影 + 剧集 + 卡通 + 动漫 + Torrents + 纪录片 + OVA + + + 电影 + 剧集 + 卡通 + @string/anime + @string/ova + Torrent + 纪录片 + + 源错误 + 远程错误 + 渲染器错误 + 意外的播放器错误 + 下载错误,请检查存储权限 + + 投屏剧集 + 投屏镜像 + 在应用程序中播放 + 在VLC中播放 + 在浏览器中播放 + 复制链接 + 自动下载 + 下载镜像 + 重新加载链接 + + 找不到更新 + 检查更新 + + 锁定 + 调整画面 + 来源 + 跳过OP + + 不再显示 + 更新 + 首选播放质量 + 视频缓冲大小 + 视频缓冲时长 + 磁盘上的视频缓存 + 清除视频和图像缓存 + + 如果设置太高,可能会在内存较低的系统(如Android TV设备或旧手机)上导致问题 + 如果将存储空间设置得太高,可能会导致Android TV设备等存储空间不足的系统出现问题 + + DNS over HTTPS + 用于忽略ISP块 + + 下载路径 + + 显示有配音/字幕的动画 + + 适应屏幕 + 拉伸 + 缩放 + + 免责声明 + legal_notice_key + Any legal issues regarding the content on this application + should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the + responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use + CloudStream 3 at your own risk. + + 全局 + 内容提供者语言 + 应用布局 + 首选媒体 + + 自动 + 电视布局 + 手机布局 + 模拟器布局 + + 主题色 + 应用主题 + + + anilist_key + mal_key + + %s %s + 账户 + 注销 + 登录 + 切换账户 + 添加帐户 + 添加跟踪 + 已添加%s + 同步 + + + + 普通 + 全部 + 最大 + 最小 + @string/none + 轮廓 + 降低 + 阴影 + 提高 + 同步字幕 + 1000ms + 字幕延迟 + 如果字幕过早显示%dms,请使用此选项 + 如果字幕过晚显示%dms,请使用此选项 + 无字幕延迟 + + + 一只敏捷的棕色狐狸跳过一只懒惰的狗 + + 推荐 + 已加载%s + 从文件加载 + 下载的文件 + 主演 + 配演 + 群演 + + 来源 + diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt new file mode 100644 index 00000000..1f437a99 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/full_description.txt @@ -0,0 +1,14 @@ +CloudStream-3可以让你串流和下载电影、剧集和动漫。这款应用没有任何广告和分析。它支持多个预告片和电影网站等。特点包括: + + + +书签 + + +下载和串流电影、电视节目和动漫 + + +下载字幕 + + +支持投屏 diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt new file mode 100644 index 00000000..c31c1282 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/short_description.txt @@ -0,0 +1 @@ +串流和下载电影、剧集和动漫。