feat: add message processing
This commit is contained in:
parent
9dd8eef781
commit
56e9a42086
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import network.loki.messenger.libsession_util.Config
|
||||
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN
|
||||
|
@ -19,6 +20,7 @@ import network.loki.messenger.libsession_util.util.Conversation
|
|||
import network.loki.messenger.libsession_util.util.ExpiryMode
|
||||
import network.loki.messenger.libsession_util.util.GroupDisplayInfo
|
||||
import network.loki.messenger.libsession_util.util.GroupInfo
|
||||
import network.loki.messenger.libsession_util.util.KeyPair
|
||||
import network.loki.messenger.libsession_util.util.UserPic
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import org.session.libsession.avatars.AvatarHelper
|
||||
|
@ -84,6 +86,7 @@ import org.session.libsignal.messages.SignalServiceAttachmentPointer
|
|||
import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteResponseMessage
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMemberChangeMessage
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
|
@ -1351,6 +1354,20 @@ open class Storage(
|
|||
val job = InviteContactsJob(groupSessionId, filteredMembers.toTypedArray())
|
||||
JobQueue.shared.add(job)
|
||||
|
||||
val timestamp = SnodeAPI.nowWithOffset
|
||||
val messageToSign = "MEMBER_CHANGE${GroupUpdateMemberChangeMessage.Type.ADDED.name}$timestamp"
|
||||
val signature = SodiumUtilities.sign(messageToSign.toByteArray(), adminKey)
|
||||
val updatedMessage = GroupUpdated(
|
||||
DataMessage.GroupUpdateMessage.newBuilder()
|
||||
.setMemberChangeMessage(
|
||||
GroupUpdateMemberChangeMessage.newBuilder()
|
||||
.addAllMemberSessionIds(filteredMembers)
|
||||
.setType(GroupUpdateMemberChangeMessage.Type.ADDED)
|
||||
.setAdminSignature(ByteString.copyFrom(signature))
|
||||
)
|
||||
.build()
|
||||
)
|
||||
MessageSender.send(updatedMessage, fromSerialized(groupSessionId))
|
||||
infoConfig.free()
|
||||
membersConfig.free()
|
||||
keysConfig.free()
|
||||
|
@ -1368,13 +1385,100 @@ open class Storage(
|
|||
override fun insertGroupInfoChange(message: GroupUpdated, closedGroup: SessionId) {
|
||||
val sentTimestamp = message.sentTimestamp ?: return
|
||||
val senderPublicKey = message.sender ?: return
|
||||
val userPublicKey = getUserPublicKey()!!
|
||||
val updateData = UpdateMessageData.buildGroupUpdate(message)?.toJSON() ?: return
|
||||
|
||||
if (senderPublicKey == userPublicKey) {
|
||||
val recipient = Recipient.from(context, fromSerialized(closedGroup.hexString()), false)
|
||||
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, closedGroup.hexString(), null, sentTimestamp, 0, true, null, listOf(), listOf())
|
||||
val mmsDB = DatabaseComponent.get(context).mmsDatabase()
|
||||
val mmsSmsDB = DatabaseComponent.get(context).mmsSmsDatabase()
|
||||
if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return
|
||||
val threadDb = DatabaseComponent.get(context).threadDatabase()
|
||||
val threadID = threadDb.getThreadIdIfExistsFor(recipient)
|
||||
val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null, runThreadUpdate = true)
|
||||
mmsDB.markAsSent(infoMessageID, true)
|
||||
} else {
|
||||
val group = SignalServiceGroup(Hex.fromStringCondensed(closedGroup.hexString()), SignalServiceGroup.GroupType.SIGNAL)
|
||||
val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true, false)
|
||||
val updateData = UpdateMessageData.buildGroupUpdate(message)?.toJSON() ?: return
|
||||
val infoMessage = IncomingGroupMessage(m, updateData, true)
|
||||
val smsDB = DatabaseComponent.get(context).smsDatabase()
|
||||
smsDB.insertMessageInbox(infoMessage, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun promoteMember(groupSessionId: String, promotions: Array<String>) {
|
||||
val closedGroupId = SessionId.from(groupSessionId)
|
||||
val adminKey = configFactory.userGroups?.getClosedGroup(groupSessionId)?.adminKey ?: return
|
||||
if (adminKey.isEmpty()) {
|
||||
return Log.e("ClosedGroup", "No admin key for group")
|
||||
}
|
||||
val info = configFactory.getGroupInfoConfig(closedGroupId) ?: return
|
||||
val members = configFactory.getGroupMemberConfig(closedGroupId) ?: return
|
||||
val keys = configFactory.getGroupKeysConfig(closedGroupId, info, members, free = false) ?: return
|
||||
|
||||
promotions.forEach { sessionId ->
|
||||
val promoted = members.get(sessionId)?.copy(
|
||||
promotionPending = true,
|
||||
) ?: return@forEach
|
||||
members.set(promoted)
|
||||
|
||||
val message = GroupUpdated(
|
||||
DataMessage.GroupUpdateMessage.newBuilder()
|
||||
.setPromoteMessage(
|
||||
DataMessage.GroupUpdatePromoteMessage.newBuilder()
|
||||
.setGroupIdentitySeed(ByteString.copyFrom(adminKey))
|
||||
)
|
||||
.build()
|
||||
)
|
||||
MessageSender.send(message, fromSerialized(sessionId))
|
||||
}
|
||||
configFactory.saveGroupConfigs(keys, info, members)
|
||||
info.free()
|
||||
members.free()
|
||||
keys.free()
|
||||
val groupDestination = Destination.ClosedGroup(groupSessionId)
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(groupDestination)
|
||||
val timestamp = SnodeAPI.nowWithOffset
|
||||
val messageToSign = "MEMBER_CHANGE${GroupUpdateMemberChangeMessage.Type.PROMOTED.name}$timestamp"
|
||||
val signature = SodiumUtilities.sign(messageToSign.toByteArray(), adminKey)
|
||||
val message = GroupUpdated(
|
||||
DataMessage.GroupUpdateMessage.newBuilder()
|
||||
.setMemberChangeMessage(
|
||||
GroupUpdateMemberChangeMessage.newBuilder()
|
||||
.addAllMemberSessionIds(promotions.toList())
|
||||
.setType(GroupUpdateMemberChangeMessage.Type.PROMOTED)
|
||||
.setAdminSignature(ByteString.copyFrom(signature))
|
||||
)
|
||||
.build()
|
||||
).apply { sentTimestamp = timestamp }
|
||||
MessageSender.send(message, fromSerialized(groupSessionId))
|
||||
}
|
||||
|
||||
override fun handlePromoted(keyPair: KeyPair) {
|
||||
val closedGroupId = SessionId(IdPrefix.GROUP, keyPair.pubKey)
|
||||
val ourSessionId = getUserPublicKey()!!
|
||||
val userGroups = configFactory.userGroups ?: return
|
||||
val closedGroup = userGroups.getClosedGroup(closedGroupId.hexString())
|
||||
?: return Log.w("ClosedGroup", "No closed group in user groups matching promoted message")
|
||||
|
||||
val modified = closedGroup.copy(adminKey = keyPair.secretKey, authData = byteArrayOf())
|
||||
userGroups.set(modified)
|
||||
val info = configFactory.getGroupInfoConfig(closedGroupId) ?: return
|
||||
val members = configFactory.getGroupMemberConfig(closedGroupId) ?: return
|
||||
val keys = configFactory.getGroupKeysConfig(closedGroupId, info, members, free = false) ?: return
|
||||
val ourMember = members.get(ourSessionId)?.copy(
|
||||
admin = true,
|
||||
promotionPending = false,
|
||||
promotionFailed = false
|
||||
) ?: return Log.e("ClosedGroup", "We aren't a member in the closed group")
|
||||
members.set(ourMember)
|
||||
configFactory.saveGroupConfigs(keys, info, members)
|
||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(Destination.ClosedGroup(closedGroupId.hexString()))
|
||||
info.free()
|
||||
members.free()
|
||||
keys.free()
|
||||
}
|
||||
|
||||
override fun setServerCapabilities(server: String, capabilities: List<String>) {
|
||||
return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
|
||||
|
|
|
@ -94,6 +94,9 @@ fun EditClosedGroupScreen(
|
|||
onReinvite = { contact ->
|
||||
eventSink(EditGroupEvent.ReInviteContact(contact))
|
||||
},
|
||||
onPromote = { contact ->
|
||||
eventSink(EditGroupEvent.PromoteContact(contact))
|
||||
},
|
||||
viewState = viewState
|
||||
)
|
||||
}
|
||||
|
@ -182,8 +185,13 @@ class EditGroupViewModel @AssistedInject constructor(
|
|||
).show()
|
||||
}
|
||||
is EditGroupEvent.ReInviteContact -> {
|
||||
// do a buffer
|
||||
JobQueue.shared.add(InviteContactsJob(groupSessionId, arrayOf(event.contactSessionId)))
|
||||
}
|
||||
is EditGroupEvent.PromoteContact -> {
|
||||
// do a buffer
|
||||
storage.promoteMember(groupSessionId, arrayOf(event.contactSessionId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +246,7 @@ fun EditGroupView(
|
|||
onBack: ()->Unit,
|
||||
onInvite: ()->Unit,
|
||||
onReinvite: (String)->Unit,
|
||||
onPromote: (String)->Unit,
|
||||
viewState: EditGroupViewState,
|
||||
) {
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
|
@ -339,6 +348,27 @@ fun EditGroupView(
|
|||
color = MaterialTheme.colors.onPrimary
|
||||
)
|
||||
}
|
||||
} else if (viewState.admin && member.memberState == MemberState.Member) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onPromote(member.memberSessionId)
|
||||
},
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
Color(
|
||||
MaterialColors.getColor(LocalContext.current,
|
||||
R.attr.colorControlHighlight,
|
||||
MaterialTheme.colors.onPrimary.toArgb())
|
||||
)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
"Promote",
|
||||
color = MaterialTheme.colors.onPrimary
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,6 +424,7 @@ sealed class EditGroupEvent {
|
|||
data class InviteContacts(val context: Context,
|
||||
val contacts: ContactList): EditGroupEvent()
|
||||
data class ReInviteContact(val contactSessionId: String): EditGroupEvent()
|
||||
data class PromoteContact(val contactSessionId: String): EditGroupEvent()
|
||||
}
|
||||
|
||||
data class EditGroupInviteViewState(
|
||||
|
@ -430,6 +461,7 @@ fun PreviewList() {
|
|||
onBack = {},
|
||||
onInvite = {},
|
||||
onReinvite = {},
|
||||
onPromote = {},
|
||||
viewState = viewState
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.net.Uri
|
|||
import network.loki.messenger.libsession_util.Config
|
||||
import network.loki.messenger.libsession_util.util.GroupDisplayInfo
|
||||
import network.loki.messenger.libsession_util.util.GroupInfo
|
||||
import network.loki.messenger.libsession_util.util.KeyPair
|
||||
import org.session.libsession.messaging.BlindedIdMapping
|
||||
import org.session.libsession.messaging.calls.CallMessageType
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
|
@ -168,6 +169,8 @@ interface StorageProtocol {
|
|||
fun getClosedGroupDisplayInfo(groupSessionId: String): GroupDisplayInfo?
|
||||
fun inviteClosedGroupMembers(groupSessionId: String, invitees: List<String>)
|
||||
fun insertGroupInfoChange(message: GroupUpdated, closedGroup: SessionId)
|
||||
fun promoteMember(groupSessionId: String, promotions: Array<String>)
|
||||
fun handlePromoted(keyPair: KeyPair)
|
||||
|
||||
// Groups
|
||||
fun getAllGroups(includeInactive: Boolean): List<GroupRecord>
|
||||
|
@ -175,7 +178,6 @@ interface StorageProtocol {
|
|||
// Settings
|
||||
fun setProfileSharing(address: Address, value: Boolean)
|
||||
|
||||
|
||||
// Thread
|
||||
fun getOrCreateThreadIdFor(address: Address): Long
|
||||
fun getThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?, createThread: Boolean): Long?
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.session.libsession.messaging.sending_receiving
|
|||
|
||||
import android.text.TextUtils
|
||||
import network.loki.messenger.libsession_util.ConfigBase
|
||||
import network.loki.messenger.libsession_util.util.Sodium
|
||||
import org.session.libsession.avatars.AvatarHelper
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
||||
|
@ -572,10 +573,14 @@ private fun handleGroupInfoChange(message: GroupUpdated, closedGroup: SessionId)
|
|||
}
|
||||
|
||||
private fun handlePromotionMessage(message: GroupUpdated) {
|
||||
val sender = message.sender!!
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val inner = message.inner
|
||||
// TODO: set ourselves as admin and overwrite the auth data for group
|
||||
val promotion = message.inner.promoteMessage
|
||||
if (!promotion.hasGroupIdentitySeed()) {
|
||||
Log.e("GroupUpdated", "")
|
||||
}
|
||||
val seed = promotion.groupIdentitySeed.toByteArray()
|
||||
val keyPair = Sodium.ed25519KeyPair(seed)
|
||||
storage.handlePromoted(keyPair)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleInviteResponse(message: GroupUpdated, closedGroup: SessionId) {
|
||||
|
|
|
@ -27,7 +27,7 @@ object UpdateMessageBuilder {
|
|||
else getSenderName(senderId!!)
|
||||
|
||||
return when (updateData) {
|
||||
is UpdateMessageData.Kind.GroupCreation -> if (isOutgoing) {
|
||||
UpdateMessageData.Kind.GroupCreation -> if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_you_created_a_new_group)
|
||||
} else {
|
||||
context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName)
|
||||
|
@ -64,12 +64,70 @@ object UpdateMessageBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.GroupMemberLeft -> if (isOutgoing) {
|
||||
UpdateMessageData.Kind.GroupMemberLeft -> if (isOutgoing) {
|
||||
context.getString(R.string.MessageRecord_left_group)
|
||||
} else {
|
||||
context.getString(R.string.ConversationItem_group_action_left, senderName)
|
||||
}
|
||||
else -> return ""
|
||||
UpdateMessageData.Kind.GroupAvatarUpdated -> context.getString(R.string.ConversationItem_group_action_avatar_updated)
|
||||
is UpdateMessageData.Kind.GroupExpirationUpdated -> TODO()
|
||||
is UpdateMessageData.Kind.GroupMemberUpdated -> {
|
||||
when (updateData.type) {
|
||||
UpdateMessageData.MemberUpdateType.ADDED -> {
|
||||
val number = updateData.sessionIds.size
|
||||
if (number == 1) context.getString(
|
||||
R.string.ConversationItem_group_member_added_single,
|
||||
getSenderName(updateData.sessionIds.first())
|
||||
)
|
||||
else if (number == 2) context.getString(
|
||||
R.string.ConversationItem_group_member_added_two,
|
||||
getSenderName(updateData.sessionIds.first()),
|
||||
getSenderName(updateData.sessionIds.last())
|
||||
)
|
||||
else context.getString(
|
||||
R.string.ConversationItem_group_member_added_multiple,
|
||||
getSenderName(updateData.sessionIds.first()),
|
||||
updateData.sessionIds.size - 1
|
||||
)
|
||||
}
|
||||
UpdateMessageData.MemberUpdateType.PROMOTED -> {
|
||||
val number = updateData.sessionIds.size
|
||||
if (number == 1) context.getString(
|
||||
R.string.ConversationItem_group_member_promoted_single,
|
||||
getSenderName(updateData.sessionIds.first())
|
||||
)
|
||||
else if (number == 2) context.getString(
|
||||
R.string.ConversationItem_group_member_promoted_two,
|
||||
getSenderName(updateData.sessionIds.first()),
|
||||
getSenderName(updateData.sessionIds.last())
|
||||
)
|
||||
else context.getString(
|
||||
R.string.ConversationItem_group_member_promoted_multiple,
|
||||
getSenderName(updateData.sessionIds.first()),
|
||||
updateData.sessionIds.size - 1
|
||||
)
|
||||
}
|
||||
UpdateMessageData.MemberUpdateType.REMOVED -> {
|
||||
val number = updateData.sessionIds.size
|
||||
if (number == 1) context.getString(
|
||||
R.string.ConversationItem_group_member_removed_single,
|
||||
getSenderName(updateData.sessionIds.first())
|
||||
)
|
||||
else if (number == 2) context.getString(
|
||||
R.string.ConversationItem_group_member_removed_two,
|
||||
getSenderName(updateData.sessionIds.first()),
|
||||
getSenderName(updateData.sessionIds.last())
|
||||
)
|
||||
else context.getString(
|
||||
R.string.ConversationItem_group_member_removed_multiple,
|
||||
getSenderName(updateData.sessionIds.first()),
|
||||
updateData.sessionIds.size - 1
|
||||
)
|
||||
}
|
||||
null -> ""
|
||||
}
|
||||
}
|
||||
is UpdateMessageData.Kind.OpenGroupInvitation -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ class UpdateMessageData () {
|
|||
@JsonSubTypes(
|
||||
JsonSubTypes.Type(Kind.GroupCreation::class, name = "GroupCreation"),
|
||||
JsonSubTypes.Type(Kind.GroupNameChange::class, name = "GroupNameChange"),
|
||||
JsonSubTypes.Type(Kind.GroupDescriptionChange::class, name = "GroupDescriptionChange"),
|
||||
JsonSubTypes.Type(Kind.GroupMemberAdded::class, name = "GroupMemberAdded"),
|
||||
JsonSubTypes.Type(Kind.GroupMemberRemoved::class, name = "GroupMemberRemoved"),
|
||||
JsonSubTypes.Type(Kind.GroupMemberLeft::class, name = "GroupMemberLeft"),
|
||||
|
@ -35,7 +34,6 @@ class UpdateMessageData () {
|
|||
class GroupNameChange(val name: String): Kind() {
|
||||
constructor(): this("") //default constructor required for json serialization
|
||||
}
|
||||
data class GroupDescriptionChange @JvmOverloads constructor(val description: String = ""): Kind()
|
||||
class GroupMemberAdded(val updatedMembers: Collection<String>): Kind() {
|
||||
constructor(): this(Collections.emptyList())
|
||||
}
|
||||
|
@ -53,6 +51,12 @@ class UpdateMessageData () {
|
|||
}
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
|
||||
@JsonSubTypes(
|
||||
JsonSubTypes.Type(MemberUpdateType.ADDED::class, name = "ADDED"),
|
||||
JsonSubTypes.Type(MemberUpdateType.REMOVED::class, name = "REMOVED"),
|
||||
JsonSubTypes.Type(MemberUpdateType.PROMOTED::class, name = "PROMOTED"),
|
||||
)
|
||||
sealed class MemberUpdateType {
|
||||
data object ADDED: MemberUpdateType()
|
||||
data object REMOVED: MemberUpdateType()
|
||||
|
|
|
@ -61,6 +61,19 @@
|
|||
<string name="expiration_weeks_abbreviated">%dw</string>
|
||||
|
||||
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
|
||||
<string name="ConversationItem_group_action_avatar_updated">Group display picture updated.</string>
|
||||
<string name="ConversationItem_group_name_updated">Group name is now %1$s.</string>
|
||||
<string name="ConversationItem_group_name_updated_fallback">Group name updated.</string>
|
||||
<string name="ConversationItem_group_member_added_single">%1$s was invited to join the group.</string>
|
||||
<string name="ConversationItem_group_member_added_two">%1$s and %2$s were invited to join the group.</string>
|
||||
<string name="ConversationItem_group_member_added_multiple">%1$s and %2$d others were invited to join the group.</string>
|
||||
<string name="ConversationItem_group_member_removed_single">%1$s was removed from the group.</string>
|
||||
<string name="ConversationItem_group_member_removed_two">%1$s and %2$s were removed from the group.</string>
|
||||
<string name="ConversationItem_group_member_removed_multiple">%1$s and %2$d others were removed from the group.</string>
|
||||
<string name="ConversationItem_group_member_promoted_single">%1$s was promoted to Admin.</string>
|
||||
<string name="ConversationItem_group_member_promoted_two">%1$s and %2$s were promoted to Admin.</string>
|
||||
<string name="ConversationItem_group_member_promoted_multiple">%1$s and %2$d others were promoted to Admin.</string>
|
||||
|
||||
<!-- RecipientProvider -->
|
||||
<string name="RecipientProvider_unnamed_group">Unnamed group</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue