Skip to content

Commit

Permalink
Merge pull request #135 from rodit/auto-download-snaps
Browse files Browse the repository at this point in the history
Add feature to auto-download snaps.
  • Loading branch information
rodit authored May 17, 2022
2 parents 0a7cb10 + f2a1730 commit d57fc06
Show file tree
Hide file tree
Showing 16 changed files with 201 additions and 103 deletions.
Binary file modified app/libs/snapmod.jar
Binary file not shown.
2 changes: 2 additions & 0 deletions app/src/main/java/xyz/rodit/snapmod/CustomResources.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ object CustomResources {
string.menu_option_stealth_mode to "Stealth Mode",
string.menu_option_preview to "More Information",
string.menu_option_auto_save to "Auto-Save Messages",
string.menu_option_auto_download to "Auto-Download Snaps",

string.chat_action_playback_speed to "Set Playback Speed"
)
Expand Down Expand Up @@ -39,6 +40,7 @@ object CustomResources {
const val menu_option_stealth_mode = -100000
const val menu_option_preview = -100001
const val menu_option_auto_save = -100002
const val menu_option_auto_download = -100003

const val chat_action_playback_speed = -200000
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import xyz.rodit.xposed.client.http.StreamServer
private const val PINNED_CONVERSATIONS_FILE = "pinned.list"
private const val STEALTH_CONVERSATIONS_FILE = "stealth.list"
private const val AUTO_SAVE_CONVERSATIONS_FILE = "auto_save.list"
private const val AUTO_DOWNLOAD_CONVERSATIONS_FILE = "auto_download.list"

class FeatureContext(
val appContext: Context,
Expand All @@ -25,6 +26,7 @@ class FeatureContext(
val pinned: ConversationManager = ConversationManager(appContext.filesDir, PINNED_CONVERSATIONS_FILE)
val stealth: ConversationManager = ConversationManager(appContext.filesDir, STEALTH_CONVERSATIONS_FILE)
val autoSave: ConversationManager = ConversationManager(appContext.filesDir, AUTO_SAVE_CONVERSATIONS_FILE)
val autoDownload: ConversationManager = ConversationManager(appContext.filesDir, AUTO_DOWNLOAD_CONVERSATIONS_FILE)

var activity: Activity? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import xyz.rodit.snapmod.features.messagemenu.MessageMenuModifier
import xyz.rodit.snapmod.features.notifications.FilterTypes
import xyz.rodit.snapmod.features.notifications.ShowMessageContent
import xyz.rodit.snapmod.features.opera.OperaModelModifier
import xyz.rodit.snapmod.features.saving.AutoDownloadSnaps
import xyz.rodit.snapmod.features.saving.ChatSaving
import xyz.rodit.snapmod.features.saving.PublicProfileSaving
import xyz.rodit.snapmod.features.saving.StoriesSaving
Expand Down Expand Up @@ -45,6 +46,7 @@ class FeatureManager(context: FeatureContext) : Contextual(context) {
add(::OperaModelModifier)

// Saving
add(::AutoDownloadSnaps)
add(::ChatSaving)
add(::PublicProfileSaving)
add(::StoriesSaving)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package xyz.rodit.snapmod.features.chatmenu

import xyz.rodit.snapmod.CustomResources.string.menu_option_auto_download
import xyz.rodit.snapmod.CustomResources.string.menu_option_auto_save
import xyz.rodit.snapmod.CustomResources.string.menu_option_stealth_mode
import xyz.rodit.snapmod.Shared
import xyz.rodit.snapmod.features.Feature
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.mappings.FriendChatActionHandler
Expand All @@ -11,16 +15,28 @@ import xyz.rodit.snapmod.util.before

const val EVENT_PREFIX = "CUSTOM_ACTION"
const val EVENT_DELIMITER = "\u0000:\u0000"
const val PIN_STRING_NAME = "action_menu_pin_conversation"

class ChatMenuModifier(context: FeatureContext) : Feature(context) {

private val plugins: MutableMap<String, MenuPlugin> = HashMap()

override fun init() {
registerPlugin(PinOption(context))
registerPlugin(StealthOption(context))
registerPlugin(PreviewOption(context))
registerPlugin(AutoSaveOption(context))

val pinTextResource = context.appContext.resources.getIdentifier(
PIN_STRING_NAME,
"string",
Shared.SNAPCHAT_PACKAGE
)
registerConversationToggle("pinning", pinTextResource) { it.pinned }
registerConversationToggle("stealth", menu_option_stealth_mode) { it.stealth }
registerConversationToggle("auto_save", menu_option_auto_save) { it.autoSave }
registerConversationToggle("auto_download", menu_option_auto_download) { it.autoDownload }
}

private fun registerConversationToggle(name: String, textResource: Int, manager: Manager) {
registerPlugin(ConversationToggleOption(context, name, textResource, manager))
}

private fun registerPlugin(plugin: MenuPlugin) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package xyz.rodit.snapmod.features.chatmenu

import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.util.ConversationManager
import xyz.rodit.snapmod.util.getList

typealias Manager = (FeatureContext) -> ConversationManager

class ConversationToggleOption(
context: FeatureContext,
name: String,
textResource: Int,
private val manager: Manager
) : ToggleOption(context, name, textResource) {

override fun shouldCreate() = !context.config.getList("hidden_chat_options").contains(name)

override fun isToggled(key: String?) = manager(context).isEnabled(key)

override fun handleEvent(data: String?) = manager(context).toggle(data)
}
25 changes: 0 additions & 25 deletions app/src/main/java/xyz/rodit/snapmod/features/chatmenu/PinOption.kt

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package xyz.rodit.snapmod.features.saving

import xyz.rodit.snapmod.features.Feature
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.mappings.*
import xyz.rodit.snapmod.util.PathManager
import xyz.rodit.snapmod.util.after
import xyz.rodit.snapmod.util.toUUIDString

class AutoDownloadSnaps(context: FeatureContext) : Feature(context) {

private val ignore = hashSetOf<String>()

override fun performHooks() {
OperaPageViewController.onDisplayStateChanged.after {
val viewController = OperaPageViewController.wrap(it.thisObject)
if (viewController.state.instance != OperaDisplayState.FULLY_DISPLAYED().instance) return@after

val params = ParamsMap.wrap(viewController.metadata.instance)
val map = params.map
if (!map.containsKey(MessageStoryKeys.getSnapInSavedState().instance)) return@after

val messageId = map[MessageStoryKeys.getMessageId().instance] as String?
val conversationId = UUID.wrap(map[ConversationStoryKeys.getConversationId().instance])
if (messageId == null || conversationId.isNull) return@after

if (!context.config.getBoolean("auto_download_snaps")
&& !context.autoDownload.isEnabled(conversationId.toUUIDString())
) return@after

if (ignore.contains(messageId)) return@after

ignore.add(messageId)
getMediaInfo(context, params) { info ->
downloadOperaMedia(
context,
PathManager.DOWNLOAD_SNAP,
info
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@ package xyz.rodit.snapmod.features.saving

import xyz.rodit.snapmod.features.Feature
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.logging.log
import xyz.rodit.snapmod.mappings.*
import xyz.rodit.snapmod.util.PathManager
import xyz.rodit.snapmod.util.after
import xyz.rodit.snapmod.util.download
import xyz.rodit.xposed.client.http.StreamProvider
import xyz.rodit.xposed.client.http.streams.FileProxyStreamProvider
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.net.URL

class StoriesSaving(context: FeatureContext) : Feature(context) {

Expand Down Expand Up @@ -42,36 +37,11 @@ class StoriesSaving(context: FeatureContext) : Feature(context) {
if (method.name != ContextClickHandler.invoke.dexName) return null

val map = ParamsMap.wrap(args!![0])
getMediaInfo(context, map, this::downloadStoryMedia)

return null
}

private fun downloadStoryMedia(media: StoryMedia?) {
if (media == null || media.info.isNull) return

val provider: StreamProvider = FileProxyStreamProvider(context.appContext) {
try {
var stream = URL(media.info.uri).openStream()
val enc = media.info.encryption
if (enc.isNotNull) {
stream = enc.decryptStream(stream)
}

return@FileProxyStreamProvider stream
} catch (e: Exception) {
log.error("Error opening story media stream.", e)
}
return@FileProxyStreamProvider null
getMediaInfo(context, map) {
downloadOperaMedia(context, PathManager.DOWNLOAD_STORY, it)
}

context.download(
PathManager.DOWNLOAD_STORY,
mapOf("u" to media.username),
media.extension,
provider,
media.username + "'s Story"
)
return null
}
}
}
37 changes: 35 additions & 2 deletions app/src/main/java/xyz/rodit/snapmod/features/saving/StoryHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import xyz.rodit.snapmod.createDummyProxy
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.logging.XLog
import xyz.rodit.snapmod.mappings.*
import xyz.rodit.snapmod.util.download
import xyz.rodit.snapmod.util.toSnapUUID
import xyz.rodit.snapmod.util.toUUIDString
import xyz.rodit.xposed.client.http.StreamProvider
import xyz.rodit.xposed.client.http.streams.FileProxyStreamProvider
import java.net.URL

private val mLog = XLog("StoryHelper")

Expand All @@ -14,8 +18,10 @@ typealias UsernameFetcher = (Map<*, *>) -> String?
private val usernameFetchers = listOf<UsernameFetcher>(
{
val session = ContextSession.wrap(it[ContextStoryKeys.getContextSession().instance])
val snapUsername = session.info.username
if (snapUsername.isNotNull) snapUsername.displayString else null
if (session.isNull) null else {
val snapUsername = session.info.username
if (snapUsername.isNotNull) snapUsername.displayString else null
}
},
{
val storySnap =
Expand All @@ -24,6 +30,33 @@ private val usernameFetchers = listOf<UsernameFetcher>(
}
)

fun downloadOperaMedia(context: FeatureContext, type: String, media: StoryMedia?) {
if (media == null || media.info.isNull) return

val provider: StreamProvider = FileProxyStreamProvider(context.appContext) {
try {
var stream = URL(media.info.uri).openStream()
val enc = media.info.encryption
if (enc.isNotNull) {
stream = enc.decryptStream(stream)
}

return@FileProxyStreamProvider stream
} catch (e: Exception) {
mLog.error("Error opening story media stream.", e)
}
return@FileProxyStreamProvider null
}

context.download(
type,
mapOf("u" to media.username),
media.extension,
provider,
media.username + "'s Story"
)
}

fun getMediaInfo(
context: FeatureContext, metadata: ParamsMap, callback: (StoryMedia?) -> Unit
) {
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@
<item>NOTE</item>
</string-array>

<string-array name="chat_options">
<item>Pin</item>
<item>Stealth</item>
<item>Auto Save</item>
<item>Auto Download Snaps</item>
</string-array>

<string-array name="chat_options_values">
<item>pinning</item>
<item>stealth</item>
<item>auto_save</item>
<item>auto_download</item>
</string-array>

<string-array name="auto_save_types_defaults">
<item>CHAT</item>
<item>STICKER</item>
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,10 @@

<string name="filtered_notification_types_title">Filtered Message Types</string>
<string name="filtered_notification_types_description">Notifications will not be shown for filtered message types.</string>

<string name="hidden_chat_options_title">Hidden Chat Options</string>
<string name="hidden_chat_options_description">Hides extra chat options (if you feel the chat menu is too cluttered, for example).</string>

<string name="auto_download_snaps_title">Auto-Download Snaps</string>
<string name="auto_download_snaps_description">Automatically downloads snaps when viewed.</string>
</resources>
Loading

0 comments on commit d57fc06

Please sign in to comment.