Skip to content

Commit

Permalink
Add feature to export chats to text file.
Browse files Browse the repository at this point in the history
  • Loading branch information
rodit committed May 19, 2022
1 parent 1963c4b commit 5ea217b
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 16 deletions.
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 @@ -12,6 +12,7 @@ object CustomResources {
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.menu_option_export to "Export...",

string.chat_action_playback_speed to "Set Playback Speed"
)
Expand Down Expand Up @@ -41,6 +42,7 @@ object CustomResources {
const val menu_option_preview = -100001
const val menu_option_auto_save = -100002
const val menu_option_auto_download = -100003
const val menu_option_export = -100004

const val chat_action_playback_speed = -200000
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/xyz/rodit/snapmod/arroyo/ArroyoMessage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package xyz.rodit.snapmod.arroyo

data class ArroyoMessage(val content: String, val timestamp: Long, val senderId: String)
32 changes: 31 additions & 1 deletion app/src/main/java/xyz/rodit/snapmod/arroyo/ArroyoReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,33 @@ class ArroyoReader(private val context: Context) {

fun getMessageContent(conversationId: String, messageId: String): String? {
val blob = getMessageBlob(conversationId, messageId) ?: return null
return followProtoString(blob, 4, 4, 2, 1)
return readChatMessageContent(blob)
}

fun getAllMessages(conversationId: String, after: Long = 0): Pair<List<ArroyoMessage>, Set<String>> {
val messages = mutableListOf<ArroyoMessage>()
val senderIds = hashSetOf<String>()
SQLiteDatabase.openDatabase(
File(context.filesDir, "../databases/arroyo.db").path,
null,
0
).use {
it.rawQuery(
"SELECT message_content,creation_timestamp,sender_id FROM conversation_message WHERE client_conversation_id='$conversationId' AND creation_timestamp>$after AND content_type=1 ORDER BY creation_timestamp ASC",
null
).use { cursor ->
while (cursor.moveToNext()) {
val content = cursor.getBlob(0)
val timestamp = cursor.getLong(1)
val senderId = cursor.getString(2)
val contentString = readChatMessageContent(content) ?: continue
messages.add(ArroyoMessage(contentString, timestamp, senderId))
senderIds.add(senderId)
}
}
}

return messages to senderIds
}

fun getKeyAndIv(conversationId: String, messageId: String): Pair<ByteArray, ByteArray>? {
Expand All @@ -28,6 +54,10 @@ class ArroyoReader(private val context: Context) {
return key to iv
}

private fun readChatMessageContent(blob: ByteArray): String? {
return followProtoString(blob, 4, 4, 2, 1)
}

private fun followProtoString(data: ByteArray, vararg indices: Int): String? {
val proto = followProto(data, *indices)
return if (proto != null) String(proto) else null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package xyz.rodit.snapmod.features

import android.app.Activity
import android.content.Context
import xyz.rodit.snapmod.arroyo.ArroyoReader
import xyz.rodit.snapmod.features.callbacks.CallbackManager
import xyz.rodit.snapmod.util.ConversationManager
import xyz.rodit.xposed.client.ConfigurationClient
Expand All @@ -27,6 +28,7 @@ class FeatureContext(
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)
val arroyo = ArroyoReader(appContext)

var activity: Activity? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class ChatMenuModifier(context: FeatureContext) : Feature(context) {

override fun init() {
registerPlugin(PreviewOption(context))
registerPlugin(ExportOption(context))

val pinTextResource = context.appContext.resources.getIdentifier(
PIN_STRING_NAME,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package xyz.rodit.snapmod.features.chatmenu

import android.content.Intent
import androidx.core.content.FileProvider
import xyz.rodit.snapmod.CustomResources.string.menu_option_export
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.mappings.SelectFriendsByUserIds
import java.io.File
import java.text.SimpleDateFormat
import java.util.*

class ExportOption(context: FeatureContext):
ButtonOption(context, "export_chat", menu_option_export) {

override fun shouldCreate() = true

override fun handleEvent(data: String?) {
if (data == null) return

val (messages, senders) = context.arroyo.getAllMessages(data)
val friendData =
context.instances.friendsRepository.selectFriendsByUserIds(senders.toList())
val senderMap = friendData.map(SelectFriendsByUserIds::wrap).associateBy { u -> u.userId }

val dateFormat = SimpleDateFormat("dd/MM/yyyy, HH:mm:ss", Locale.getDefault())

val temp = File.createTempFile(
"Snapchat Export ",
".txt",
File(context.appContext.filesDir, "file_manager/media")
)
temp.deleteOnExit()
temp.bufferedWriter().use {
messages.forEach { m ->
val username = senderMap[m.senderId]?.displayName ?: "Unknown"
val dateTime = dateFormat.format(m.timestamp)
it.append(dateTime)
.append(" - ")
.append(username)
.append(": ")
.appendLine(m.content)
}
}

val intent = Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(
Intent.EXTRA_STREAM,
FileProvider.getUriForFile(
context.appContext,
"com.snapchat.android.media.fileprovider",
temp
)
)
context.activity?.startActivity(Intent.createChooser(intent, "Export Chat"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package xyz.rodit.snapmod.features.chatmenu

import android.app.AlertDialog
import de.robv.android.xposed.XC_MethodHook
import xyz.rodit.snapmod.CustomResources
import xyz.rodit.snapmod.CustomResources.string.menu_option_preview
import xyz.rodit.snapmod.createDummyProxy
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.mappings.*
Expand All @@ -11,11 +11,9 @@ import xyz.rodit.snapmod.util.toUUIDString
import java.lang.Integer.min

class PreviewOption(context: FeatureContext) :
ButtonOption(context, "preview", CustomResources.string.menu_option_preview) {
ButtonOption(context, "preview", menu_option_preview) {

override fun shouldCreate(): Boolean {
return true
}
override fun shouldCreate() = true

override fun handleEvent(data: String?) {
if (data == null) return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import androidx.core.app.NotificationCompat
import com.google.gson.Gson
import com.google.gson.JsonArray
import xyz.rodit.snapmod.Shared
import xyz.rodit.snapmod.arroyo.ArroyoReader
import xyz.rodit.snapmod.features.Feature
import xyz.rodit.snapmod.features.FeatureContext
import xyz.rodit.snapmod.logging.log
Expand All @@ -38,7 +37,6 @@ private val imageSig = listOf(

class ShowMessageContent(context: FeatureContext) : Feature(context, 84608.toMax()) {

private val arroyoReader = ArroyoReader(context.appContext)
private val gson: Gson = Gson()

private val notifications
Expand Down Expand Up @@ -78,9 +76,9 @@ class ShowMessageContent(context: FeatureContext) : Feature(context, 84608.toMax

val snap = type == "snap"
val (key, iv) = (if (snap)
arroyoReader.getSnapKeyAndIv(conversationId, messageId)
context.arroyo.getSnapKeyAndIv(conversationId, messageId)
else
arroyoReader.getKeyAndIv(conversationId, messageId)) ?: return@before
context.arroyo.getKeyAndIv(conversationId, messageId)) ?: return@before

val media = getDownloadUrls(mediaInfo)
val crypt = AesCrypto(key, iv)
Expand All @@ -92,10 +90,10 @@ class ShowMessageContent(context: FeatureContext) : Feature(context, 84608.toMax
val group = idProvider.conversationIdentifier.group

val title = (bundle.getString("ab_cnotif_body") ?: "sent Media") +
if (snap)
" (${if (isImage) "Image" else "Video"})"
else
" (Media x ${media.size})"
if (snap)
" (${if (isImage) "Image" else "Video"})"
else
" (Media x ${media.size})"

val notification = NotificationCompat.Builder(context.appContext, CHANNEL_ID)
.setContentTitle(bundle.getString("sender") ?: "Unknown Sender")
Expand All @@ -118,7 +116,8 @@ class ShowMessageContent(context: FeatureContext) : Feature(context, 84608.toMax
return@before
}

val content = arroyoReader.getMessageContent(conversationId, messageId) ?: return@before
val content =
context.arroyo.getMessageContent(conversationId, messageId) ?: return@before
bundle.putString("ab_cnotif_body", content)
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/xyz/rodit/snapmod/util/PathManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object PathManager {
const val DOWNLOAD_PROFILE = "profile"
const val DOWNLOAD_SNAP = "snap"

private const val DEFAULT_DATE_FORMAT = "dd-MM-yyyy_HH:mm:ss"
private const val DEFAULT_DATE_FORMAT = "dd-MM-yyyy_HH-mm-ss"

private val PATTERN_PUBLIC_DIR = Pattern.compile("""\$(\w+)""")
private val PATTERN_PARAMETER = Pattern.compile("%([A-Za-z]+)")
Expand Down

0 comments on commit 5ea217b

Please sign in to comment.