mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-03 07:54:30 +02:00
fix(media_downloader): story voice note reply
- refactor media author and download source - optimize download section
This commit is contained in:
parent
ea6260463c
commit
5776d44111
@ -180,7 +180,7 @@ class LogManager(
|
|||||||
|
|
||||||
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
|
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||||
internalLog(tag, LogLevel.ERROR, message)
|
internalLog(tag, LogLevel.ERROR, message)
|
||||||
internalLog(tag, LogLevel.ERROR, throwable)
|
internalLog(tag, LogLevel.ERROR, throwable.stackTraceToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun info(message: Any?, tag: String = TAG) {
|
fun info(message: Any?, tag: String = TAG) {
|
||||||
|
@ -20,7 +20,7 @@ import me.rhunk.snapenhance.core.BuildConfig
|
|||||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||||
import me.rhunk.snapenhance.core.bridge.wrapper.MappingsWrapper
|
import me.rhunk.snapenhance.core.bridge.wrapper.MappingsWrapper
|
||||||
import me.rhunk.snapenhance.core.config.ModConfig
|
import me.rhunk.snapenhance.core.config.ModConfig
|
||||||
import me.rhunk.snapenhance.core.download.DownloadTaskManager
|
import me.rhunk.snapenhance.download.DownloadTaskManager
|
||||||
import me.rhunk.snapenhance.messaging.ModDatabase
|
import me.rhunk.snapenhance.messaging.ModDatabase
|
||||||
import me.rhunk.snapenhance.messaging.StreaksReminder
|
import me.rhunk.snapenhance.messaging.StreaksReminder
|
||||||
import me.rhunk.snapenhance.ui.manager.MainActivity
|
import me.rhunk.snapenhance.ui.manager.MainActivity
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
package me.rhunk.snapenhance.core.download.data
|
package me.rhunk.snapenhance.download
|
||||||
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import me.rhunk.snapenhance.core.download.DownloadTaskManager
|
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||||
|
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
||||||
|
|
||||||
data class DownloadObject(
|
data class DownloadObject(
|
||||||
var downloadId: Int = 0,
|
var downloadId: Int = 0,
|
||||||
var outputFile: String? = null,
|
var outputFile: String? = null,
|
||||||
val metadata : DownloadMetadata
|
val metadata : DownloadMetadata
|
||||||
) {
|
) {
|
||||||
lateinit var downloadTaskManager: DownloadTaskManager
|
|
||||||
var job: Job? = null
|
var job: Job? = null
|
||||||
|
|
||||||
var changeListener = { _: DownloadStage, _: DownloadStage -> }
|
var changeListener = { _: DownloadStage, _: DownloadStage -> }
|
||||||
|
lateinit var updateTaskCallback: (DownloadObject) -> Unit
|
||||||
|
|
||||||
private var _stage: DownloadStage = DownloadStage.PENDING
|
private var _stage: DownloadStage = DownloadStage.PENDING
|
||||||
var downloadStage: DownloadStage
|
var downloadStage: DownloadStage
|
||||||
get() = synchronized(this) {
|
get() = synchronized(this) {
|
||||||
@ -20,7 +22,7 @@ data class DownloadObject(
|
|||||||
set(value) = synchronized(this) {
|
set(value) = synchronized(this) {
|
||||||
changeListener(_stage, value)
|
changeListener(_stage, value)
|
||||||
_stage = value
|
_stage = value
|
||||||
downloadTaskManager.updateTask(this)
|
updateTaskCallback(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isJobActive() = job?.isActive == true
|
fun isJobActive() = job?.isActive == true
|
@ -16,14 +16,12 @@ import kotlinx.coroutines.joinAll
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import me.rhunk.snapenhance.Constants
|
import me.rhunk.snapenhance.Constants
|
||||||
import me.rhunk.snapenhance.Logger
|
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
import me.rhunk.snapenhance.RemoteSideContext
|
||||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||||
import me.rhunk.snapenhance.core.download.DownloadManagerClient
|
import me.rhunk.snapenhance.core.download.DownloadManagerClient
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadRequest
|
import me.rhunk.snapenhance.core.download.data.DownloadRequest
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
||||||
import me.rhunk.snapenhance.core.download.data.InputMedia
|
import me.rhunk.snapenhance.core.download.data.InputMedia
|
||||||
@ -320,7 +318,11 @@ class DownloadProcessor (
|
|||||||
|
|
||||||
val downloadObjectObject = DownloadObject(
|
val downloadObjectObject = DownloadObject(
|
||||||
metadata = downloadMetadata
|
metadata = downloadMetadata
|
||||||
).apply { downloadTaskManager = remoteSideContext.downloadTaskManager }
|
).apply {
|
||||||
|
updateTaskCallback = {
|
||||||
|
remoteSideContext.downloadTaskManager.updateTask(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downloadObjectObject.also {
|
downloadObjectObject.also {
|
||||||
remoteSideContext.downloadTaskManager.addTask(it)
|
remoteSideContext.downloadTaskManager.addTask(it)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package me.rhunk.snapenhance.core.download
|
package me.rhunk.snapenhance.download
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
import me.rhunk.snapenhance.core.download.data.DownloadStage
|
||||||
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
import me.rhunk.snapenhance.core.download.data.MediaDownloadSource
|
||||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||||
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
||||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||||
@ -26,8 +25,8 @@ class DownloadTaskManager {
|
|||||||
"hash VARCHAR UNIQUE",
|
"hash VARCHAR UNIQUE",
|
||||||
"outputPath TEXT",
|
"outputPath TEXT",
|
||||||
"outputFile TEXT",
|
"outputFile TEXT",
|
||||||
"mediaDisplayType TEXT",
|
"mediaAuthor TEXT",
|
||||||
"mediaDisplaySource TEXT",
|
"downloadSource TEXT",
|
||||||
"iconUrl TEXT",
|
"iconUrl TEXT",
|
||||||
"downloadStage TEXT"
|
"downloadStage TEXT"
|
||||||
)
|
)
|
||||||
@ -36,13 +35,13 @@ class DownloadTaskManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addTask(task: DownloadObject): Int {
|
fun addTask(task: DownloadObject): Int {
|
||||||
taskDatabase.execSQL("INSERT INTO tasks (hash, outputPath, outputFile, mediaDisplayType, mediaDisplaySource, iconUrl, downloadStage) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
taskDatabase.execSQL("INSERT INTO tasks (hash, outputPath, outputFile, downloadSource, mediaAuthor, iconUrl, downloadStage) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
task.metadata.mediaIdentifier,
|
task.metadata.mediaIdentifier,
|
||||||
task.metadata.outputPath,
|
task.metadata.outputPath,
|
||||||
task.outputFile,
|
task.outputFile,
|
||||||
task.metadata.mediaDisplayType,
|
task.metadata.downloadSource,
|
||||||
task.metadata.mediaDisplaySource,
|
task.metadata.mediaAuthor,
|
||||||
task.metadata.iconUrl,
|
task.metadata.iconUrl,
|
||||||
task.downloadStage.name
|
task.downloadStage.name
|
||||||
)
|
)
|
||||||
@ -56,13 +55,13 @@ class DownloadTaskManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateTask(task: DownloadObject) {
|
fun updateTask(task: DownloadObject) {
|
||||||
taskDatabase.execSQL("UPDATE tasks SET hash = ?, outputPath = ?, outputFile = ?, mediaDisplayType = ?, mediaDisplaySource = ?, iconUrl = ?, downloadStage = ? WHERE id = ?",
|
taskDatabase.execSQL("UPDATE tasks SET hash = ?, outputPath = ?, outputFile = ?, downloadSource = ?, mediaAuthor = ?, iconUrl = ?, downloadStage = ? WHERE id = ?",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
task.metadata.mediaIdentifier,
|
task.metadata.mediaIdentifier,
|
||||||
task.metadata.outputPath,
|
task.metadata.outputPath,
|
||||||
task.outputFile,
|
task.outputFile,
|
||||||
task.metadata.mediaDisplayType,
|
task.metadata.downloadSource,
|
||||||
task.metadata.mediaDisplaySource,
|
task.metadata.mediaAuthor,
|
||||||
task.metadata.iconUrl,
|
task.metadata.iconUrl,
|
||||||
task.downloadStage.name,
|
task.downloadStage.name,
|
||||||
task.downloadId
|
task.downloadId
|
||||||
@ -113,11 +112,11 @@ class DownloadTaskManager {
|
|||||||
removeTask(task.downloadId)
|
removeTask(task.downloadId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun queryFirstTasks(filter: MediaFilter): Map<Int, DownloadObject> {
|
fun queryFirstTasks(filter: MediaDownloadSource): Map<Int, DownloadObject> {
|
||||||
val isPendingFilter = filter == MediaFilter.PENDING
|
val isPendingFilter = filter == MediaDownloadSource.PENDING
|
||||||
val tasks = mutableMapOf<Int, DownloadObject>()
|
val tasks = mutableMapOf<Int, DownloadObject>()
|
||||||
|
|
||||||
tasks.putAll(pendingTasks.filter { isPendingFilter || filter.matches(it.value.metadata.mediaDisplayType) })
|
tasks.putAll(pendingTasks.filter { isPendingFilter || filter.matches(it.value.metadata.downloadSource) })
|
||||||
if (isPendingFilter) {
|
if (isPendingFilter) {
|
||||||
return tasks.toSortedMap(reverseOrder())
|
return tasks.toSortedMap(reverseOrder())
|
||||||
}
|
}
|
||||||
@ -132,16 +131,16 @@ class DownloadTaskManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("Range")
|
@SuppressLint("Range")
|
||||||
fun queryTasks(from: Int, amount: Int = 30, filter: MediaFilter = MediaFilter.NONE): Map<Int, DownloadObject> {
|
fun queryTasks(from: Int, amount: Int = 30, filter: MediaDownloadSource = MediaDownloadSource.NONE): Map<Int, DownloadObject> {
|
||||||
if (filter == MediaFilter.PENDING) {
|
if (filter == MediaDownloadSource.PENDING) {
|
||||||
return emptyMap()
|
return emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
val cursor = taskDatabase.rawQuery(
|
val cursor = taskDatabase.rawQuery(
|
||||||
"SELECT * FROM tasks WHERE id < ? AND mediaDisplayType LIKE ? ORDER BY id DESC LIMIT ?",
|
"SELECT * FROM tasks WHERE id < ? AND downloadSource LIKE ? ORDER BY id DESC LIMIT ?",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
from.toString(),
|
from.toString(),
|
||||||
if (filter.shouldIgnoreFilter) "%" else "%${filter.key}",
|
if (filter.ignoreFilter) "%" else "%${filter.key}",
|
||||||
amount.toString()
|
amount.toString()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -155,12 +154,12 @@ class DownloadTaskManager {
|
|||||||
metadata = DownloadMetadata(
|
metadata = DownloadMetadata(
|
||||||
outputPath = cursor.getStringOrNull("outputPath")!!,
|
outputPath = cursor.getStringOrNull("outputPath")!!,
|
||||||
mediaIdentifier = cursor.getStringOrNull("hash"),
|
mediaIdentifier = cursor.getStringOrNull("hash"),
|
||||||
mediaDisplayType = cursor.getStringOrNull("mediaDisplayType"),
|
downloadSource = cursor.getStringOrNull("downloadSource") ?: MediaDownloadSource.NONE.key,
|
||||||
mediaDisplaySource = cursor.getStringOrNull("mediaDisplaySource"),
|
mediaAuthor = cursor.getStringOrNull("mediaAuthor"),
|
||||||
iconUrl = cursor.getStringOrNull("iconUrl")
|
iconUrl = cursor.getStringOrNull("iconUrl")
|
||||||
)
|
)
|
||||||
).apply {
|
).apply {
|
||||||
downloadTaskManager = this@DownloadTaskManager
|
updateTaskCallback = { updateTask(it) }
|
||||||
downloadStage = DownloadStage.valueOf(cursor.getStringOrNull("downloadStage")!!)
|
downloadStage = DownloadStage.valueOf(cursor.getStringOrNull("downloadStage")!!)
|
||||||
//if downloadStage is not saved, it means the app was killed before the download was finished
|
//if downloadStage is not saved, it means the app was killed before the download was finished
|
||||||
if (downloadStage != DownloadStage.SAVED) {
|
if (downloadStage != DownloadStage.SAVED) {
|
@ -42,6 +42,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.blur
|
import androidx.compose.ui.draw.blur
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@ -49,27 +50,42 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import me.rhunk.snapenhance.core.download.data.MediaDownloadSource
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadObject
|
import me.rhunk.snapenhance.download.DownloadObject
|
||||||
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
|
||||||
import me.rhunk.snapenhance.ui.manager.Section
|
import me.rhunk.snapenhance.ui.manager.Section
|
||||||
import me.rhunk.snapenhance.ui.util.BitmojiImage
|
import me.rhunk.snapenhance.ui.util.BitmojiImage
|
||||||
import me.rhunk.snapenhance.ui.util.ImageRequestHelper
|
import me.rhunk.snapenhance.ui.util.ImageRequestHelper
|
||||||
|
|
||||||
class DownloadsSection : Section() {
|
class DownloadsSection : Section() {
|
||||||
private val loadedDownloads = mutableStateOf(mapOf<Int, DownloadObject>())
|
private val loadedDownloads = mutableStateOf(mapOf<Int, DownloadObject>())
|
||||||
private var currentFilter = mutableStateOf(MediaFilter.NONE)
|
private var currentFilter = mutableStateOf(MediaDownloadSource.NONE)
|
||||||
|
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
override fun onResumed() {
|
override fun onResumed() {
|
||||||
super.onResumed()
|
super.onResumed()
|
||||||
loadByFilter(currentFilter.value)
|
coroutineScope.launch {
|
||||||
|
loadByFilter(currentFilter.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadByFilter(filter: MediaFilter) {
|
private fun loadByFilter(filter: MediaDownloadSource) {
|
||||||
this.currentFilter.value = filter
|
this.currentFilter.value = filter
|
||||||
synchronized(loadedDownloads) {
|
synchronized(loadedDownloads) {
|
||||||
loadedDownloads.value = context.downloadTaskManager.queryFirstTasks(filter)
|
loadedDownloads.value = context.downloadTaskManager.queryFirstTasks(filter).toMutableMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeTask(download: DownloadObject) {
|
||||||
|
synchronized(loadedDownloads) {
|
||||||
|
loadedDownloads.value = loadedDownloads.value.toMutableMap().also {
|
||||||
|
it.remove(download.downloadId)
|
||||||
|
}
|
||||||
|
context.downloadTaskManager.removeTask(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +103,6 @@ class DownloadsSection : Section() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FilterList() {
|
private fun FilterList() {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
IconButton(onClick = { showMenu = !showMenu}) {
|
IconButton(onClick = { showMenu = !showMenu}) {
|
||||||
Icon(
|
Icon(
|
||||||
@ -97,7 +112,7 @@ class DownloadsSection : Section() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
|
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
|
||||||
MediaFilter.values().toList().forEach { filter ->
|
MediaDownloadSource.values().toList().forEach { filter ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = {
|
text = {
|
||||||
Row(
|
Row(
|
||||||
@ -110,7 +125,7 @@ class DownloadsSection : Section() {
|
|||||||
selected = (currentFilter.value == filter),
|
selected = (currentFilter.value == filter),
|
||||||
onClick = null
|
onClick = null
|
||||||
)
|
)
|
||||||
Text(filter.name, modifier = Modifier.weight(1f))
|
Text(filter.displayName, modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -144,11 +159,12 @@ class DownloadsSection : Section() {
|
|||||||
context.androidContext,
|
context.androidContext,
|
||||||
download.outputFile
|
download.outputFile
|
||||||
),
|
),
|
||||||
imageLoader = context.imageLoader
|
imageLoader = context.imageLoader,
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.matchParentSize()
|
.matchParentSize()
|
||||||
.blur(12.dp),
|
.blur(5.dp),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.FillWidth
|
contentScale = ContentScale.FillWidth
|
||||||
)
|
)
|
||||||
@ -156,9 +172,9 @@ class DownloadsSection : Section() {
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 10.dp, end = 10.dp)
|
.padding(start = 10.dp, end = 10.dp)
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
){
|
){
|
||||||
//info card
|
//info card
|
||||||
Row(
|
Row(
|
||||||
@ -177,13 +193,13 @@ class DownloadsSection : Section() {
|
|||||||
verticalArrangement = Arrangement.SpaceBetween
|
verticalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = download.metadata.mediaDisplayType ?: "",
|
text = MediaDownloadSource.fromKey(download.metadata.downloadSource).displayName,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = download.metadata.mediaDisplaySource ?: "",
|
text = download.metadata.mediaAuthor ?: "",
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
fontWeight = FontWeight.Light
|
fontWeight = FontWeight.Light
|
||||||
@ -191,16 +207,17 @@ class DownloadsSection : Section() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
//action buttons
|
//action buttons
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(5.dp),
|
.padding(5.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
removeTask(download)
|
||||||
},
|
},
|
||||||
colors = IconButtonDefaults.iconButtonColors(
|
colors = IconButtonDefaults.iconButtonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.error,
|
containerColor = MaterialTheme.colorScheme.error,
|
||||||
@ -240,6 +257,7 @@ class DownloadsSection : Section() {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val scrollState = rememberLazyListState()
|
val scrollState = rememberLazyListState()
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = scrollState,
|
state = scrollState,
|
||||||
@ -252,14 +270,19 @@ class DownloadsSection : Section() {
|
|||||||
item {
|
item {
|
||||||
Spacer(Modifier.height(20.dp))
|
Spacer(Modifier.height(20.dp))
|
||||||
if (loadedDownloads.value.isEmpty()) {
|
if (loadedDownloads.value.isEmpty()) {
|
||||||
Text(text = "No downloads", fontSize = 20.sp, modifier = Modifier
|
Text(text = "(empty)", fontSize = 20.sp, modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(10.dp), textAlign = TextAlign.Center)
|
.padding(10.dp), textAlign = TextAlign.Center)
|
||||||
}
|
}
|
||||||
LaunchedEffect(true) {
|
LaunchedEffect(Unit) {
|
||||||
val lastItemIndex = (loadedDownloads.value.size - 1).takeIf { it >= 0 } ?: return@LaunchedEffect
|
val lastItemIndex = (loadedDownloads.value.size - 1).takeIf { it >= 0 } ?: return@LaunchedEffect
|
||||||
lazyLoadFromIndex(lastItemIndex)
|
scope.launch(Dispatchers.IO) {
|
||||||
scrollState.animateScrollToItem(lastItemIndex)
|
lazyLoadFromIndex(lastItemIndex)
|
||||||
|
}.asCompletableFuture().thenAccept {
|
||||||
|
scope.launch {
|
||||||
|
scrollState.animateScrollToItem(lastItemIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,8 @@ object ImageRequestHelper {
|
|||||||
fun newDownloadPreviewImageRequest(context: Context, filePath: String?) = ImageRequest.Builder(context)
|
fun newDownloadPreviewImageRequest(context: Context, filePath: String?) = ImageRequest.Builder(context)
|
||||||
.data(filePath)
|
.data(filePath)
|
||||||
.cacheKey(filePath)
|
.cacheKey(filePath)
|
||||||
|
.memoryCacheKey(filePath)
|
||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
|
.crossfade(200)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
@ -388,11 +388,12 @@
|
|||||||
"conversation_info": "\uD83D\uDC64 Conversation Info"
|
"conversation_info": "\uD83D\uDC64 Conversation Info"
|
||||||
},
|
},
|
||||||
"path_format": {
|
"path_format": {
|
||||||
"create_user_folder": "Create folder for each user",
|
"create_author_folder": "Create folder for each author",
|
||||||
|
"create_source_folder": "Create folder for each media source type",
|
||||||
"append_hash": "Add a unique hash to the file name",
|
"append_hash": "Add a unique hash to the file name",
|
||||||
|
"append_source": "Add the media source to the file name",
|
||||||
"append_username": "Add the username to the file name",
|
"append_username": "Add the username to the file name",
|
||||||
"append_date_time": "Add the date and time to the file name",
|
"append_date_time": "Add the date and time to the file name"
|
||||||
"append_type": "Add the media type to the file name"
|
|
||||||
},
|
},
|
||||||
"auto_download_sources": {
|
"auto_download_sources": {
|
||||||
"friend_snaps": "Friend Snaps",
|
"friend_snaps": "Friend Snaps",
|
||||||
|
@ -14,11 +14,12 @@ class DownloaderConfig : ConfigContainer() {
|
|||||||
)
|
)
|
||||||
val preventSelfAutoDownload = boolean("prevent_self_auto_download")
|
val preventSelfAutoDownload = boolean("prevent_self_auto_download")
|
||||||
val pathFormat = multiple("path_format",
|
val pathFormat = multiple("path_format",
|
||||||
"create_user_folder",
|
"create_author_folder",
|
||||||
|
"create_source_folder",
|
||||||
"append_hash",
|
"append_hash",
|
||||||
|
"append_source",
|
||||||
|
"append_username",
|
||||||
"append_date_time",
|
"append_date_time",
|
||||||
"append_type",
|
|
||||||
"append_username"
|
|
||||||
).apply { set(mutableListOf("append_hash", "append_date_time", "append_type", "append_username")) }
|
).apply { set(mutableListOf("append_hash", "append_date_time", "append_type", "append_username")) }
|
||||||
val allowDuplicate = boolean("allow_duplicate")
|
val allowDuplicate = boolean("allow_duplicate")
|
||||||
val mergeOverlays = boolean("merge_overlays") { addNotices(FeatureNotice.MAY_CAUSE_CRASHES) }
|
val mergeOverlays = boolean("merge_overlays") { addNotices(FeatureNotice.MAY_CAUSE_CRASHES) }
|
||||||
|
@ -3,7 +3,7 @@ package me.rhunk.snapenhance.core.download.data
|
|||||||
data class DownloadMetadata(
|
data class DownloadMetadata(
|
||||||
val mediaIdentifier: String?,
|
val mediaIdentifier: String?,
|
||||||
val outputPath: String,
|
val outputPath: String,
|
||||||
val mediaDisplaySource: String?,
|
val mediaAuthor: String?,
|
||||||
val mediaDisplayType: String?,
|
val downloadSource: String,
|
||||||
val iconUrl: String?
|
val iconUrl: String?
|
||||||
)
|
)
|
@ -0,0 +1,28 @@
|
|||||||
|
package me.rhunk.snapenhance.core.download.data
|
||||||
|
|
||||||
|
enum class MediaDownloadSource(
|
||||||
|
val key: String,
|
||||||
|
val displayName: String = key,
|
||||||
|
val pathName: String = key,
|
||||||
|
val ignoreFilter: Boolean = false
|
||||||
|
) {
|
||||||
|
NONE("none", "None", ignoreFilter = true),
|
||||||
|
PENDING("pending", "Pending", ignoreFilter = true),
|
||||||
|
CHAT_MEDIA("chat_media", "Chat Media", "chat_media"),
|
||||||
|
STORY("story", "Story", "story"),
|
||||||
|
PUBLIC_STORY("public_story", "Public Story", "public_story"),
|
||||||
|
SPOTLIGHT("spotlight", "Spotlight", "spotlight"),
|
||||||
|
PROFILE_PICTURE("profile_picture", "Profile Picture", "profile_picture");
|
||||||
|
|
||||||
|
fun matches(source: String?): Boolean {
|
||||||
|
if (source == null) return false
|
||||||
|
return source.contains(key, ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromKey(key: String?): MediaDownloadSource {
|
||||||
|
if (key == null) return NONE
|
||||||
|
return values().find { it.key == key } ?: NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.core.download.data
|
|
||||||
|
|
||||||
enum class MediaFilter(
|
|
||||||
val key: String,
|
|
||||||
val shouldIgnoreFilter: Boolean = false
|
|
||||||
) {
|
|
||||||
NONE("none", true),
|
|
||||||
PENDING("pending", true),
|
|
||||||
CHAT_MEDIA("chat_media"),
|
|
||||||
STORY("story"),
|
|
||||||
SPOTLIGHT("spotlight"),
|
|
||||||
PROFILE_PICTURE("profile_picture");
|
|
||||||
|
|
||||||
fun matches(source: String?): Boolean {
|
|
||||||
if (source == null) return false
|
|
||||||
return source.contains(key, ignoreCase = true)
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ import me.rhunk.snapenhance.core.download.DownloadManagerClient
|
|||||||
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
|
||||||
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
|
||||||
import me.rhunk.snapenhance.core.download.data.InputMedia
|
import me.rhunk.snapenhance.core.download.data.InputMedia
|
||||||
import me.rhunk.snapenhance.core.download.data.MediaFilter
|
import me.rhunk.snapenhance.core.download.data.MediaDownloadSource
|
||||||
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
|
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
|
||||||
import me.rhunk.snapenhance.core.download.data.toKeyPair
|
import me.rhunk.snapenhance.core.download.data.toKeyPair
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
@ -45,16 +45,20 @@ import kotlin.coroutines.suspendCoroutine
|
|||||||
import kotlin.io.encoding.Base64
|
import kotlin.io.encoding.Base64
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
|
private fun String.sanitizeForPath(): String {
|
||||||
|
return this.replace(" ", "_")
|
||||||
|
.replace(Regex("\\p{Cntrl}"), "")
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleType.AUTO_DOWNLOAD, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleType.AUTO_DOWNLOAD, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
private var lastSeenMediaInfoMap: MutableMap<SplitMediaAssetType, MediaInfo>? = null
|
private var lastSeenMediaInfoMap: MutableMap<SplitMediaAssetType, MediaInfo>? = null
|
||||||
private var lastSeenMapParams: ParamMap? = null
|
private var lastSeenMapParams: ParamMap? = null
|
||||||
|
|
||||||
private fun provideDownloadManagerClient(
|
private fun provideDownloadManagerClient(
|
||||||
pathSuffix: String,
|
|
||||||
mediaIdentifier: String,
|
mediaIdentifier: String,
|
||||||
mediaDisplaySource: String? = null,
|
mediaAuthor: String,
|
||||||
mediaDisplayType: String? = null,
|
downloadSource: MediaDownloadSource,
|
||||||
friendInfo: FriendInfo? = null
|
friendInfo: FriendInfo? = null
|
||||||
): DownloadManagerClient {
|
): DownloadManagerClient {
|
||||||
val generatedHash = mediaIdentifier.hashCode().toString(16).replaceFirst("-", "")
|
val generatedHash = mediaIdentifier.hashCode().toString(16).replaceFirst("-", "")
|
||||||
@ -66,7 +70,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
context.shortToast(context.translation["download_processor.download_started_toast"])
|
context.shortToast(context.translation["download_processor.download_started_toast"])
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputPath = createNewFilePath(generatedHash, mediaDisplayType, pathSuffix)
|
val outputPath = createNewFilePath(generatedHash, downloadSource, mediaAuthor)
|
||||||
|
|
||||||
return DownloadManagerClient(
|
return DownloadManagerClient(
|
||||||
context = context,
|
context = context,
|
||||||
@ -74,8 +78,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
mediaIdentifier = if (!context.config.downloader.allowDuplicate.get()) {
|
mediaIdentifier = if (!context.config.downloader.allowDuplicate.get()) {
|
||||||
generatedHash
|
generatedHash
|
||||||
} else null,
|
} else null,
|
||||||
mediaDisplaySource = mediaDisplaySource,
|
mediaAuthor = mediaAuthor,
|
||||||
mediaDisplayType = mediaDisplayType,
|
downloadSource = downloadSource.key,
|
||||||
iconUrl = iconUrl,
|
iconUrl = iconUrl,
|
||||||
outputPath = outputPath
|
outputPath = outputPath
|
||||||
),
|
),
|
||||||
@ -106,13 +110,9 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//TODO: implement subfolder argument
|
private fun createNewFilePath(hexHash: String, downloadSource: MediaDownloadSource, mediaAuthor: String): String {
|
||||||
private fun createNewFilePath(hexHash: String, mediaDisplayType: String?, pathPrefix: String): String {
|
|
||||||
val pathFormat by context.config.downloader.pathFormat
|
val pathFormat by context.config.downloader.pathFormat
|
||||||
val sanitizedPathPrefix = pathPrefix
|
val sanitizedMediaAuthor = mediaAuthor.sanitizeForPath().ifEmpty { hexHash }
|
||||||
.replace(" ", "_")
|
|
||||||
.replace(Regex("[\\p{Cntrl}]"), "")
|
|
||||||
.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())
|
||||||
|
|
||||||
@ -126,19 +126,20 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pathFormat.contains("create_user_folder")) {
|
if (pathFormat.contains("create_author_folder")) {
|
||||||
finalPath.append(sanitizedPathPrefix).append("/")
|
finalPath.append(sanitizedMediaAuthor).append("/")
|
||||||
|
}
|
||||||
|
if (pathFormat.contains("create_source_folder")) {
|
||||||
|
finalPath.append(downloadSource.pathName).append("/")
|
||||||
}
|
}
|
||||||
if (pathFormat.contains("append_hash")) {
|
if (pathFormat.contains("append_hash")) {
|
||||||
appendFileName(hexHash)
|
appendFileName(hexHash)
|
||||||
}
|
}
|
||||||
mediaDisplayType?.let {
|
if (pathFormat.contains("append_source")) {
|
||||||
if (pathFormat.contains("append_type")) {
|
appendFileName(downloadSource.pathName)
|
||||||
appendFileName(it.lowercase().replace(" ", "-"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (pathFormat.contains("append_username")) {
|
if (pathFormat.contains("append_username")) {
|
||||||
appendFileName(sanitizedPathPrefix)
|
appendFileName(sanitizedMediaAuthor)
|
||||||
}
|
}
|
||||||
if (pathFormat.contains("append_date_time")) {
|
if (pathFormat.contains("append_date_time")) {
|
||||||
appendFileName(currentDateTime)
|
appendFileName(currentDateTime)
|
||||||
@ -235,10 +236,9 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
val authorUsername = author.usernameForSorting!!
|
val authorUsername = author.usernameForSorting!!
|
||||||
|
|
||||||
downloadOperaMedia(provideDownloadManagerClient(
|
downloadOperaMedia(provideDownloadManagerClient(
|
||||||
pathSuffix = authorUsername,
|
|
||||||
mediaIdentifier = "$conversationId$senderId${conversationMessage.serverMessageId}",
|
mediaIdentifier = "$conversationId$senderId${conversationMessage.serverMessageId}",
|
||||||
mediaDisplaySource = authorUsername,
|
mediaAuthor = authorUsername,
|
||||||
mediaDisplayType = MediaFilter.CHAT_MEDIA.key,
|
downloadSource = MediaDownloadSource.CHAT_MEDIA,
|
||||||
friendInfo = author
|
friendInfo = author
|
||||||
), mediaInfoMap)
|
), mediaInfoMap)
|
||||||
|
|
||||||
@ -278,10 +278,9 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
if (!forceDownload && !canUseRule(author.userId!!)) return
|
if (!forceDownload && !canUseRule(author.userId!!)) return
|
||||||
|
|
||||||
downloadOperaMedia(provideDownloadManagerClient(
|
downloadOperaMedia(provideDownloadManagerClient(
|
||||||
pathSuffix = authorName,
|
|
||||||
mediaIdentifier = paramMap["MEDIA_ID"].toString(),
|
mediaIdentifier = paramMap["MEDIA_ID"].toString(),
|
||||||
mediaDisplaySource = authorName,
|
mediaAuthor = authorName,
|
||||||
mediaDisplayType = MediaFilter.STORY.key,
|
downloadSource = MediaDownloadSource.STORY,
|
||||||
friendInfo = author
|
friendInfo = author
|
||||||
), mediaInfoMap)
|
), mediaInfoMap)
|
||||||
return
|
return
|
||||||
@ -292,15 +291,12 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
//public stories
|
//public stories
|
||||||
if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") &&
|
if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") &&
|
||||||
(forceDownload || canAutoDownload("public_stories"))) {
|
(forceDownload || canAutoDownload("public_stories"))) {
|
||||||
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 "").sanitizeForPath()
|
||||||
"[\\p{Cntrl}]".toRegex(),
|
|
||||||
"")
|
|
||||||
|
|
||||||
downloadOperaMedia(provideDownloadManagerClient(
|
downloadOperaMedia(provideDownloadManagerClient(
|
||||||
pathSuffix = "Public-Stories/$userDisplayName",
|
|
||||||
mediaIdentifier = paramMap["SNAP_ID"].toString(),
|
mediaIdentifier = paramMap["SNAP_ID"].toString(),
|
||||||
mediaDisplayType = userDisplayName,
|
mediaAuthor = userDisplayName,
|
||||||
mediaDisplaySource = "Public Story"
|
downloadSource = MediaDownloadSource.PUBLIC_STORY,
|
||||||
), mediaInfoMap)
|
), mediaInfoMap)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -308,10 +304,9 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
//spotlight
|
//spotlight
|
||||||
if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || canAutoDownload("spotlight"))) {
|
if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || canAutoDownload("spotlight"))) {
|
||||||
downloadOperaMedia(provideDownloadManagerClient(
|
downloadOperaMedia(provideDownloadManagerClient(
|
||||||
pathSuffix = "Spotlight",
|
|
||||||
mediaIdentifier = paramMap["SNAP_ID"].toString(),
|
mediaIdentifier = paramMap["SNAP_ID"].toString(),
|
||||||
mediaDisplayType = MediaFilter.SPOTLIGHT.key,
|
downloadSource = MediaDownloadSource.SPOTLIGHT,
|
||||||
mediaDisplaySource = paramMap["TIME_STAMP"].toString()
|
mediaAuthor = paramMap["TIME_STAMP"].toString()
|
||||||
), mediaInfoMap)
|
), mediaInfoMap)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -319,9 +314,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
//stories with mpeg dash media
|
//stories with mpeg dash media
|
||||||
//TODO: option to download multiple chapters
|
//TODO: option to download multiple chapters
|
||||||
if (paramMap.containsKey("LONGFORM_VIDEO_PLAYLIST_ITEM") && forceDownload) {
|
if (paramMap.containsKey("LONGFORM_VIDEO_PLAYLIST_ITEM") && forceDownload) {
|
||||||
val storyName = paramMap["STORY_NAME"].toString().replace(
|
val storyName = paramMap["STORY_NAME"].toString().sanitizeForPath()
|
||||||
"[\\p{Cntrl}]".toRegex(),
|
|
||||||
"")
|
|
||||||
|
|
||||||
//get the position of the media in the playlist and the duration
|
//get the position of the media in the playlist and the duration
|
||||||
val snapItem = SnapPlaylistItem(paramMap["SNAP_PLAYLIST_ITEM"]!!)
|
val snapItem = SnapPlaylistItem(paramMap["SNAP_PLAYLIST_ITEM"]!!)
|
||||||
@ -338,20 +331,19 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
val duration: Long? = nextChapter?.startTimeMs?.minus(snapChapterTimestamp)
|
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
|
||||||
|
context.log.verbose("Downloading dash media ${paramMap["MEDIA_ID"].toString()}", featureKey)
|
||||||
val playlistUrl = paramMap["MEDIA_ID"].toString().let {
|
val playlistUrl = paramMap["MEDIA_ID"].toString().let {
|
||||||
val urlIndex = it.indexOf("https://cf-st.sc-cdn.net")
|
val urlIndexes = arrayOf(it.indexOf("https://cf-st.sc-cdn.net"), it.indexOf("https://bolt-gcdn.sc-cdn.net"))
|
||||||
if (urlIndex == -1) {
|
|
||||||
"${RemoteMediaResolver.CF_ST_CDN_D}$it"
|
urlIndexes.firstOrNull { index -> index != -1 }?.let { validIndex ->
|
||||||
} else {
|
it.substring(validIndex)
|
||||||
it.substring(urlIndex)
|
} ?: "${RemoteMediaResolver.CF_ST_CDN_D}$it"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provideDownloadManagerClient(
|
provideDownloadManagerClient(
|
||||||
pathSuffix = "Pro-Stories/${storyName}",
|
|
||||||
mediaIdentifier = "${paramMap["STORY_ID"]}-${snapItem.snapId}",
|
mediaIdentifier = "${paramMap["STORY_ID"]}-${snapItem.snapId}",
|
||||||
mediaDisplaySource = storyName,
|
downloadSource = MediaDownloadSource.PUBLIC_STORY,
|
||||||
mediaDisplayType = "Pro Story"
|
mediaAuthor = storyName
|
||||||
).downloadDashMedia(
|
).downloadDashMedia(
|
||||||
playlistUrl,
|
playlistUrl,
|
||||||
snapChapterTimestamp,
|
snapChapterTimestamp,
|
||||||
@ -476,10 +468,9 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
if (!isPreview) {
|
if (!isPreview) {
|
||||||
val encryptionKeys = EncryptionHelper.getEncryptionKeys(contentType, messageReader, isArroyo = isArroyoMessage)
|
val encryptionKeys = EncryptionHelper.getEncryptionKeys(contentType, messageReader, isArroyo = isArroyoMessage)
|
||||||
provideDownloadManagerClient(
|
provideDownloadManagerClient(
|
||||||
pathSuffix = authorName,
|
|
||||||
mediaIdentifier = "${message.clientConversationId}${message.senderId}${message.serverMessageId}",
|
mediaIdentifier = "${message.clientConversationId}${message.senderId}${message.serverMessageId}",
|
||||||
mediaDisplaySource = authorName,
|
downloadSource = MediaDownloadSource.CHAT_MEDIA,
|
||||||
mediaDisplayType = MediaFilter.CHAT_MEDIA.key,
|
mediaAuthor = authorName,
|
||||||
friendInfo = friendInfo
|
friendInfo = friendInfo
|
||||||
).downloadSingleMedia(
|
).downloadSingleMedia(
|
||||||
Base64.UrlSafe.encode(urlProto),
|
Base64.UrlSafe.encode(urlProto),
|
||||||
@ -532,10 +523,9 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
|
|
||||||
fun downloadProfilePicture(url: String, author: String) {
|
fun downloadProfilePicture(url: String, author: String) {
|
||||||
provideDownloadManagerClient(
|
provideDownloadManagerClient(
|
||||||
pathSuffix = "Profile Pictures",
|
|
||||||
mediaIdentifier = url.hashCode().toString(16).replaceFirst("-", ""),
|
mediaIdentifier = url.hashCode().toString(16).replaceFirst("-", ""),
|
||||||
mediaDisplaySource = author,
|
mediaAuthor = author,
|
||||||
mediaDisplayType = MediaFilter.PROFILE_PICTURE.key
|
downloadSource = MediaDownloadSource.PROFILE_PICTURE
|
||||||
).downloadSingleMedia(
|
).downloadSingleMedia(
|
||||||
url,
|
url,
|
||||||
DownloadMediaType.REMOTE_MEDIA
|
DownloadMediaType.REMOTE_MEDIA
|
||||||
|
@ -12,13 +12,13 @@ import javax.crypto.spec.SecretKeySpec
|
|||||||
|
|
||||||
object EncryptionHelper {
|
object EncryptionHelper {
|
||||||
fun getEncryptionKeys(contentType: ContentType, messageProto: ProtoReader, isArroyo: Boolean): Pair<ByteArray, ByteArray>? {
|
fun getEncryptionKeys(contentType: ContentType, messageProto: ProtoReader, isArroyo: Boolean): Pair<ByteArray, ByteArray>? {
|
||||||
val messageMediaInfo = MediaDownloaderHelper.getMessageMediaInfo(messageProto, contentType, isArroyo) ?: return null
|
val mediaEncryptionInfo = MediaDownloaderHelper.getMessageMediaEncryptionInfo(messageProto, contentType, isArroyo) ?: return null
|
||||||
val encryptionProtoIndex = if (messageMediaInfo.contains(Constants.ENCRYPTION_PROTO_INDEX_V2)) {
|
val encryptionProtoIndex = if (mediaEncryptionInfo.contains(Constants.ENCRYPTION_PROTO_INDEX_V2)) {
|
||||||
Constants.ENCRYPTION_PROTO_INDEX_V2
|
Constants.ENCRYPTION_PROTO_INDEX_V2
|
||||||
} else {
|
} else {
|
||||||
Constants.ENCRYPTION_PROTO_INDEX
|
Constants.ENCRYPTION_PROTO_INDEX
|
||||||
}
|
}
|
||||||
val encryptionProto = messageMediaInfo.followPath(encryptionProtoIndex) ?: return null
|
val encryptionProto = mediaEncryptionInfo.followPath(encryptionProtoIndex) ?: return null
|
||||||
|
|
||||||
var key: ByteArray = encryptionProto.getByteArray(1)!!
|
var key: ByteArray = encryptionProto.getByteArray(1)!!
|
||||||
var iv: ByteArray = encryptionProto.getByteArray(2)!!
|
var iv: ByteArray = encryptionProto.getByteArray(2)!!
|
||||||
|
@ -18,7 +18,7 @@ import java.util.zip.ZipInputStream
|
|||||||
|
|
||||||
|
|
||||||
object MediaDownloaderHelper {
|
object MediaDownloaderHelper {
|
||||||
fun getMessageMediaInfo(protoReader: ProtoReader, contentType: ContentType, isArroyo: Boolean): ProtoReader? {
|
fun getMessageMediaEncryptionInfo(protoReader: ProtoReader, contentType: ContentType, isArroyo: Boolean): ProtoReader? {
|
||||||
val messageContainerPath = if (isArroyo) protoReader.followPath(*Constants.ARROYO_MEDIA_CONTAINER_PROTO_PATH)!! else protoReader
|
val messageContainerPath = if (isArroyo) protoReader.followPath(*Constants.ARROYO_MEDIA_CONTAINER_PROTO_PATH)!! else protoReader
|
||||||
val mediaContainerPath = if (contentType == ContentType.NOTE) intArrayOf(6, 1, 1) else intArrayOf(5, 1, 1)
|
val mediaContainerPath = if (contentType == ContentType.NOTE) intArrayOf(6, 1, 1) else intArrayOf(5, 1, 1)
|
||||||
|
|
||||||
@ -27,12 +27,13 @@ object MediaDownloaderHelper {
|
|||||||
ContentType.SNAP -> messageContainerPath.followPath(*(intArrayOf(11) + mediaContainerPath))
|
ContentType.SNAP -> messageContainerPath.followPath(*(intArrayOf(11) + mediaContainerPath))
|
||||||
ContentType.EXTERNAL_MEDIA -> {
|
ContentType.EXTERNAL_MEDIA -> {
|
||||||
val externalMediaTypes = arrayOf(
|
val externalMediaTypes = arrayOf(
|
||||||
intArrayOf(3, 3), //normal external media
|
intArrayOf(3, 3, *mediaContainerPath), //normal external media
|
||||||
intArrayOf(7, 12, 3), //attached story reply
|
intArrayOf(7, 15, 1, 1), //attached audio note
|
||||||
intArrayOf(7, 3) //original story reply
|
intArrayOf(7, 12, 3, *mediaContainerPath), //attached story reply
|
||||||
|
intArrayOf(7, 3, *mediaContainerPath), //original story reply
|
||||||
)
|
)
|
||||||
externalMediaTypes.forEach { path ->
|
externalMediaTypes.forEach { path ->
|
||||||
messageContainerPath.followPath(*(path + mediaContainerPath))?.also { return it }
|
messageContainerPath.followPath(*path)?.also { return it }
|
||||||
}
|
}
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user