feat: friend tracker experiment
This commit is contained in:
@@ -5,7 +5,8 @@ import me.rhunk.snapenhance.bridge.DownloadCallback;
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback;
|
||||
import me.rhunk.snapenhance.bridge.scripting.IScripting;
|
||||
import me.rhunk.snapenhance.bridge.e2ee.E2eeInterface;
|
||||
import me.rhunk.snapenhance.bridge.MessageLoggerInterface;
|
||||
import me.rhunk.snapenhance.bridge.logger.LoggerInterface;
|
||||
import me.rhunk.snapenhance.bridge.logger.TrackerInterface;
|
||||
import me.rhunk.snapenhance.bridge.ConfigStateListener;
|
||||
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge;
|
||||
|
||||
@@ -79,7 +80,9 @@ interface BridgeInterface {
|
||||
|
||||
E2eeInterface getE2eeInterface();
|
||||
|
||||
MessageLoggerInterface getMessageLogger();
|
||||
LoggerInterface getLogger();
|
||||
|
||||
TrackerInterface getTracker();
|
||||
|
||||
oneway void registerMessagingBridge(MessagingBridge bridge);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.rhunk.snapenhance.bridge;
|
||||
package me.rhunk.snapenhance.bridge.logger;
|
||||
|
||||
interface MessageLoggerInterface {
|
||||
interface LoggerInterface {
|
||||
/**
|
||||
* Get the ids of the messages that are logged
|
||||
* @return message ids that are logged
|
||||
@@ -26,4 +26,14 @@ interface MessageLoggerInterface {
|
||||
* Add a story to the message logger database if it is not already there
|
||||
*/
|
||||
boolean addStory(String userId, String url, long postedAt, long createdAt, in byte[] key, in byte[] iv);
|
||||
|
||||
oneway void logTrackerEvent(
|
||||
String conversationId,
|
||||
String conversationTitle,
|
||||
boolean isGroup,
|
||||
String username,
|
||||
String userId,
|
||||
String eventType,
|
||||
String data
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.rhunk.snapenhance.bridge.logger;
|
||||
|
||||
interface TrackerInterface {
|
||||
String getTrackedEvents(String eventType); // returns serialized TrackerEventsResult
|
||||
}
|
||||
@@ -3,10 +3,11 @@ package me.rhunk.snapenhance.common.bridge.wrapper
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import kotlinx.coroutines.*
|
||||
import me.rhunk.snapenhance.bridge.MessageLoggerInterface
|
||||
import me.rhunk.snapenhance.bridge.logger.LoggerInterface
|
||||
import me.rhunk.snapenhance.common.data.StoryData
|
||||
import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper
|
||||
import me.rhunk.snapenhance.common.util.ktx.getBlobOrNull
|
||||
import me.rhunk.snapenhance.common.util.ktx.getIntOrNull
|
||||
import me.rhunk.snapenhance.common.util.ktx.getLongOrNull
|
||||
import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
|
||||
import java.io.File
|
||||
@@ -19,9 +20,20 @@ class LoggedMessage(
|
||||
val messageData: ByteArray
|
||||
)
|
||||
|
||||
class MessageLoggerWrapper(
|
||||
class TrackerLog(
|
||||
val timestamp: Long,
|
||||
val conversationId: String,
|
||||
val conversationTitle: String?,
|
||||
val isGroup: Boolean,
|
||||
val username: String,
|
||||
val userId: String,
|
||||
val eventType: String,
|
||||
val data: String
|
||||
)
|
||||
|
||||
class LoggerWrapper(
|
||||
val databaseFile: File
|
||||
): MessageLoggerInterface.Stub() {
|
||||
): LoggerInterface.Stub() {
|
||||
private var _database: SQLiteDatabase? = null
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
|
||||
@@ -47,6 +59,17 @@ class MessageLoggerWrapper(
|
||||
"url VARCHAR",
|
||||
"encryption_key BLOB",
|
||||
"encryption_iv BLOB"
|
||||
),
|
||||
"tracker_events" to listOf(
|
||||
"id INTEGER PRIMARY KEY",
|
||||
"timestamp BIGINT",
|
||||
"conversation_id CHAR(36)",
|
||||
"conversation_title VARCHAR",
|
||||
"is_group BOOLEAN",
|
||||
"username VARCHAR",
|
||||
"user_id VARCHAR",
|
||||
"event_type VARCHAR",
|
||||
"data VARCHAR"
|
||||
)
|
||||
))
|
||||
_database = openedDatabase
|
||||
@@ -159,6 +182,76 @@ class MessageLoggerWrapper(
|
||||
return true
|
||||
}
|
||||
|
||||
override fun logTrackerEvent(
|
||||
conversationId: String,
|
||||
conversationTitle: String?,
|
||||
isGroup: Boolean,
|
||||
username: String,
|
||||
userId: String,
|
||||
eventType: String,
|
||||
data: String
|
||||
) {
|
||||
runBlocking {
|
||||
withContext(coroutineScope.coroutineContext) {
|
||||
database.insert("tracker_events", null, ContentValues().apply {
|
||||
put("timestamp", System.currentTimeMillis())
|
||||
put("conversation_id", conversationId)
|
||||
put("conversation_title", conversationTitle)
|
||||
put("is_group", isGroup)
|
||||
put("username", username)
|
||||
put("user_id", userId)
|
||||
put("event_type", eventType)
|
||||
put("data", data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getLogs(
|
||||
lastTimestamp: Long,
|
||||
filter: ((TrackerLog) -> Boolean)? = null
|
||||
): List<TrackerLog> {
|
||||
return database.rawQuery("SELECT * FROM tracker_events WHERE timestamp < ? ORDER BY timestamp DESC", arrayOf(lastTimestamp.toString())).use {
|
||||
val logs = mutableListOf<TrackerLog>()
|
||||
while (it.moveToNext() && logs.size < 50) {
|
||||
val log = TrackerLog(
|
||||
timestamp = it.getLongOrNull("timestamp") ?: continue,
|
||||
conversationId = it.getStringOrNull("conversation_id") ?: continue,
|
||||
conversationTitle = it.getStringOrNull("conversation_title"),
|
||||
isGroup = it.getIntOrNull("is_group") == 1,
|
||||
username = it.getStringOrNull("username") ?: continue,
|
||||
userId = it.getStringOrNull("user_id") ?: continue,
|
||||
eventType = it.getStringOrNull("event_type") ?: continue,
|
||||
data = it.getStringOrNull("data") ?: continue
|
||||
)
|
||||
if (filter != null && !filter(log)) continue
|
||||
logs.add(log)
|
||||
}
|
||||
logs
|
||||
}
|
||||
}
|
||||
|
||||
fun findConversation(search: String): List<String> {
|
||||
return database.rawQuery("SELECT DISTINCT conversation_id FROM tracker_events WHERE is_group = 1 AND conversation_id LIKE ?", arrayOf("%$search%")).use {
|
||||
val conversations = mutableListOf<String>()
|
||||
while (it.moveToNext()) {
|
||||
conversations.add(it.getString(0))
|
||||
}
|
||||
conversations
|
||||
}
|
||||
}
|
||||
|
||||
fun findUsername(search: String): List<String> {
|
||||
return database.rawQuery("SELECT DISTINCT username FROM tracker_events WHERE username LIKE ?", arrayOf("%$search%")).use {
|
||||
val usernames = mutableListOf<String>()
|
||||
while (it.moveToNext()) {
|
||||
usernames.add(it.getString(0))
|
||||
}
|
||||
usernames
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getStories(userId: String, from: Long, limit: Int = Int.MAX_VALUE): Map<Long, StoryData> {
|
||||
val stories = sortedMapOf<Long, StoryData>()
|
||||
database.rawQuery("SELECT * FROM stories WHERE user_id = ? AND posted_timestamp < ? ORDER BY posted_timestamp DESC LIMIT $limit", arrayOf(userId, from.toString())).use {
|
||||
@@ -1,5 +1,8 @@
|
||||
package me.rhunk.snapenhance.common.data
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
|
||||
data class FriendPresenceState(
|
||||
val bitmojiPresent: Boolean,
|
||||
@@ -40,3 +43,94 @@ enum class SessionEventType(
|
||||
SNAP_SCREENSHOT("snap_screenshot"),
|
||||
SNAP_SCREEN_RECORD("snap_screen_record"),
|
||||
}
|
||||
|
||||
object TrackerFlags {
|
||||
const val TRACK = 1
|
||||
const val LOG = 2
|
||||
const val NOTIFY = 4
|
||||
const val APP_IS_ACTIVE = 8
|
||||
const val APP_IS_INACTIVE = 16
|
||||
const val IS_IN_CONVERSATION = 32
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class TrackerEventsResult(
|
||||
private val rules: Map<TrackerRule, List<TrackerRuleEvent>>
|
||||
): Parcelable {
|
||||
fun hasFlags(vararg flags: Int): Boolean {
|
||||
return rules.any { (_, ruleEvents) ->
|
||||
ruleEvents.any { flags.all { flag -> it.flags and flag != 0 } }
|
||||
}
|
||||
}
|
||||
|
||||
fun canTrackOn(conversationId: String?, userId: String?): Boolean {
|
||||
return rules.any t@{ (rule, ruleEvents) ->
|
||||
ruleEvents.any { event ->
|
||||
if (event.flags and TrackerFlags.TRACK == 0) {
|
||||
return@any false
|
||||
}
|
||||
|
||||
// global rule
|
||||
if (rule.conversationId == null && rule.userId == null) {
|
||||
return@any true
|
||||
}
|
||||
|
||||
// user rule
|
||||
if (rule.conversationId == null && rule.userId == userId) {
|
||||
return@any true
|
||||
}
|
||||
|
||||
// conversation rule
|
||||
if (rule.conversationId == conversationId && rule.userId == null) {
|
||||
return@any true
|
||||
}
|
||||
|
||||
// conversation and user rule
|
||||
return@any rule.conversationId == conversationId && rule.userId == userId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Parcelize
|
||||
data class TrackerRule(
|
||||
val id: Int,
|
||||
val flags: Int,
|
||||
val conversationId: String?,
|
||||
val userId: String?
|
||||
): Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class TrackerRuleEvent(
|
||||
val id: Int,
|
||||
val flags: Int,
|
||||
val eventType: String,
|
||||
): Parcelable
|
||||
|
||||
enum class TrackerEventType(
|
||||
val key: String
|
||||
) {
|
||||
// pcs events
|
||||
CONVERSATION_ENTER("conversation_enter"),
|
||||
CONVERSATION_EXIT("conversation_exit"),
|
||||
STARTED_TYPING("started_typing"),
|
||||
STOPPED_TYPING("stopped_typing"),
|
||||
STARTED_SPEAKING("started_speaking"),
|
||||
STOPPED_SPEAKING("stopped_speaking"),
|
||||
STARTED_PEEKING("started_peeking"),
|
||||
STOPPED_PEEKING("stopped_peeking"),
|
||||
|
||||
// mcs events
|
||||
MESSAGE_READ("message_read"),
|
||||
MESSAGE_DELETED("message_deleted"),
|
||||
MESSAGE_SAVED("message_saved"),
|
||||
MESSAGE_UNSAVED("message_unsaved"),
|
||||
MESSAGE_REACTION_ADD("message_reaction_add"),
|
||||
MESSAGE_REACTION_REMOVE("message_reaction_remove"),
|
||||
SNAP_OPENED("snap_opened"),
|
||||
SNAP_REPLAYED("snap_replayed"),
|
||||
SNAP_REPLAYED_TWICE("snap_replayed_twice"),
|
||||
SNAP_SCREENSHOT("snap_screenshot"),
|
||||
SNAP_SCREEN_RECORD("snap_screen_record"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user