Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix(It/StreamingCommunity): Fix empty videolist and change base domain #3103

Merged
merged 9 commits into from
Mar 29, 2024
2 changes: 1 addition & 1 deletion src/it/streamingcommunity/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ext {
extName = 'StreamingCommunity'
extClass = '.StreamingCommunity'
extVersionCode = 3
extVersionCode = 4
}

apply from: "$rootDir/common.gradle"
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
Expand All @@ -29,14 +30,14 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {

override val name = "StreamingCommunity"

// TODO: Check frequency of url changes to potentially
// add back overridable baseurl preference
override val baseUrl = "https://streamingcommunity.report"
override val baseUrl = "https://streamingcommunity.forum"

override val lang = "it"

override val supportsLatest = true

override val client: OkHttpClient = network.client

private val json: Json by injectLazy()

private val preferences: SharedPreferences by lazy {
Expand Down Expand Up @@ -240,7 +241,7 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
SEpisode.create().apply {
name = "Stagione ${season.number} episodio ${episode.number} - ${episode.name}"
episode_number = episode.number.toFloat()
url = "${data.title.id}?e=${episode.id}"
url = "${data.title.id}?episode_id=${episode.id}&next_episode=1"
},
)
}
Expand All @@ -252,68 +253,83 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {

// ============================ Video Links =============================

override fun videoListRequest(episode: SEpisode): Request = GET("$baseUrl/watch/${episode.url}", headers)
interface VideoProvider {
fun getVideoList(): List<Video>
}
Dark25 marked this conversation as resolved.
Show resolved Hide resolved

override fun videoListParse(response: Response): List<Video> {
val data = json.decodeFromString<VideoResponse>(response.asJsoup().getData())
override suspend fun getVideoList(episode: SEpisode): List<Video> {
val videoList = mutableListOf<Video>()
val doc =
client
.newCall(
GET("$baseUrl/iframe/${episode.url}", headers),
).execute()
.asJsoup()
val iframeUrl =
doc.selectFirst("iframe[src]")?.attr("abs:src")
?: error("Failed to extract iframe")
val iframeHeaders =
headers
.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", iframeUrl.toHttpUrl().host)
.add("Referer", "$baseUrl/")
.build()

val embedUrl = data.props.embedUrl
val embedHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", baseUrl.toHttpUrl().host)
.add("Referer", response.request.url.toString())
.build()

val embedded = client.newCall(
GET(embedUrl, headers = embedHeaders),
).execute().asJsoup()

val iframeUrl = embedded.selectFirst("iframe[src]")?.attr("abs:src") ?: error("Failed to load iframe")
val iframeHeaders = headers.newBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Host", iframeUrl.toHttpUrl().host)
.add("Referer", "$baseUrl/")
.build()

val iframe = client.newCall(
GET(iframeUrl, headers = iframeHeaders),
).execute().asJsoup()
val script = iframe.selectFirst("script:containsData(masterPlaylistParams)")!!.data()

val playlistUrl = Regex("""masterPlaylistUrl.*?'(.*?)'""").find(script)!!.groupValues[1]
val expires = Regex("""'expires': ?'(\d+)'""").find(script)!!.groupValues[1]
val canCast = Regex("""'canCast': ?'(\d*)'""").find(script)!!.groupValues[1]
val token = Regex("""'token': ?'([\w-]+)'""").find(script)!!.groupValues[1]
val iframe =
client
.newCall(
GET(iframeUrl, headers = iframeHeaders),
).execute()
.asJsoup()
val script = iframe.selectFirst("script:containsData(masterPlaylist)")!!.data().replace("\n", "\t")
var playlistUrl = PLAYLIST_URL_REGEX.find(script)!!.groupValues[1]
val filename = playlistUrl.substringAfterLast("/")
if (!filename.endsWith(".m3u8")) {
playlistUrl = playlistUrl.replace(filename, filename + ".m3u8")
}

// Get subtitles
val masterPlUrl = "$playlistUrl?token=$token&expires=$expires&canCast=$canCast&n=1"
val masterPl = client.newCall(GET(masterPlUrl)).execute().body.string()
val subList = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""").findAll(masterPl).map {
Track(it.groupValues[2], it.groupValues[1])
}.toList()
val expires = EXPIRES_REGEX.find(script)!!.groupValues[1]
val token = TOKEN_REGEX.find(script)!!.groupValues[1]

Regex("""'token(\d+p?)': ?'([\w-]+)'""").findAll(script).forEach { match ->
// Get subtitles
val masterPlUrl = "$playlistUrl?token=$token&expires=$expires&n=1"
val masterPl =
Dark25 marked this conversation as resolved.
Show resolved Hide resolved
client
.newCall(GET(masterPlUrl))
.execute()
.body
.string()
val subList =
SUBTITLES_REGEX.findAll(masterPl)
.map {
Track(it.groupValues[2], it.groupValues[1])
}.toList()
TOKEN_QUALITY_REGEX.findAll(script).forEach { match ->
val quality = match.groupValues[1]

val videoUrl = buildString {
append(playlistUrl)
append("?type=video&rendition=")
append(quality)
append("&token=")
append(match.groupValues[2])
append("&expires=$expires")
append("&canCast=$canCast")
append("&n=1")
}
val videoUrl =
buildString {
append(playlistUrl)
append("?type=video&rendition=")
append(quality)
append("&token=")
append(match.groupValues[2])
append("&expires=$expires")
append("&n=1")
}
videoList.add(Video(videoUrl, quality, videoUrl, subtitleTracks = subList))
}

require(videoList.isNotEmpty()) { "Failed to fetch videos" }

return videoList.sort()
return videoList
Dark25 marked this conversation as resolved.
Show resolved Hide resolved
}

override fun videoListRequest(episode: SEpisode): Request = throw Exception("Not used")

override fun videoListParse(response: Response): List<Video> = throw Exception("Not used")

// ============================= Utilities ==============================

private fun Document.getData(): String {
Expand Down Expand Up @@ -344,6 +360,11 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
}

companion object {
private val PLAYLIST_URL_REGEX = Regex("""url: ?'(.*?)'""")
private val EXPIRES_REGEX = Regex("""'expires': ?'(\d+)'""")
private val TOKEN_REGEX = Regex("""'token': ?'([\w-]+)'""")
private val TOKEN_QUALITY_REGEX = Regex("""'token(\d+p?)': ?'([\w-]+)'""")
private val SUBTITLES_REGEX = Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "720"
}
Expand Down