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 @@ -27,7 +27,7 @@ class TelegramSearchMatcher @Inject constructor() {
fileName: String,
caption: String,
title: String,
hebrewTitle: String? = null,
localizedTitle: String? = null,
englishTitle: String? = null,
year: Int?,
season: Int?,
Expand All @@ -36,15 +36,15 @@ class TelegramSearchMatcher @Inject constructor() {
val combined = "$fileName $caption"
val normalizedCombined = normalize(combined)
val normalizedTitle = normalize(title)
val normalizedHebrew = hebrewTitle?.let { normalize(it) }
val normalizedLocalized = localizedTitle?.let { normalize(it) }
val normalizedEnglish = englishTitle?.let { normalize(it) }

// Primary match: TMDB English title, TMDB Hebrew title, or app title (in that priority)
// Primary match: TMDB English title, TMDB localized title, or app title (in that priority)
val engMatch = normalizedEnglish != null && normalizedEnglish.isNotBlank() && normalizedCombined.contains(normalizedEnglish)
val hebMatch = normalizedHebrew != null && normalizedHebrew.isNotBlank() && normalizedCombined.contains(normalizedHebrew)
val locMatch = normalizedLocalized != null && normalizedLocalized.isNotBlank() && normalizedCombined.contains(normalizedLocalized)
val appMatch = normalizedCombined.contains(normalizedTitle)

if (!engMatch && !hebMatch && !appMatch) return 0
if (!engMatch && !locMatch && !appMatch) return 0

var score = 60

Expand Down Expand Up @@ -101,42 +101,60 @@ class TelegramSearchMatcher @Inject constructor() {
return m.groupValues[1].toIntOrNull()
}

fun buildMovieQueries(title: String, year: Int?, hebrewTitle: String? = null, englishTitle: String? = null): List<String> {
fun buildMovieQueries(title: String, year: Int?, localizedTitle: String? = null, englishTitle: String? = null): List<String> {
// Prefer TMDB English title as primary; fall back to app title
val primary = englishTitle?.let { cleanTitle(it) } ?: cleanTitle(title)
val hebrew = hebrewTitle?.let { cleanTitle(it) }
val localized = localizedTitle?.let { cleanTitle(it) }
val queries = mutableListOf<String>()
if (year != null) queries.add("$primary $year")
queries.add(primary)
if (hebrew != null && !hebrew.equals(primary, ignoreCase = true)) {
if (year != null) queries.add("$hebrew $year")
queries.add(hebrew)
if (localized != null && !localized.equals(primary, ignoreCase = true)) {
if (year != null) queries.add("$localized $year")
queries.add(localized)
}
return queries.distinct()
}

fun buildSeriesQueries(title: String, season: Int, episode: Int, hebrewTitle: String? = null, englishTitle: String? = null): List<String> {
// Prefer TMDB English as primary for English patterns; TMDB Hebrew for Hebrew patterns
fun buildSeriesQueries(
title: String,
season: Int,
episode: Int,
localizedTitle: String? = null,
englishTitle: String? = null,
languageCode: String = "en"
): List<String> {
val engBase = englishTitle?.let { cleanTitle(it) } ?: cleanTitle(title)
val hebBase = hebrewTitle?.let { cleanTitle(it) }
val titlesAreSame = hebBase == null || hebBase.equals(engBase, ignoreCase = true)
val locBase = localizedTitle?.let { cleanTitle(it) }
val titlesAreSame = locBase == null || locBase.equals(engBase, ignoreCase = true)
val s = season.toString()
val e = episode.toString()
val s2 = season.toString().padStart(2, '0')
val e2 = episode.toString().padStart(2, '0')

val queries = mutableListOf<String>()

// Hebrew patterns with Hebrew title (or engBase if same)
val hebTitle = if (titlesAreSame) engBase else hebBase!!
queries += listOf(
"$hebTitle ע$s פ$e",
"$hebTitle ע${s}פ${e}",
"$hebTitle עונה $s פרק $e",
)
if (season == 1) queries += listOf("$hebTitle פ$e", "$hebTitle פרק $e")
// Hebrew-specific episode markers — only for Hebrew users
if (languageCode == "he") {
val hebTitle = if (titlesAreSame) engBase else locBase ?: engBase
queries += listOf(
"$hebTitle ע$s פ$e",
"$hebTitle ע${s}פ${e}",
"$hebTitle עונה $s פרק $e",
)
if (season == 1) queries += listOf("$hebTitle פ$e", "$hebTitle פרק $e")
}

// Localized title with English S/E patterns (any non-English language)
if (!titlesAreSame) {
queries += listOf(
"$locBase s${s}e${e}",
"$locBase s${s2}e${e2}",
"$locBase s$s e$e",
"$locBase s$s2 e$e2",
)
}

// English patterns with English title
// English patterns
queries += listOf(
"$engBase s${s}e${e}",
"$engBase s${s2}e${e2}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,17 @@ class TelegramSourceResolver @Inject constructor(
isMovie: Boolean
): List<StreamSource> {
val excludedIds = repository.getExcludedChatIds().first()
val (englishTitle, hebrewTitle) = fetchTitles(imdbId, isMovie)
// Read content language from SharedPreferences (same store SettingsViewModel writes to).
// Avoids a DI cycle: StreamRepository → TelegramSourceResolver → MediaRepository → StreamRepository.
val rawLang = context.getSharedPreferences("app_locale", android.content.Context.MODE_PRIVATE)
.getString("locale_tag", "en-US") ?: "en-US"
val langCode = rawLang.replace("iw", "he").substringBefore("-")
val (englishTitle, localizedTitle) = fetchTitles(imdbId, isMovie, langCode)

val queries = if (season != null && episode != null)
matcher.buildSeriesQueries(title, season, episode, hebrewTitle, englishTitle)
matcher.buildSeriesQueries(title, season, episode, localizedTitle, englishTitle, langCode)
else
matcher.buildMovieQueries(title, year, hebrewTitle, englishTitle)
matcher.buildMovieQueries(title, year, localizedTitle, englishTitle)

val seen = mutableSetOf<Pair<String, Long>>()
val allMessages = mutableListOf<TelegramVideoMessage>()
Expand Down Expand Up @@ -128,7 +133,7 @@ class TelegramSourceResolver @Inject constructor(
fileName = msg.fileName,
caption = msg.caption,
title = title,
hebrewTitle = hebrewTitle,
localizedTitle = localizedTitle,
englishTitle = englishTitle,
year = year,
season = season,
Expand Down Expand Up @@ -163,7 +168,7 @@ class TelegramSourceResolver @Inject constructor(
)
}
.sortedWith(
compareByDescending<StreamSource> { matcher.isHebrew(it.source) }
compareByDescending<StreamSource> { langCode == "he" && matcher.isHebrew(it.source) }
.thenByDescending { qualityTier(it.quality) }
.thenByDescending { it.sizeBytes ?: 0L }
)
Expand Down Expand Up @@ -195,21 +200,31 @@ class TelegramSourceResolver @Inject constructor(
else -> 0
}

private suspend fun fetchTitles(imdbId: String, isMovie: Boolean): Pair<String?, String?> {
private suspend fun fetchTitles(imdbId: String, isMovie: Boolean, langCode: String): Pair<String?, String?> {
if (imdbId.isBlank()) return null to null
return try {
val findResult = tmdbApi.findByExternalId(imdbId, Constants.TMDB_API_KEY)
val findItem = if (isMovie) findResult.movieResults.firstOrNull()
else findResult.tvResults.firstOrNull()
val tmdbId = findItem?.id ?: return null to null
val englishTitle = (if (isMovie) findItem.title else findItem.name).takeIf { it.isNotBlank() }
val hebrewTitle = if (isMovie)
tmdbApi.getMovieDetails(tmdbId, Constants.TMDB_API_KEY, language = "he").title
// Always fetch English explicitly — the HTTP interceptor may inject the user's
// content language into all TMDB calls, so findItem.title isn't always English.
val englishTitle = if (isMovie)
tmdbApi.getMovieDetails(tmdbId, Constants.TMDB_API_KEY, language = "en").title
.takeIf { it.isNotBlank() }
else
tmdbApi.getTvDetails(tmdbId, Constants.TMDB_API_KEY, language = "he").name
tmdbApi.getTvDetails(tmdbId, Constants.TMDB_API_KEY, language = "en").name
.takeIf { it.isNotBlank() }
englishTitle to hebrewTitle
// Fetch localized title only when the user's language is not English
val localizedTitle = if (langCode != "en") {
if (isMovie)
tmdbApi.getMovieDetails(tmdbId, Constants.TMDB_API_KEY, language = langCode).title
.takeIf { it.isNotBlank() }
else
tmdbApi.getTvDetails(tmdbId, Constants.TMDB_API_KEY, language = langCode).name
.takeIf { it.isNotBlank() }
} else null
englishTitle to localizedTitle
} catch (e: Exception) {
Log.w(TAG, "Failed to fetch titles for $imdbId: ${e.message}")
null to null
Expand Down
Loading