fix: unique hash (#81)

* fix: media hash reference

* fix: download manager receiver
longToast -> shortToast

* fix: media downloader
sanitize file name
fix playlistUrl bug
fix dash download duration

---------

Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
auth 2023-06-22 00:57:51 +02:00 committed by GitHub
parent cc59f9d060
commit c501682a2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 150 additions and 41 deletions

View File

@ -87,6 +87,7 @@
"conversation_info": "\uD83D\uDC64 Conversation Info" "conversation_info": "\uD83D\uDC64 Conversation Info"
}, },
"download_options": { "download_options": {
"allow_duplicate": "Allow duplicate downloads",
"format_user_folder": "Create folder for each user", "format_user_folder": "Create folder for each user",
"format_hash": "Add a unique hash to the file path", "format_hash": "Add a unique hash to the file path",
"format_username": "Add the username to the file path", "format_username": "Add the username to the file path",
@ -232,6 +233,8 @@
} }
}, },
"download_manager_receiver": { "download_manager_receiver": {
"already_queued_toast": "Media already in queue!",
"already_downloaded_toast": "Media already downloaded!",
"saved_toast": "Saved to {path}", "saved_toast": "Saved to {path}",
"download_toast": "Downloading {path}...", "download_toast": "Downloading {path}...",
"processing_toast": "Processing {path}...", "processing_toast": "Processing {path}...",

View File

@ -16,7 +16,7 @@ object Logger {
} }
fun error(throwable: Throwable) { fun error(throwable: Throwable) {
Log.e(TAG, "",throwable) Log.e(TAG, "", throwable)
} }
fun error(message: Any?) { fun error(message: Any?) {

View File

@ -115,8 +115,16 @@ enum class ConfigProperty(
"download_options", "download_options",
ConfigCategory.MEDIA_MANAGEMENT, ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateListValue( ConfigStateListValue(
listOf("format_user_folder", "format_hash", "format_date_time", "format_username", "merge_overlay"), listOf(
"allow_duplicate",
"format_user_folder",
"format_hash",
"format_date_time",
"format_username",
"merge_overlay"
),
mutableMapOf( mutableMapOf(
"allow_duplicate" to false,
"format_user_folder" to true, "format_user_folder" to true,
"format_hash" to true, "format_hash" to true,
"format_date_time" to true, "format_date_time" to true,

View File

@ -13,7 +13,8 @@ class DownloadManagerClient (
private val outputPath: String, private val outputPath: String,
private val mediaDisplaySource: String?, private val mediaDisplaySource: String?,
private val mediaDisplayType: String?, private val mediaDisplayType: String?,
private val iconUrl: String? private val iconUrl: String?,
private val uniqueHash: String?
) { ) {
private fun sendToBroadcastReceiver(bundle: Bundle) { private fun sendToBroadcastReceiver(bundle: Bundle) {
val intent = Intent() val intent = Intent()
@ -32,10 +33,11 @@ class DownloadManagerClient (
putString("mediaDisplaySource", mediaDisplaySource) putString("mediaDisplaySource", mediaDisplaySource)
putString("mediaDisplayType", mediaDisplayType) putString("mediaDisplayType", mediaDisplayType)
putString("iconUrl", iconUrl) putString("iconUrl", iconUrl)
putString("uniqueHash", uniqueHash)
}.apply(extras)) }.apply(extras))
} }
fun downloadDashMedia(playlistUrl: String, offsetTime: Long, duration: Long) { fun downloadDashMedia(playlistUrl: String, offsetTime: Long, duration: Long?) {
sendToBroadcastReceiver( sendToBroadcastReceiver(
DownloadRequest( DownloadRequest(
inputMedias = arrayOf(playlistUrl), inputMedias = arrayOf(playlistUrl),
@ -44,8 +46,8 @@ class DownloadManagerClient (
) )
) { ) {
putBundle("dashOptions", Bundle().apply { putBundle("dashOptions", Bundle().apply {
putLong("offsetTime", offsetTime) putString("offsetTime", offsetTime.toString())
putLong("duration", duration) duration?.let { putString("duration", it.toString()) }
}) })
} }
} }

View File

@ -128,7 +128,7 @@ class DownloadManagerReceiver : BroadcastReceiver() {
if (!it.endsWith("/")) "$it/" else it if (!it.endsWith("/")) "$it/" else it
} }
longToast( shortToast(
translation.format("saved_toast", "path" to outputFile.absolutePath.replace(parentName ?: "", "")) translation.format("saved_toast", "path" to outputFile.absolutePath.replace(parentName ?: "", ""))
) )
@ -255,9 +255,19 @@ class DownloadManagerReceiver : BroadcastReceiver() {
this.context = context this.context = context
SharedContext.ensureInitialized(context) SharedContext.ensureInitialized(context)
val downloadRequest = DownloadRequest.fromBundle(intent.extras!!) val downloadRequest = DownloadRequest.fromBundle(intent.extras!!)
SharedContext.downloadTaskManager.canDownloadMedia(downloadRequest.getUniqueHash())?.let { downloadStage ->
shortToast(
translation[if (downloadStage.isFinalStage) {
"already_downloaded_toast"
} else {
"already_queued_toast"
}]
)
return
}
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
val pendingDownloadObject = PendingDownload.fromBundle(intent.extras!!) val pendingDownloadObject = PendingDownload.fromBundle(intent.extras!!)

View File

@ -20,6 +20,7 @@ class DownloadTaskManager {
SQLiteDatabaseHelper.createTablesFromSchema(this, mapOf( SQLiteDatabaseHelper.createTablesFromSchema(this, mapOf(
"tasks" to listOf( "tasks" to listOf(
"id INTEGER PRIMARY KEY AUTOINCREMENT", "id INTEGER PRIMARY KEY AUTOINCREMENT",
"hash VARCHAR UNIQUE",
"outputPath TEXT", "outputPath TEXT",
"outputFile TEXT", "outputFile TEXT",
"mediaDisplayType TEXT", "mediaDisplayType TEXT",
@ -32,8 +33,9 @@ class DownloadTaskManager {
} }
fun addTask(task: PendingDownload): Int { fun addTask(task: PendingDownload): Int {
taskDatabase.execSQL("INSERT INTO tasks (outputPath, outputFile, mediaDisplayType, mediaDisplaySource, iconUrl, downloadStage) VALUES (?, ?, ?, ?, ?, ?)", taskDatabase.execSQL("INSERT INTO tasks (hash, outputPath, outputFile, mediaDisplayType, mediaDisplaySource, iconUrl, downloadStage) VALUES (?, ?, ?, ?, ?, ?, ?)",
arrayOf( arrayOf(
task.uniqueHash,
task.outputPath, task.outputPath,
task.outputFile, task.outputFile,
task.mediaDisplayType, task.mediaDisplayType,
@ -51,8 +53,9 @@ class DownloadTaskManager {
} }
fun updateTask(task: PendingDownload) { fun updateTask(task: PendingDownload) {
taskDatabase.execSQL("UPDATE tasks SET outputPath = ?, outputFile = ?, mediaDisplayType = ?, mediaDisplaySource = ?, iconUrl = ?, downloadStage = ? WHERE id = ?", taskDatabase.execSQL("UPDATE tasks SET hash = ?, outputPath = ?, outputFile = ?, mediaDisplayType = ?, mediaDisplaySource = ?, iconUrl = ?, downloadStage = ? WHERE id = ?",
arrayOf( arrayOf(
task.uniqueHash,
task.outputPath, task.outputPath,
task.outputFile, task.outputFile,
task.mediaDisplayType, task.mediaDisplayType,
@ -71,6 +74,28 @@ class DownloadTaskManager {
} }
} }
@SuppressLint("Range")
fun canDownloadMedia(mediaIdentifier: String?): DownloadStage? {
if (mediaIdentifier == null) return null
val cursor = taskDatabase.rawQuery("SELECT * FROM tasks WHERE hash = ?", arrayOf(mediaIdentifier))
if (cursor.count > 0) {
cursor.moveToFirst()
val downloadStage = DownloadStage.valueOf(cursor.getString(cursor.getColumnIndex("downloadStage")))
cursor.close()
//if the stage has reached a final stage and is not in a saved state, remove the task
if (downloadStage.isFinalStage && downloadStage != DownloadStage.SAVED) {
taskDatabase.execSQL("DELETE FROM tasks WHERE hash = ?", arrayOf(mediaIdentifier))
return null
}
return downloadStage
}
cursor.close()
return null
}
fun isEmpty(): Boolean { fun isEmpty(): Boolean {
return cachedTasks.isEmpty() && pendingTasks.isEmpty() return cachedTasks.isEmpty() && pendingTasks.isEmpty()
} }
@ -127,6 +152,7 @@ class DownloadTaskManager {
outputPath = cursor.getString(cursor.getColumnIndex("outputPath")), outputPath = cursor.getString(cursor.getColumnIndex("outputPath")),
mediaDisplayType = cursor.getString(cursor.getColumnIndex("mediaDisplayType")), mediaDisplayType = cursor.getString(cursor.getColumnIndex("mediaDisplayType")),
mediaDisplaySource = cursor.getString(cursor.getColumnIndex("mediaDisplaySource")), mediaDisplaySource = cursor.getString(cursor.getColumnIndex("mediaDisplaySource")),
uniqueHash = cursor.getString(cursor.getColumnIndex("hash")),
iconUrl = cursor.getString(cursor.getColumnIndex("iconUrl")) iconUrl = cursor.getString(cursor.getColumnIndex("iconUrl"))
).apply { ).apply {
downloadStage = DownloadStage.valueOf(cursor.getString(cursor.getColumnIndex("downloadStage"))) downloadStage = DownloadStage.valueOf(cursor.getString(cursor.getColumnIndex("downloadStage")))

View File

@ -19,7 +19,8 @@ class DownloadRequest(
private val flags: Int = 0, private val flags: Int = 0,
private val dashOptions: Map<String, String?>? = null, private val dashOptions: Map<String, String?>? = null,
private val mediaDisplaySource: String? = null, private val mediaDisplaySource: String? = null,
private val mediaDisplayType: String? = null private val mediaDisplayType: String? = null,
private val uniqueHash: String? = null
) { ) {
companion object { companion object {
fun fromBundle(bundle: Bundle): DownloadRequest { fun fromBundle(bundle: Bundle): DownloadRequest {
@ -39,7 +40,8 @@ class DownloadRequest(
options.getString(key) options.getString(key)
} }
}, },
flags = bundle.getInt("flags", 0) flags = bundle.getInt("flags", 0),
uniqueHash = bundle.getString("uniqueHash")
) )
} }
} }
@ -62,6 +64,7 @@ class DownloadRequest(
} }
}) })
putInt("flags", flags) putInt("flags", flags)
putString("uniqueHash", uniqueHash)
} }
} }
@ -85,10 +88,6 @@ class DownloadRequest(
} }
} }
fun getInputMedia(index: Int): String? {
return inputMedias.getOrNull(index)
}
fun getInputMedias(): List<InputMedia> { fun getInputMedias(): List<InputMedia> {
return inputMedias.mapIndexed { index, uri -> return inputMedias.mapIndexed { index, uri ->
InputMedia( InputMedia(
@ -102,4 +101,6 @@ class DownloadRequest(
fun getInputType(index: Int): DownloadMediaType? { fun getInputType(index: Int): DownloadMediaType? {
return inputTypes.getOrNull(index)?.let { DownloadMediaType.valueOf(it) } return inputTypes.getOrNull(index)?.let { DownloadMediaType.valueOf(it) }
} }
fun getUniqueHash() = uniqueHash
} }

View File

@ -13,7 +13,8 @@ data class PendingDownload(
val outputPath: String, val outputPath: String,
val mediaDisplayType: String?, val mediaDisplayType: String?,
val mediaDisplaySource: String?, val mediaDisplaySource: String?,
val iconUrl: String? val iconUrl: String?,
val uniqueHash: String?
) { ) {
companion object { companion object {
fun fromBundle(bundle: Bundle): PendingDownload { fun fromBundle(bundle: Bundle): PendingDownload {
@ -21,7 +22,8 @@ data class PendingDownload(
outputPath = bundle.getString("outputPath")!!, outputPath = bundle.getString("outputPath")!!,
mediaDisplayType = bundle.getString("mediaDisplayType"), mediaDisplayType = bundle.getString("mediaDisplayType"),
mediaDisplaySource = bundle.getString("mediaDisplaySource"), mediaDisplaySource = bundle.getString("mediaDisplaySource"),
iconUrl = bundle.getString("iconUrl") iconUrl = bundle.getString("iconUrl"),
uniqueHash = bundle.getString("uniqueHash")
) )
} }
} }

View File

@ -30,13 +30,14 @@ import me.rhunk.snapenhance.hook.HookAdapter
import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.HookStage
import me.rhunk.snapenhance.hook.Hooker import me.rhunk.snapenhance.hook.Hooker
import me.rhunk.snapenhance.ui.download.MediaFilter import me.rhunk.snapenhance.ui.download.MediaFilter
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
import me.rhunk.snapenhance.util.getObjectField
import me.rhunk.snapenhance.util.protobuf.ProtoReader
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
import me.rhunk.snapenhance.util.snap.EncryptionHelper import me.rhunk.snapenhance.util.snap.EncryptionHelper
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
import me.rhunk.snapenhance.util.snap.MediaType import me.rhunk.snapenhance.util.snap.MediaType
import me.rhunk.snapenhance.util.snap.PreviewUtils import me.rhunk.snapenhance.util.snap.PreviewUtils
import me.rhunk.snapenhance.util.getObjectField
import me.rhunk.snapenhance.util.protobuf.ProtoReader
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
import java.io.File import java.io.File
import java.nio.file.Paths import java.nio.file.Paths
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -56,10 +57,13 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
private fun provideClientDownloadManager( private fun provideClientDownloadManager(
pathSuffix: String, pathSuffix: String,
mediaIdentifier: String,
mediaDisplaySource: String? = null, mediaDisplaySource: String? = null,
mediaDisplayType: String? = null, mediaDisplayType: String? = null,
friendInfo: FriendInfo? = null friendInfo: FriendInfo? = null
): DownloadManagerClient { ): DownloadManagerClient {
val generatedHash = mediaIdentifier.hashCode().toString(16)
val iconUrl = friendInfo?.takeIf { val iconUrl = friendInfo?.takeIf {
it.bitmojiAvatarId != null && it.bitmojiSelfieId != null it.bitmojiAvatarId != null && it.bitmojiSelfieId != null
}?.let { }?.let {
@ -68,7 +72,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
val outputPath = File( val outputPath = File(
context.config.string(ConfigProperty.SAVE_FOLDER), context.config.string(ConfigProperty.SAVE_FOLDER),
createNewFilePath(pathSuffix.hashCode(), pathSuffix) createNewFilePath(generatedHash, pathSuffix)
).absolutePath ).absolutePath
return DownloadManagerClient( return DownloadManagerClient(
@ -76,6 +80,11 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
mediaDisplaySource = mediaDisplaySource, mediaDisplaySource = mediaDisplaySource,
mediaDisplayType = mediaDisplayType, mediaDisplayType = mediaDisplayType,
iconUrl = iconUrl, iconUrl = iconUrl,
uniqueHash =
// If duplicate is allowed, we don't need to pass the hash
if (context.config.options(ConfigProperty.DOWNLOAD_OPTIONS)["allow_duplicate"] == false) {
generatedHash
} else null,
outputPath = outputPath outputPath = outputPath
) )
} }
@ -85,9 +94,12 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
return isFFmpegPresent return isFFmpegPresent
} }
private fun createNewFilePath(hash: Int, pathPrefix: String): String { private fun createNewFilePath(hexHash: String, pathPrefix: String): String {
val hexHash = Integer.toHexString(hash)
val downloadOptions = context.config.options(ConfigProperty.DOWNLOAD_OPTIONS) val downloadOptions = context.config.options(ConfigProperty.DOWNLOAD_OPTIONS)
val sanitizedPathPrefix = pathPrefix
.replace(" ", "_")
.replace(Regex("[\\\\/:*?\"<>|]"), "")
.ifEmpty { hexHash }
val currentDateTime = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(System.currentTimeMillis()) val currentDateTime = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(System.currentTimeMillis())
@ -102,13 +114,13 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
} }
if (downloadOptions["format_user_folder"] == true) { if (downloadOptions["format_user_folder"] == true) {
finalPath.append(pathPrefix).append("/") finalPath.append(sanitizedPathPrefix).append("/")
} }
if (downloadOptions["format_hash"] == true) { if (downloadOptions["format_hash"] == true) {
appendFileName(hexHash) appendFileName(hexHash)
} }
if (downloadOptions["format_username"] == true) { if (downloadOptions["format_username"] == true) {
appendFileName(pathPrefix) appendFileName(sanitizedPathPrefix)
} }
if (downloadOptions["format_date_time"] == true) { if (downloadOptions["format_date_time"] == true) {
appendFileName(currentDateTime) appendFileName(currentDateTime)
@ -186,7 +198,10 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
//messages //messages
paramMap["MESSAGE_ID"]?.toString()?.takeIf { forceDownload || canAutoDownload("friend_snaps") }?.let { id -> paramMap["MESSAGE_ID"]?.toString()?.takeIf { forceDownload || canAutoDownload("friend_snaps") }?.let { id ->
val messageId = id.substring(id.lastIndexOf(":") + 1).toLong() val messageId = id.substring(id.lastIndexOf(":") + 1).toLong()
val senderId: String = context.database.getConversationMessageFromId(messageId)!!.sender_id!! val conversationMessage = context.database.getConversationMessageFromId(messageId)!!
val senderId = conversationMessage.sender_id!!
val conversationId = conversationMessage.client_conversation_id!!
if (!forceDownload && context.feature(AntiAutoDownload::class).isUserIgnored(senderId)) { if (!forceDownload && context.feature(AntiAutoDownload::class).isUserIgnored(senderId)) {
return return
@ -194,7 +209,15 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
val author = context.database.getFriendInfo(senderId) ?: return val author = context.database.getFriendInfo(senderId) ?: return
val authorUsername = author.usernameForSorting!! val authorUsername = author.usernameForSorting!!
downloadOperaMedia(provideClientDownloadManager(authorUsername, authorUsername, MediaFilter.CHAT_MEDIA.mediaDisplayType, friendInfo = author), mediaInfoMap)
downloadOperaMedia(provideClientDownloadManager(
pathSuffix = authorUsername,
mediaIdentifier = "$conversationId$senderId$messageId",
mediaDisplaySource = authorUsername,
mediaDisplayType = MediaFilter.CHAT_MEDIA.mediaDisplayType,
friendInfo = author
), mediaInfoMap)
return return
} }
@ -202,15 +225,20 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
paramMap["PLAYLIST_V2_GROUP"]?.toString()?.takeIf { paramMap["PLAYLIST_V2_GROUP"]?.toString()?.takeIf {
it.contains("storyUserId=") && (forceDownload || canAutoDownload("friend_stories")) it.contains("storyUserId=") && (forceDownload || canAutoDownload("friend_stories"))
}?.let { playlistGroup -> }?.let { playlistGroup ->
val storyIdStartIndex = playlistGroup.indexOf("storyUserId=") + 12 val storyUserId = (playlistGroup.indexOf("storyUserId=") + 12).let {
val storyUserId = playlistGroup.substring( playlistGroup.substring(it, playlistGroup.indexOf(",", it))
storyIdStartIndex, }
playlistGroup.indexOf(",", storyIdStartIndex)
)
val author = context.database.getFriendInfo(if (storyUserId == "null") context.database.getMyUserId()!! else storyUserId) ?: return val author = context.database.getFriendInfo(if (storyUserId == "null") context.database.getMyUserId()!! else storyUserId) ?: return
val authorName = author.usernameForSorting!! val authorName = author.usernameForSorting!!
downloadOperaMedia(provideClientDownloadManager(authorName, authorName, MediaFilter.STORY.mediaDisplayType, friendInfo = author), mediaInfoMap, ) downloadOperaMedia(provideClientDownloadManager(
pathSuffix = authorName,
mediaIdentifier = paramMap["MEDIA_ID"].toString(),
mediaDisplaySource = authorName,
mediaDisplayType = MediaFilter.STORY.mediaDisplayType,
friendInfo = author
), mediaInfoMap)
return return
} }
@ -222,13 +250,24 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
val userDisplayName = (if (paramMap.containsKey("USER_DISPLAY_NAME")) paramMap["USER_DISPLAY_NAME"].toString() else "").replace( val userDisplayName = (if (paramMap.containsKey("USER_DISPLAY_NAME")) paramMap["USER_DISPLAY_NAME"].toString() else "").replace(
"[^\\x00-\\x7F]".toRegex(), "[^\\x00-\\x7F]".toRegex(),
"") "")
downloadOperaMedia(provideClientDownloadManager("Public-Stories/$userDisplayName", userDisplayName, "Public Story"), mediaInfoMap)
downloadOperaMedia(provideClientDownloadManager(
pathSuffix = "Public-Stories/$userDisplayName",
mediaIdentifier = paramMap["SNAP_ID"].toString(),
mediaDisplayType = userDisplayName,
mediaDisplaySource = "Public Story"
), mediaInfoMap)
return return
} }
//spotlight //spotlight
if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || canAutoDownload("spotlight"))) { if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || canAutoDownload("spotlight"))) {
downloadOperaMedia(provideClientDownloadManager("Spotlight", mediaDisplayType = MediaFilter.SPOTLIGHT.mediaDisplayType, mediaDisplaySource = paramMap["TIME_STAMP"].toString()), mediaInfoMap) downloadOperaMedia(provideClientDownloadManager(
pathSuffix = "Spotlight",
mediaIdentifier = paramMap["SNAP_ID"].toString(),
mediaDisplayType = MediaFilter.SPOTLIGHT.mediaDisplayType,
mediaDisplaySource = paramMap["TIME_STAMP"].toString()
), mediaInfoMap)
return return
} }
@ -256,12 +295,24 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
//add 100ms to the start time to prevent the video from starting too early //add 100ms to the start time to prevent the video from starting too early
val snapChapterTimestamp = snapChapter.startTimeMs.plus(100) val snapChapterTimestamp = snapChapter.startTimeMs.plus(100)
val duration = nextChapter?.startTimeMs?.minus(snapChapterTimestamp) ?: 0 val duration: Long? = nextChapter?.startTimeMs?.minus(snapChapterTimestamp)
//get the mpd playlist and append the cdn url to baseurl nodes //get the mpd playlist and append the cdn url to baseurl nodes
val playlistUrl = paramMap["MEDIA_ID"].toString().let { it.substring(it.indexOf("https://cf-st.sc-cdn.net")) } val playlistUrl = paramMap["MEDIA_ID"].toString().let {
val clientDownloadManager = provideClientDownloadManager("Pro-Stories/${storyName}", storyName, "Pro Story") val urlIndex = it.indexOf("https://cf-st.sc-cdn.net")
clientDownloadManager.downloadDashMedia( if (urlIndex == -1) {
"${RemoteMediaResolver.CF_ST_CDN_D}$it"
} else {
it.substring(urlIndex)
}
}
provideClientDownloadManager(
pathSuffix = "Pro-Stories/${storyName}",
mediaIdentifier = "${paramMap["STORY_ID"]}-${snapItem.snapId}",
mediaDisplaySource = storyName,
mediaDisplayType = "Pro Story"
).downloadDashMedia(
playlistUrl, playlistUrl,
snapChapterTimestamp, snapChapterTimestamp,
duration duration
@ -384,7 +435,13 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
runCatching { runCatching {
if (!isPreviewMode) { if (!isPreviewMode) {
val encryptionKeys = EncryptionHelper.getEncryptionKeys(contentType, messageReader, isArroyo = isArroyoMessage) val encryptionKeys = EncryptionHelper.getEncryptionKeys(contentType, messageReader, isArroyo = isArroyoMessage)
provideClientDownloadManager(authorName, authorName, MediaFilter.CHAT_MEDIA.mediaDisplayType, friendInfo = friendInfo).downloadMedia( provideClientDownloadManager(
pathSuffix = authorName,
mediaIdentifier = "${message.client_conversation_id}${message.sender_id}${message.client_message_id}",
mediaDisplaySource = authorName,
mediaDisplayType = MediaFilter.CHAT_MEDIA.mediaDisplayType,
friendInfo = friendInfo
).downloadMedia(
Base64.UrlSafe.encode(urlProto), Base64.UrlSafe.encode(urlProto),
DownloadMediaType.PROTO_MEDIA, DownloadMediaType.PROTO_MEDIA,
encryption = encryptionKeys?.toKeyPair() encryption = encryptionKeys?.toKeyPair()