Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class CatalogDiscoveryRepository @Inject constructor(
append(result.description.orEmpty().lowercase())
}
val tokens = query.lowercase()
.split(NON_ALPHA_NUM_REGEX)
.split(CatalogDiscoveryRepoRegexes.NON_ALPHA_NUM_REGEX)
.filter { it.length >= 3 }
.distinct()
if (tokens.isEmpty()) return 0
Expand All @@ -197,7 +197,9 @@ class CatalogDiscoveryRepository @Inject constructor(
titleScore + bodyScore
}
}
private companion object {
private val NON_ALPHA_NUM_REGEX = Regex("[^a-z0-9]+")
}

}

private object CatalogDiscoveryRepoRegexes {
val NON_ALPHA_NUM_REGEX = Regex("[^a-z0-9]+")
}
Original file line number Diff line number Diff line change
Expand Up @@ -973,9 +973,9 @@ class CatalogRepository @Inject constructor(
}
}

val titleFromMeta = TITLE_FROM_META_REGEX.find(html)?.groupValues?.getOrNull(1)
val titleFromMeta = CatalogRepoRegexes.TITLE_FROM_META_REGEX.find(html)?.groupValues?.getOrNull(1)

val titleFromTag = TITLE_FROM_TAG_REGEX.find(html)?.groupValues?.getOrNull(1)
val titleFromTag = CatalogRepoRegexes.TITLE_FROM_TAG_REGEX.find(html)?.groupValues?.getOrNull(1)
?.replace(" - MDBList", "", ignoreCase = true)

val titleFromSlug = extractMdblistSlugTitle(url)
Expand All @@ -999,7 +999,7 @@ class CatalogRepository @Inject constructor(
}

private fun extractTraktUrl(html: String): String? {
return TRAKT_URL_REGEX.find(html)?.value
return CatalogRepoRegexes.TRAKT_URL_REGEX.find(html)?.value
}

private suspend fun fetchUrl(url: String): String? {
Expand Down Expand Up @@ -1291,18 +1291,6 @@ class CatalogRepository @Inject constructor(
private companion object {
private const val ADDON_SOURCE_REF_PREFIX = "addon_catalog|"

private val TITLE_FROM_META_REGEX = Regex(
"""<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']""",
RegexOption.IGNORE_CASE
)
private val TITLE_FROM_TAG_REGEX = Regex(
"""<title>([^<]+)</title>""",
RegexOption.IGNORE_CASE
)
private val TRAKT_URL_REGEX = Regex(
"""https?://(?:www\.)?trakt\.tv/users/[^"'\s<]+/lists/[^"'\s<]+""",
RegexOption.IGNORE_CASE
)
}
}

Expand All @@ -1315,3 +1303,18 @@ private fun String.toDisplayTitle(): String {
}
.ifBlank { "Custom Catalog" }
}

private object CatalogRepoRegexes {
val TITLE_FROM_META_REGEX = Regex(
"""<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']""",
RegexOption.IGNORE_CASE
)
val TITLE_FROM_TAG_REGEX = Regex(
"""<title>([^<]+)</title>""",
RegexOption.IGNORE_CASE
)
val TRAKT_URL_REGEX = Regex(
"""https?://(?:www\.)?trakt\.tv/users/[^"'\s<]+/lists/[^"'\s<]+""",
RegexOption.IGNORE_CASE
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class CloudSyncCoordinator @Inject constructor(
if (!started.compareAndSet(false, true)) return
collectorJob = scope.launch {
invalidationBus.events.collectLatest { invalidation ->
if (authRepository.getCurrentUserId().isNullOrBlank()) return@collectLatest
val userId = runCatching { authRepository.getCurrentUserId() }.getOrNull()
if (userId.isNullOrBlank()) return@collectLatest
cloudSyncRepository.markLocalStateDirtyNow()
scheduleFlush(invalidation)
}
Expand All @@ -54,7 +55,8 @@ class CloudSyncCoordinator @Inject constructor(
flushJob?.cancel()
flushJob = scope.launch {
delay(debounceMsFor(invalidation.scope))
if (authRepository.getCurrentUserId().isNullOrBlank()) return@launch
val userId = runCatching { authRepository.getCurrentUserId() }.getOrNull()
if (userId.isNullOrBlank()) return@launch
runCatching { cloudSyncRepository.pushToCloud() }
.onFailure { error ->
Log.w("CloudSyncCoordinator", "Cloud push failed after ${invalidation.scope}: ${error.message}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2166,9 +2166,28 @@ class HomeServerRepository @Inject constructor(
private fun JsonElement.asStringOrNull(): String? =
runCatching { takeUnless { it.isJsonNull }?.asString }.getOrNull()

private val xmlAttributeRegexCache = java.util.concurrent.ConcurrentHashMap<String, Regex>()

private fun String.xmlAttribute(name: String): String {
val pattern = Regex("""\b${Regex.escape(name)}=["']([^"']*)["']""")
return pattern.find(this)?.groupValues?.getOrNull(1).orEmpty().xmlDecoded()
val target = "$name="
var index = this.indexOf(target)
// Ensure word boundary check to avoid substring matches
while (index != -1) {
if (index == 0 || !(this[index - 1].isLetterOrDigit() || this[index - 1] == '_')) {
val valueStart = index + target.length
if (valueStart < this.length) {
val quote = this[valueStart]
if (quote == '"' || quote == '\'') {
val valueEnd = this.indexOf(quote, valueStart + 1)
if (valueEnd != -1) {
return this.substring(valueStart + 1, valueEnd).xmlDecoded()
}
}
}
}
index = this.indexOf(target, index + target.length)
}
return ""
}

private fun String.xmlBooleanAttribute(name: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ class IptvRepository @Inject constructor(
}

private fun String.replaceDurationScalePlaceholders(durationSec: Long): String {
return Regex("""\$\{duration:(\d+)\}|\{duration:(\d+)\}""").replace(this) { match ->
return DURATION_SCALE_REGEX.replace(this) { match ->
val divisor = (match.groupValues.getOrNull(1)?.takeIf { it.isNotBlank() }
?: match.groupValues.getOrNull(2))
?.toLongOrNull()
Expand All @@ -988,8 +988,10 @@ class IptvRepository @Inject constructor(
}
}

private val datePatternRegexCache = java.util.concurrent.ConcurrentHashMap<String, Regex>()

private fun String.replaceDatePatternPlaceholders(key: String, dateTime: LocalDateTime): String {
val regex = Regex("""\$\{""" + key + """:([^}]+)\}|\{""" + key + """:([^}]+)\}""")
val regex = datePatternRegexCache.getOrPut(key) { Regex("""\$\{""" + key + """:([^}]+)\}|\{""" + key + """:([^}]+)\}""") }
return regex.replace(this) { match ->
val pattern = match.groupValues.getOrNull(1)?.takeIf { it.isNotBlank() }
?: match.groupValues.getOrNull(2)
Expand Down Expand Up @@ -1066,11 +1068,9 @@ class IptvRepository @Inject constructor(
}

private fun redactIptvUrl(url: String): String {
val withoutQuerySecrets = Regex(
pattern = """(?i)([?&](?:username|user|uname|password|pass|pwd)=)[^&]+"""
).replace(url) { match -> "${match.groupValues[1]}***" }
val withoutQuerySecrets = URL_QUERY_SECRETS_REGEX.replace(url) { match -> "${match.groupValues[1]}***" }

return Regex("""(?i)(/(?:live|movie|series|timeshift)/)([^/]+)/([^/]+)(/)""")
return URL_PATH_SECRETS_REGEX
.replace(withoutQuerySecrets) { match ->
"${match.groupValues[1]}***/***${match.groupValues[4]}"
}
Expand Down Expand Up @@ -6454,8 +6454,8 @@ class IptvRepository @Inject constructor(
val base = epgId?.takeIf { it.isNotBlank() } ?: name
val normalizedBase = normalizeLooseKey(
base
.replace(Regex("""\b(4K|UHD|FHD|HD|SD|2160P?|1080P?|720P?|576P?|480P?)\b""", RegexOption.IGNORE_CASE), " ")
.replace(Regex("""\[[^\]]*]|\([^)]*\)"""), " ")
.replace(QUALITY_WORDS_REGEX, " ")
.replace(BRACKET_PAREN_REGEX, " ")
)
val normalizedGroup = normalizeLooseKey(group)
return listOf(normalizedGroup, normalizedBase).filter { it.isNotBlank() }.joinToString(":")
Expand Down Expand Up @@ -7529,6 +7529,12 @@ class IptvRepository @Inject constructor(
// ════════════════════════════════════════════════════════════════════════

private companion object {
private val DURATION_SCALE_REGEX = Regex("""\$\{duration:(\d+)\}|\{duration:(\d+)\}""")
private val URL_QUERY_SECRETS_REGEX = Regex("""(?i)([?&](?:username|user|uname|password|pass|pwd)=)[^&]+""")
private val URL_PATH_SECRETS_REGEX = Regex("""(?i)(/(?:live|movie|series|timeshift)/)([^/]+)/([^/]+)(/)""")
private val QUALITY_WORDS_REGEX = Regex("""\b(4K|UHD|FHD|HD|SD|2160P?|1080P?|720P?|576P?|480P?)\b""", RegexOption.IGNORE_CASE)
private val BRACKET_PAREN_REGEX = Regex("""\[[^\]]*]|\([^)]*\)""")

const val ENC_PREFIX = "encv1:"
const val ANDROID_KEYSTORE = "AndroidKeyStore"
const val CONFIG_KEY_ALIAS = "arvio_iptv_config_v1"
Expand Down
Loading
Loading