mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-04-30 06:34:29 +02:00
fix(core/media_downloader): public stories
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
parent
296d996b6c
commit
d5a926f336
@ -16,7 +16,7 @@ data class InputMedia(
|
|||||||
val isOverlay: Boolean = false,
|
val isOverlay: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
class DownloadRequest(
|
data class DownloadRequest(
|
||||||
val inputMedias: Array<InputMedia>,
|
val inputMedias: Array<InputMedia>,
|
||||||
val dashOptions: DashOptions? = null,
|
val dashOptions: DashOptions? = null,
|
||||||
val audioStreamFormat: AudioStreamFormat? = null,
|
val audioStreamFormat: AudioStreamFormat? = null,
|
||||||
|
@ -12,12 +12,17 @@ import kotlin.io.encoding.ExperimentalEncodingApi
|
|||||||
// key and iv are base64 encoded into url safe strings
|
// key and iv are base64 encoded into url safe strings
|
||||||
data class MediaEncryptionKeyPair(
|
data class MediaEncryptionKeyPair(
|
||||||
val key: String,
|
val key: String,
|
||||||
val iv: String
|
val iv: String,
|
||||||
|
val urlSafe: Boolean = true
|
||||||
) {
|
) {
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
fun decryptInputStream(inputStream: InputStream): InputStream {
|
fun decryptInputStream(inputStream: InputStream): InputStream {
|
||||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(Base64.UrlSafe.decode(key), "AES"), IvParameterSpec(Base64.UrlSafe.decode(iv)))
|
cipher.init(
|
||||||
|
Cipher.DECRYPT_MODE,
|
||||||
|
SecretKeySpec(if (urlSafe) Base64.UrlSafe.decode(key) else Base64.Default.decode(key), "AES"),
|
||||||
|
IvParameterSpec(if (urlSafe) Base64.UrlSafe.decode(iv) else Base64.Default.decode(iv))
|
||||||
|
)
|
||||||
return CipherInputStream(inputStream, cipher)
|
return CipherInputStream(inputStream, cipher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package me.rhunk.snapenhance.common.database.impl
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
import me.rhunk.snapenhance.common.database.DatabaseObject
|
||||||
|
import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
|
||||||
|
|
||||||
|
data class StorySnapEntry(
|
||||||
|
var rawSnapId: String? = null,
|
||||||
|
var mediaUrl: String? = null,
|
||||||
|
var mediaKey: String? = null,
|
||||||
|
var mediaIv: String? = null,
|
||||||
|
) : DatabaseObject {
|
||||||
|
override fun write(cursor: Cursor) {
|
||||||
|
with(cursor) {
|
||||||
|
rawSnapId = getStringOrNull("rawSnapId")!!
|
||||||
|
mediaUrl = getStringOrNull("mediaUrl")
|
||||||
|
mediaKey = getStringOrNull("mediaKey")?.takeIf { it.isNotEmpty() }
|
||||||
|
mediaIv = getStringOrNull("mediaIv")?.takeIf { it.isNotEmpty() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import me.rhunk.snapenhance.common.database.impl.ConversationMessage
|
|||||||
import me.rhunk.snapenhance.common.database.impl.FriendFeedEntry
|
import me.rhunk.snapenhance.common.database.impl.FriendFeedEntry
|
||||||
import me.rhunk.snapenhance.common.database.impl.FriendInfo
|
import me.rhunk.snapenhance.common.database.impl.FriendInfo
|
||||||
import me.rhunk.snapenhance.common.database.impl.StoryEntry
|
import me.rhunk.snapenhance.common.database.impl.StoryEntry
|
||||||
|
import me.rhunk.snapenhance.common.database.impl.StorySnapEntry
|
||||||
import me.rhunk.snapenhance.common.database.impl.UserConversationLink
|
import me.rhunk.snapenhance.common.database.impl.UserConversationLink
|
||||||
import me.rhunk.snapenhance.common.util.ktx.getBlobOrNull
|
import me.rhunk.snapenhance.common.util.ktx.getBlobOrNull
|
||||||
import me.rhunk.snapenhance.common.util.ktx.getIntOrNull
|
import me.rhunk.snapenhance.common.util.ktx.getIntOrNull
|
||||||
@ -24,7 +25,8 @@ enum class DatabaseType(
|
|||||||
val fileName: String
|
val fileName: String
|
||||||
) {
|
) {
|
||||||
MAIN("main.db"),
|
MAIN("main.db"),
|
||||||
ARROYO("arroyo.db")
|
ARROYO("arroyo.db"),
|
||||||
|
SIMPLE_DB_HELPER("simple_db_helper.db")
|
||||||
}
|
}
|
||||||
|
|
||||||
class DatabaseAccess(
|
class DatabaseAccess(
|
||||||
@ -495,4 +497,15 @@ class DatabaseAccess(
|
|||||||
}
|
}
|
||||||
}?.close()
|
}?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getStorySnapEntry(rawSnapId: String): StorySnapEntry? {
|
||||||
|
return useDatabase(DatabaseType.SIMPLE_DB_HELPER)?.performOperation {
|
||||||
|
readDatabaseObject(
|
||||||
|
StorySnapEntry(),
|
||||||
|
"DiscoverStorySnap",
|
||||||
|
"rawSnapId = ?",
|
||||||
|
arrayOf(rawSnapId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -207,9 +207,22 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadOperaMedia(downloadManagerClient: DownloadManagerClient, mediaInfoMap: Map<SplitMediaAssetType, MediaInfo>) {
|
private fun downloadOperaMedia(downloadManagerClient: DownloadManagerClient, mediaInfoMap: Map<SplitMediaAssetType, MediaInfo>, paramMap: ParamMap) {
|
||||||
if (mediaInfoMap.isEmpty()) return
|
if (mediaInfoMap.isEmpty()) return
|
||||||
|
|
||||||
|
paramMap["SNAP_ID"]?.toString()?.let { snapId ->
|
||||||
|
context.database.getStorySnapEntry(snapId)?.let { storySnapEntry ->
|
||||||
|
downloadManagerClient.downloadSingleMedia(
|
||||||
|
storySnapEntry.mediaUrl ?: throw Exception("Media URL not found"),
|
||||||
|
DownloadMediaType.fromUri(Uri.parse(storySnapEntry.mediaUrl)),
|
||||||
|
(storySnapEntry.mediaKey to storySnapEntry.mediaIv).takeIf { it.first != null && it.second != null }?.let { (key, iv) ->
|
||||||
|
MediaEncryptionKeyPair(key!!, iv!!, urlSafe = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val originalMediaInfo = mediaInfoMap[SplitMediaAssetType.ORIGINAL]!!
|
val originalMediaInfo = mediaInfoMap[SplitMediaAssetType.ORIGINAL]!!
|
||||||
val originalMediaInfoReference = handleLocalReferences(originalMediaInfo.uri)
|
val originalMediaInfoReference = handleLocalReferences(originalMediaInfo.uri)
|
||||||
|
|
||||||
@ -286,7 +299,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
downloadSource = MediaDownloadSource.CHAT_MEDIA,
|
downloadSource = MediaDownloadSource.CHAT_MEDIA,
|
||||||
friendInfo = author,
|
friendInfo = author,
|
||||||
forceAllowDuplicate = forceAllowDuplicate
|
forceAllowDuplicate = forceAllowDuplicate
|
||||||
), mediaInfoMap)
|
), mediaInfoMap, paramMap)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -333,40 +346,12 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
downloadSource = MediaDownloadSource.STORY,
|
downloadSource = MediaDownloadSource.STORY,
|
||||||
friendInfo = author,
|
friendInfo = author,
|
||||||
forceAllowDuplicate = forceAllowDuplicate,
|
forceAllowDuplicate = forceAllowDuplicate,
|
||||||
), mediaInfoMap)
|
), mediaInfoMap, paramMap)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val snapSource = paramMap["SNAP_SOURCE"].toString()
|
val snapSource = paramMap["SNAP_SOURCE"].toString()
|
||||||
|
|
||||||
//public stories
|
|
||||||
if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") &&
|
|
||||||
(forceDownload || shouldAutoDownload("public_stories"))) {
|
|
||||||
|
|
||||||
val author = (
|
|
||||||
paramMap["USER_ID"]?.let { context.database.getFriendInfo(it.toString())?.mutableUsername } // only for following users
|
|
||||||
?: paramMap["USERNAME"]?.toString()?.takeIf {
|
|
||||||
it.contains("value=")
|
|
||||||
}?.substringAfter("value=")?.substringBefore(")")?.substringBefore(",")
|
|
||||||
?: paramMap["CONTEXT_USER_IDENTITY"]?.toString()?.takeIf {
|
|
||||||
it.contains("username=")
|
|
||||||
}?.substringAfter("username=")?.substringBefore(",")
|
|
||||||
// fallback display name
|
|
||||||
?: paramMap["USER_DISPLAY_NAME"]?.toString()?.takeIf { it.isNotEmpty() }
|
|
||||||
?: paramMap["TIME_STAMP"]?.toString()
|
|
||||||
?: "unknown"
|
|
||||||
).sanitizeForPath()
|
|
||||||
|
|
||||||
downloadOperaMedia(provideDownloadManagerClient(
|
|
||||||
mediaIdentifier = paramMap["SNAP_ID"].toString(),
|
|
||||||
mediaAuthor = author,
|
|
||||||
downloadSource = MediaDownloadSource.PUBLIC_STORY,
|
|
||||||
creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
|
|
||||||
forceAllowDuplicate = forceAllowDuplicate,
|
|
||||||
), mediaInfoMap)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//spotlight
|
//spotlight
|
||||||
if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || shouldAutoDownload("spotlight"))) {
|
if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || shouldAutoDownload("spotlight"))) {
|
||||||
downloadOperaMedia(provideDownloadManagerClient(
|
downloadOperaMedia(provideDownloadManagerClient(
|
||||||
@ -375,7 +360,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
mediaAuthor = paramMap["CREATOR_DISPLAY_NAME"].toString(),
|
mediaAuthor = paramMap["CREATOR_DISPLAY_NAME"].toString(),
|
||||||
creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
|
creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
|
||||||
forceAllowDuplicate = forceAllowDuplicate,
|
forceAllowDuplicate = forceAllowDuplicate,
|
||||||
), mediaInfoMap)
|
), mediaInfoMap, paramMap)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,6 +466,29 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//public stories
|
||||||
|
val author = (
|
||||||
|
paramMap["USER_ID"]?.let { context.database.getFriendInfo(it.toString())?.mutableUsername } // only for following users
|
||||||
|
?: paramMap["USERNAME"]?.toString()?.takeIf {
|
||||||
|
it.contains("value=")
|
||||||
|
}?.substringAfter("value=")?.substringBefore(")")?.substringBefore(",")
|
||||||
|
?: paramMap["CONTEXT_USER_IDENTITY"]?.toString()?.takeIf {
|
||||||
|
it.contains("username=")
|
||||||
|
}?.substringAfter("username=")?.substringBefore(",")
|
||||||
|
// fallback display name
|
||||||
|
?: paramMap["USER_DISPLAY_NAME"]?.toString()?.takeIf { it.isNotEmpty() }
|
||||||
|
?: paramMap["TIME_STAMP"]?.toString()
|
||||||
|
?: "unknown"
|
||||||
|
).sanitizeForPath()
|
||||||
|
|
||||||
|
downloadOperaMedia(provideDownloadManagerClient(
|
||||||
|
mediaIdentifier = paramMap["SNAP_ID"].toString(),
|
||||||
|
mediaAuthor = author,
|
||||||
|
downloadSource = MediaDownloadSource.PUBLIC_STORY,
|
||||||
|
creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
|
||||||
|
forceAllowDuplicate = forceAllowDuplicate,
|
||||||
|
), mediaInfoMap, paramMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldAutoDownload(keyFilter: String? = null): Boolean {
|
private fun shouldAutoDownload(keyFilter: String? = null): Boolean {
|
||||||
|
@ -16,7 +16,7 @@ class MediaInfo(obj: Any?) : AbstractWrapper(obj) {
|
|||||||
init {
|
init {
|
||||||
instance?.let {
|
instance?.let {
|
||||||
if (it is List<*>) {
|
if (it is List<*>) {
|
||||||
if (it.size == 0) {
|
if (it.isEmpty()) {
|
||||||
throw RuntimeException("MediaInfo is empty")
|
throw RuntimeException("MediaInfo is empty")
|
||||||
}
|
}
|
||||||
instance = it[0]!!
|
instance = it[0]!!
|
||||||
|
Loading…
x
Reference in New Issue
Block a user