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 @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.animeextension.it.streamingcommunity

import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
Expand All @@ -17,9 +19,11 @@ 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
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
Expand All @@ -29,14 +33,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 by lazy { preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! }

override val lang = "it"

override val supportsLatest = true

override val client: OkHttpClient = network.cloudflareClient
Dark25 marked this conversation as resolved.
Show resolved Hide resolved

private val json: Json by injectLazy()

private val preferences: SharedPreferences by lazy {
Expand Down Expand Up @@ -240,7 +244,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 +256,80 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {

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

override fun videoListRequest(episode: SEpisode): Request = GET("$baseUrl/watch/${episode.url}", headers)

override fun videoListParse(response: Response): List<Video> {
val data = json.decodeFromString<VideoResponse>(response.asJsoup().getData())
override fun fetchVideoList(episode: SEpisode): Observable<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 iframe =
client
.newCall(
GET(iframeUrl, headers = iframeHeaders),
).execute()
.asJsoup()
val script = iframe.selectFirst("script:containsData(masterPlaylist)")!!.data().replace("\n", "\t")
var playlistUrl = Regex("""url: ?'(.*?)'""").find(script)!!.groupValues[1]
Dark25 marked this conversation as resolved.
Show resolved Hide resolved
val filename = playlistUrl.slice(playlistUrl.lastIndexOf("/") + 1 until playlistUrl.length)
Dark25 marked this conversation as resolved.
Show resolved Hide resolved
if (!filename.endsWith(".m3u8")) {
playlistUrl = playlistUrl.replace(filename, filename + ".m3u8")
}

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]

// 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 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 =
Regex("""#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"""")
.findAll(masterPl)
.map {
Track(it.groupValues[2], it.groupValues[1])
}.toList()
Regex("""'token(\d+p?)': ?'([\w-]+)'""").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 Observable.just(videoList.sort())
}

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,13 +360,33 @@ class StreamingCommunity : ConfigurableAnimeSource, AnimeHttpSource() {
}

companion object {
private const val PREF_DOMAIN_KEY = "referred_domain"
private const val PREF_DOMAIN_TITLE = "Override BaseUrl"
private const val PREF_DOMAIN_DEFAULT = "https://streamingcommunity.forum"
private const val PREF_DOMAIN_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
Dark25 marked this conversation as resolved.
Show resolved Hide resolved

private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "720"
}

// ============================== Settings ==============================

override fun setupPreferenceScreen(screen: PreferenceScreen) {
EditTextPreference(screen.context).apply {
key = PREF_DOMAIN_KEY
title = PREF_DOMAIN_TITLE
summary = PREF_DOMAIN_SUMMARY
dialogTitle = PREF_DOMAIN_TITLE
dialogMessage = "Default: $PREF_DOMAIN_DEFAULT"
setDefaultValue(PREF_DOMAIN_DEFAULT)

setOnPreferenceChangeListener { _, newValue ->
val newValueString = newValue as String
Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show()
preferences.edit().putString(key, newValueString.trim()).commit()
}
}.also(screen::addPreference)

ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
Expand Down
Loading