mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-14 15:27:37 +00:00
Add Session Id blinding (#862)
* feat: Add Session Id blinding
Including modified version of lazysodium-android to expose missing libsodium functions, we could build from a fork which we still need to setup.
* Add v4 onion request handling
* Update SOGS signature construction
* Fix SOGS signature construction
* Update onion request
* Update signature data
* Keep path prefixes for v4 endpoints
* Update SOGS signature message
* Rename to remove api version suffix
* Update onion response parsing
* Refactor file download paths
* Implement request batching
* Refactor batch response handling
* Handle batch endpoint responses
* Update batch endpoint responses
* Update attachment download handling
* Handle file downloads
* Handle inbox messages
* Fix issue with file downloads
* Preserve image bytearray encoding
* Refactor
* Open group message requests
* Check id blinding in user detail bottom sheet rather
* Message validation refactor
* Cache last inbox/outbox server ids
* Update message encryption/decryption
* Refactor
* Refactor
* Bypass user details bottom sheet in open groups for blinded session ids
* Fix capabilities call auth
* Refactor
* Revert default server details
* Update sodium dependency to forked repo
* Fix attachment upload
* Revert "Update sodium dependency to forked repo"
This reverts commit c7db9529f9
.
* Add signed sodium lib
* Update contact id truncation and mention logic
* Open group inbox messaging fix
* Refactor
* Update blinded id check
* Fix open group message sends
* Fix crash on open group direct message send
* Direct message refactor
* Direct message encrypt/decrypt fixes
* Use updated curve25519 version
* Updated lazysodium dependency
* Update encryption/decryption calls
* Handle direct message parse errors
* Minor refactor
* Existing chat refactor
* Update encryption & decryption parameters
* Fix authenticated ciphertext size
* Set direct message sync target
* Update direct message thread lookup
* Add blinded id mapping table
* Add blinded id mapping table
* Update threads after sends
* Update open group message timestamp handling
* Filter unblinded contacts
* Format blinded id mentions
* Add message deleted field
* Hide open group inbox id
* Update message request response handling
* Update message request response sender handling
* Fix mentions of blinded ids
* Handle open group poll failure
* fix: add log for failed open group onion request, add decoding body for blinding required error at destination
* fix: change the error check
* Persist group members
* Reschedule polling after capabilities update
* Retry on other exceptions
* Minor refactor
* Open group profile fix
* Group member db schema update
* Fix ban request key
* Update ban response type
* Ban endpoint updates
* Ban endpoint updates
* Delete messages
Co-authored-by: charles <charles@oxen.io>
Co-authored-by: jubb <hjubb@users.noreply.github.com>
This commit is contained in:
@@ -104,8 +104,8 @@ dependencies {
|
||||
implementation project(":libsignal")
|
||||
implementation project(":libsession")
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxJsonVersion"
|
||||
implementation "org.whispersystems:curve25519-java:$curve25519Version"
|
||||
implementation 'com.goterl:lazysodium-android:5.0.2@aar'
|
||||
implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version"
|
||||
implementation project(":liblazysodium")
|
||||
implementation "net.java.dev.jna:jna:5.8.0@aar"
|
||||
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
|
||||
|
@@ -0,0 +1,166 @@
|
||||
package network.loki.messenger
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.goterl.lazysodium.utils.Key
|
||||
import com.goterl.lazysodium.utils.KeyPair
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SodiumUtilitiesTest {
|
||||
|
||||
private val publicKey: String = "88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"
|
||||
private val privateKey: String = "30d796c1ddb4dc455fd998a98aa275c247494a9a7bde9c1fee86ae45cd585241"
|
||||
private val edKeySeed: String = "c010d89eccbaf5d1c6d19df766c6eedf965d4a28a56f87c9fc819edb59896dd9"
|
||||
private val edPublicKey: String = "bac6e71efd7dfa4a83c98ed24f254ab2c267f9ccdb172a5280a0444ad24e89cc"
|
||||
private val edSecretKey: String = "c010d89eccbaf5d1c6d19df766c6eedf965d4a28a56f87c9fc819edb59896dd9bac6e71efd7dfa4a83c98ed24f254ab2c267f9ccdb172a5280a0444ad24e89cc"
|
||||
private val blindedPublicKey: String = "98932d4bccbe595a8789d7eb1629cefc483a0eaddc7e20e8fe5c771efafd9af5"
|
||||
private val serverPublicKey: String = "c3b3c6f32f0ab5a57f853cc4f30f5da7fda5624b0c77b3fb0829de562ada081d"
|
||||
|
||||
private val edKeyPair = KeyPair(Key.fromHexString(edPublicKey), Key.fromHexString(edSecretKey))
|
||||
|
||||
@Test
|
||||
fun generateBlindingFactorSuccess() {
|
||||
val result = SodiumUtilities.generateBlindingFactor(serverPublicKey)
|
||||
|
||||
assertThat(result?.toHexString(), equalTo("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateBlindingFactorFailure() {
|
||||
val result = SodiumUtilities.generateBlindingFactor("Test")
|
||||
|
||||
assertNull(result?.toHexString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blindedKeyPairSuccess() {
|
||||
val result = SodiumUtilities.blindedKeyPair(serverPublicKey, edKeyPair)!!
|
||||
|
||||
assertThat(result.publicKey.asHexString.lowercase(), equalTo(blindedPublicKey))
|
||||
assertThat(result.secretKey.asHexString.take(64).lowercase(), equalTo("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blindedKeyPairFailurePublicKeyLength() {
|
||||
val result = SodiumUtilities.blindedKeyPair(
|
||||
serverPublicKey,
|
||||
KeyPair(Key.fromHexString(edPublicKey.take(4)), Key.fromHexString(edKeySeed))
|
||||
)
|
||||
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blindedKeyPairFailureSecretKeyLength() {
|
||||
val result = SodiumUtilities.blindedKeyPair(
|
||||
serverPublicKey,
|
||||
KeyPair(Key.fromHexString(edPublicKey), Key.fromHexString(edSecretKey.take(4)))
|
||||
)
|
||||
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blindedKeyPairFailureBlindingFactor() {
|
||||
val result = SodiumUtilities.blindedKeyPair("Test", edKeyPair)
|
||||
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sogsSignature() {
|
||||
val expectedSignature = "dcc086abdd2a740d9260b008fb37e12aa0ff47bd2bd9e177bbbec37fd46705a9072ce747bda66c788c3775cdd7ad60ad15a478e0886779aad5d795fd7bf8350d"
|
||||
|
||||
val result = SodiumUtilities.sogsSignature(
|
||||
"TestMessage".toByteArray(),
|
||||
Hex.fromStringCondensed(edSecretKey),
|
||||
Hex.fromStringCondensed("44d82cc15c0a5056825cae7520b6b52d000a23eb0c5ed94c4be2d9dc41d2d409"),
|
||||
Hex.fromStringCondensed("0bb7815abb6ba5142865895f3e5286c0527ba4d31dbb75c53ce95e91ffe025a2")
|
||||
)
|
||||
|
||||
assertThat(result?.toHexString(), equalTo(expectedSignature))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun combineKeysSuccess() {
|
||||
val result = SodiumUtilities.combineKeys(
|
||||
Hex.fromStringCondensed(edSecretKey),
|
||||
Hex.fromStringCondensed(edPublicKey)
|
||||
)
|
||||
|
||||
assertThat(result?.toHexString(), equalTo("1159b5d0fcfba21228eb2121a0f59712fa8276fc6e5547ff519685a40b9819e6"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun combineKeysFailure() {
|
||||
val result = SodiumUtilities.combineKeys(
|
||||
SodiumUtilities.generatePrivateKeyScalar(Hex.fromStringCondensed(edSecretKey))!!,
|
||||
Hex.fromStringCondensed(publicKey)
|
||||
)
|
||||
|
||||
assertNull(result?.toHexString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedBlindedEncryptionKeySuccess() {
|
||||
val result = SodiumUtilities.sharedBlindedEncryptionKey(
|
||||
Hex.fromStringCondensed(edSecretKey),
|
||||
Hex.fromStringCondensed(blindedPublicKey),
|
||||
Hex.fromStringCondensed(publicKey),
|
||||
Hex.fromStringCondensed(blindedPublicKey)
|
||||
)
|
||||
|
||||
assertThat(result?.toHexString(), equalTo("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sharedBlindedEncryptionKeyFailure() {
|
||||
val result = SodiumUtilities.sharedBlindedEncryptionKey(
|
||||
Hex.fromStringCondensed(edSecretKey),
|
||||
Hex.fromStringCondensed(publicKey),
|
||||
Hex.fromStringCondensed(edPublicKey),
|
||||
Hex.fromStringCondensed(publicKey)
|
||||
)
|
||||
|
||||
assertNull(result?.toHexString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sessionIdSuccess() {
|
||||
val result = SodiumUtilities.sessionId("05$publicKey", "15$blindedPublicKey", serverPublicKey)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sessionIdFailureInvalidSessionId() {
|
||||
val result = SodiumUtilities.sessionId("AB$publicKey", "15$blindedPublicKey", serverPublicKey)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sessionIdFailureInvalidBlindedId() {
|
||||
val result = SodiumUtilities.sessionId("05$publicKey", "AB$blindedPublicKey", serverPublicKey)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sessionIdFailureBlindingFactor() {
|
||||
val result = SodiumUtilities.sessionId("05$publicKey", "15$blindedPublicKey", "Test")
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
}
|
@@ -15,6 +15,7 @@ import org.session.libsession.avatars.ProfileContactPhoto
|
||||
import org.session.libsession.avatars.ResourceContactPhoto
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
@@ -57,6 +58,11 @@ class ProfilePictureView @JvmOverloads constructor(
|
||||
val apk = members.getOrNull(1)?.serialize() ?: ""
|
||||
additionalPublicKey = apk
|
||||
additionalDisplayName = getUserDisplayName(apk)
|
||||
} else if(recipient.isOpenGroupInboxRecipient) {
|
||||
val publicKey = GroupUtil.getDecodedOpenGroupInbox(recipient.address.serialize())
|
||||
this.publicKey = publicKey
|
||||
displayName = getUserDisplayName(publicKey)
|
||||
additionalPublicKey = null
|
||||
} else {
|
||||
val publicKey = recipient.address.toString()
|
||||
this.publicKey = publicKey
|
||||
|
@@ -58,19 +58,22 @@ import org.session.libsession.messaging.messages.control.DataExtractionNotificat
|
||||
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
|
||||
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
import org.session.libsession.messaging.utilities.SessionId
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.MediaTypes
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.concurrent.SimpleTask
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.recipients.RecipientModifiedListener
|
||||
import org.session.libsignal.crypto.MnemonicCodec
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.session.libsignal.utilities.ListenableFuture
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.session.libsignal.utilities.hexEncodedPrivateKey
|
||||
@@ -109,6 +112,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase
|
||||
import org.thoughtcrime.securesms.database.SessionContactDatabase
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase
|
||||
import org.thoughtcrime.securesms.database.Storage
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
@@ -167,6 +171,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
@Inject lateinit var smsDb: SmsDatabase
|
||||
@Inject lateinit var mmsDb: MmsDatabase
|
||||
@Inject lateinit var lokiMessageDb: LokiMessageDatabase
|
||||
@Inject lateinit var storage: Storage
|
||||
@Inject lateinit var viewModelFactory: ConversationViewModel.AssistedFactory
|
||||
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
@@ -177,9 +182,25 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
private val viewModel: ConversationViewModel by viewModels {
|
||||
var threadId = intent.getLongExtra(THREAD_ID, -1L)
|
||||
if (threadId == -1L) {
|
||||
intent.getParcelableExtra<Address>(ADDRESS)?.let { address ->
|
||||
val recipient = Recipient.from(this, address, false)
|
||||
threadId = threadDb.getOrCreateThreadIdFor(recipient)
|
||||
intent.getParcelableExtra<Address>(ADDRESS)?.let { it ->
|
||||
threadId = threadDb.getThreadIdIfExistsFor(it.serialize())
|
||||
if (threadId == -1L) {
|
||||
val sessionId = SessionId(it.serialize())
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(intent.getLongExtra(FROM_GROUP_THREAD_ID, -1))
|
||||
val address = if (sessionId.prefix == IdPrefix.BLINDED && openGroup != null) {
|
||||
storage.getOrCreateBlindedIdMapping(sessionId.hexString, openGroup.server, openGroup.publicKey).sessionId?.let {
|
||||
fromSerialized(it)
|
||||
} ?: run {
|
||||
val openGroupInboxId =
|
||||
"${openGroup.server}!${openGroup.publicKey}!${sessionId.hexString}".toByteArray()
|
||||
fromSerialized(GroupUtil.getEncodedOpenGroupInboxID(openGroupInboxId))
|
||||
}
|
||||
} else {
|
||||
it
|
||||
}
|
||||
val recipient = Recipient.from(this, address, false)
|
||||
threadId = threadDb.getOrCreateThreadIdFor(recipient)
|
||||
}
|
||||
} ?: finish()
|
||||
}
|
||||
viewModelFactory.create(threadId)
|
||||
@@ -263,6 +284,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
// Extras
|
||||
const val THREAD_ID = "thread_id"
|
||||
const val ADDRESS = "address"
|
||||
const val FROM_GROUP_THREAD_ID = "from_group_thread_id"
|
||||
const val SCROLL_MESSAGE_ID = "scroll_message_id"
|
||||
const val SCROLL_MESSAGE_AUTHOR = "scroll_message_author"
|
||||
// Request codes
|
||||
@@ -508,7 +530,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
|
||||
private fun getLatestOpenGroupInfoIfNeeded() {
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(viewModel.threadId) ?: return
|
||||
OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
|
||||
OpenGroupApi.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
|
||||
}
|
||||
|
||||
// called from onCreate
|
||||
|
@@ -7,7 +7,7 @@ import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import network.loki.messenger.databinding.ViewMentionCandidateBinding
|
||||
import org.session.libsession.messaging.mentions.Mention
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
|
||||
class MentionCandidateView : LinearLayout {
|
||||
@@ -34,7 +34,7 @@ class MentionCandidateView : LinearLayout {
|
||||
profilePictureView.root.glide = glide!!
|
||||
profilePictureView.root.update()
|
||||
if (openGroupServer != null && openGroupRoom != null) {
|
||||
val isUserModerator = OpenGroupAPIV2.isUserModerator(mentionCandidate.publicKey, openGroupRoom!!, openGroupServer!!)
|
||||
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey)
|
||||
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||
} else {
|
||||
moderatorIconImageView.visibility = View.GONE
|
||||
|
@@ -7,7 +7,7 @@ import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
|
||||
import org.session.libsession.messaging.mentions.Mention
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
|
||||
class MentionCandidateView : RelativeLayout {
|
||||
@@ -34,7 +34,7 @@ class MentionCandidateView : RelativeLayout {
|
||||
profilePictureView.root.glide = glide!!
|
||||
profilePictureView.root.update()
|
||||
if (openGroupServer != null && openGroupRoom != null) {
|
||||
val isUserModerator = OpenGroupAPIV2.isUserModerator(candidate.publicKey, openGroupRoom!!, openGroupServer!!)
|
||||
val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", candidate.publicKey)
|
||||
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
|
||||
} else {
|
||||
moderatorIconImageView.visibility = View.GONE
|
||||
|
@@ -5,13 +5,17 @@ import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.utilities.SessionId
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapter
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
|
||||
class ConversationActionModeCallback(private val adapter: ConversationAdapter, private val threadID: Long,
|
||||
private val context: Context) : ActionMode.Callback {
|
||||
@@ -34,6 +38,9 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
||||
val openGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID)
|
||||
val thread = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(threadID)!!
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val edKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!
|
||||
val blindedPublicKey = openGroup?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, edKeyPair)?.publicKey?.asBytes }
|
||||
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
|
||||
fun userCanDeleteSelectedItems(): Boolean {
|
||||
val allSentByCurrentUser = selectedItems.all { it.isOutgoing }
|
||||
|
||||
@@ -41,13 +48,13 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
||||
if (!ConversationActivityV2.IS_UNSEND_REQUESTS_ENABLED) {
|
||||
if (openGroup == null) { return true }
|
||||
if (allSentByCurrentUser) { return true }
|
||||
return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
|
||||
return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey)
|
||||
}
|
||||
|
||||
val allReceivedByCurrentUser = selectedItems.all { !it.isOutgoing }
|
||||
if (openGroup == null) { return allSentByCurrentUser || allReceivedByCurrentUser }
|
||||
if (allSentByCurrentUser) { return true }
|
||||
return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
|
||||
return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey)
|
||||
}
|
||||
fun userCanBanSelectedUsers(): Boolean {
|
||||
if (openGroup == null) { return false }
|
||||
@@ -55,7 +62,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
||||
if (anySentByCurrentUser) { return false } // Users can't ban themselves
|
||||
val selectedUsers = selectedItems.map { it.recipient.address.toString() }.toSet()
|
||||
if (selectedUsers.size > 1) { return false }
|
||||
return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
|
||||
return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey)
|
||||
}
|
||||
// Delete message
|
||||
menu.findItem(R.id.menu_context_delete_message).isVisible = userCanDeleteSelectedItems()
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
@@ -23,16 +24,19 @@ import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewVisibleMessageBinding
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.messaging.contacts.Contact.ContactContext
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.database.LokiThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
@@ -140,15 +144,27 @@ class VisibleMessageView : LinearLayout {
|
||||
binding.profilePictureView.root.glide = glide
|
||||
binding.profilePictureView.root.update(message.individualRecipient)
|
||||
binding.profilePictureView.root.setOnClickListener {
|
||||
showUserDetails(senderSessionID, threadID)
|
||||
if (thread.isOpenGroupRecipient) {
|
||||
if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) {
|
||||
val intent = Intent(context, ConversationActivityV2::class.java)
|
||||
intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID)
|
||||
intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(senderSessionID))
|
||||
context.startActivity(intent)
|
||||
}
|
||||
} else {
|
||||
maybeShowUserDetails(senderSessionID, threadID)
|
||||
}
|
||||
}
|
||||
if (thread.isOpenGroupRecipient) {
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return
|
||||
val isModerator = OpenGroupAPIV2.isUserModerator(
|
||||
senderSessionID,
|
||||
openGroup.room,
|
||||
openGroup.server
|
||||
)
|
||||
var standardPublicKey = ""
|
||||
var blindedPublicKey: String? = null
|
||||
if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) {
|
||||
blindedPublicKey = senderSessionID
|
||||
} else {
|
||||
standardPublicKey = senderSessionID
|
||||
}
|
||||
val isModerator = OpenGroupManager.isUserModerator(context, openGroup.groupId, standardPublicKey, blindedPublicKey)
|
||||
binding.moderatorIconImageView.isVisible = !message.isOutgoing && isModerator
|
||||
}
|
||||
}
|
||||
@@ -403,7 +419,7 @@ class VisibleMessageView : LinearLayout {
|
||||
pressCallback = null
|
||||
}
|
||||
|
||||
private fun showUserDetails(publicKey: String, threadID: Long) {
|
||||
private fun maybeShowUserDetails(publicKey: String, threadID: Long) {
|
||||
val userDetailsBottomSheet = UserDetailsBottomSheet()
|
||||
val bundle = bundleOf(
|
||||
UserDetailsBottomSheet.ARGUMENT_PUBLIC_KEY to publicKey,
|
||||
|
@@ -11,6 +11,7 @@ import androidx.core.content.res.ResourcesCompat
|
||||
import network.loki.messenger.R
|
||||
import nl.komponents.kovenant.combine.Tuple2
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
@@ -20,39 +21,27 @@ object MentionUtilities {
|
||||
|
||||
@JvmStatic
|
||||
fun highlightMentions(text: CharSequence, threadID: Long, context: Context): String {
|
||||
val threadDB = DatabaseComponent.get(context).threadDatabase()
|
||||
val isOpenGroup = threadDB.getRecipientForThreadId(threadID)?.isOpenGroupRecipient ?: false
|
||||
return highlightMentions(text, false, isOpenGroup, context).toString() // isOutgoingMessage is irrelevant
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun highlightMentions(text:CharSequence, isOpenGroup: Boolean, context: Context): String {
|
||||
return highlightMentions(text, false, isOpenGroup, context).toString()
|
||||
return highlightMentions(text, false, threadID, context).toString() // isOutgoingMessage is irrelevant
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, threadID: Long, context: Context): SpannableString {
|
||||
val threadDB = DatabaseComponent.get(context).threadDatabase()
|
||||
val isOpenGroup = threadDB.getRecipientForThreadId(threadID)?.isOpenGroupRecipient ?: false
|
||||
return highlightMentions(text, isOutgoingMessage, isOpenGroup, context) // isOutgoingMessage is irrelevant
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, isOpenGroup: Boolean, context: Context): SpannableString {
|
||||
@Suppress("NAME_SHADOWING") var text = text
|
||||
val pattern = Pattern.compile("@[0-9a-fA-F]*")
|
||||
var matcher = pattern.matcher(text)
|
||||
val mentions = mutableListOf<Tuple2<Range<Int>, String>>()
|
||||
var startIndex = 0
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val openGroup = DatabaseComponent.get(context).storage().getOpenGroup(threadID)
|
||||
if (matcher.find(startIndex)) {
|
||||
while (true) {
|
||||
val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
||||
val userDisplayName: String? = if (publicKey.equals(userPublicKey, ignoreCase = true)) {
|
||||
TextSecurePreferences.getProfileName(context)
|
||||
val isUserBlindedPublicKey = openGroup?.let { SodiumUtilities.sessionId(userPublicKey, publicKey, it.publicKey) } ?: false
|
||||
val userDisplayName: String? = if (publicKey.equals(userPublicKey, ignoreCase = true) || isUserBlindedPublicKey) {
|
||||
context.getString(R.string.MessageRecord_you)
|
||||
} else {
|
||||
val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey)
|
||||
@Suppress("NAME_SHADOWING") val context = if (isOpenGroup) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR
|
||||
@Suppress("NAME_SHADOWING") val context = if (openGroup != null) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR
|
||||
contact?.displayName(context)
|
||||
}
|
||||
if (userDisplayName != null) {
|
||||
|
@@ -0,0 +1,88 @@
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import androidx.core.database.getStringOrNull
|
||||
import org.session.libsession.messaging.BlindedIdMapping
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
|
||||
class BlindedIdMappingDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
|
||||
|
||||
companion object {
|
||||
const val TABLE_NAME = "blinded_id_mapping"
|
||||
const val ROW_ID = "_id"
|
||||
const val BLINDED_PK = "blinded_pk"
|
||||
const val SESSION_PK = "session_pk"
|
||||
const val SERVER_URL = "server_url"
|
||||
const val SERVER_PK = "server_pk"
|
||||
|
||||
@JvmField
|
||||
val CREATE_BLINDED_ID_MAPPING_TABLE_COMMAND = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ROW_ID INTEGER PRIMARY KEY,
|
||||
$BLINDED_PK TEXT NOT NULL,
|
||||
$SESSION_PK TEXT DEFAULT NULL,
|
||||
$SERVER_URL TEXT NOT NULL,
|
||||
$SERVER_PK TEXT NOT NULL
|
||||
)
|
||||
""".trimIndent()
|
||||
|
||||
private fun readBlindedIdMapping(cursor: Cursor): BlindedIdMapping {
|
||||
return BlindedIdMapping(
|
||||
blindedId = cursor.getString(cursor.getColumnIndexOrThrow(BLINDED_PK)),
|
||||
sessionId = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(SESSION_PK)),
|
||||
serverUrl = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_URL)),
|
||||
serverId = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_PK)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlindedIdMapping(blindedId: String): List<BlindedIdMapping> {
|
||||
val query = "$BLINDED_PK = ?"
|
||||
val args = arrayOf(blindedId)
|
||||
|
||||
val mappings: MutableList<BlindedIdMapping> = mutableListOf()
|
||||
|
||||
readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
mappings += readBlindedIdMapping(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
return mappings
|
||||
}
|
||||
|
||||
fun addBlindedIdMapping(blindedIdMapping: BlindedIdMapping) {
|
||||
writableDatabase.beginTransaction()
|
||||
try {
|
||||
val values = ContentValues().apply {
|
||||
put(BLINDED_PK, blindedIdMapping.blindedId)
|
||||
put(SERVER_PK, blindedIdMapping.sessionId)
|
||||
put(SERVER_URL, blindedIdMapping.serverUrl)
|
||||
put(SERVER_PK, blindedIdMapping.serverId)
|
||||
}
|
||||
|
||||
writableDatabase.insert(TABLE_NAME, null, values)
|
||||
writableDatabase.setTransactionSuccessful()
|
||||
} finally {
|
||||
writableDatabase.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlindedIdMappingsExceptFor(server: String): List<BlindedIdMapping> {
|
||||
val query = "$SESSION_PK IS NOT NULL AND $SERVER_URL <> ?"
|
||||
val args = arrayOf(server)
|
||||
|
||||
val mappings: MutableList<BlindedIdMapping> = mutableListOf()
|
||||
|
||||
readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
mappings += readBlindedIdMapping(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
return mappings
|
||||
}
|
||||
|
||||
}
|
@@ -23,6 +23,8 @@ import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.session.libsession.utilities.WindowDebouncer;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
@@ -92,4 +94,12 @@ public abstract class Database {
|
||||
this.databaseHelper = databaseHelper;
|
||||
}
|
||||
|
||||
protected SQLiteDatabase getReadableDatabase() {
|
||||
return databaseHelper.getReadableDatabase();
|
||||
}
|
||||
|
||||
protected SQLiteDatabase getWritableDatabase() {
|
||||
return databaseHelper.getWritableDatabase();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,72 @@
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.session.libsession.messaging.open_groups.GroupMember
|
||||
import org.session.libsession.messaging.open_groups.GroupMemberRole
|
||||
|
||||
class GroupMemberDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
|
||||
|
||||
companion object {
|
||||
const val TABLE_NAME = "group_member"
|
||||
const val GROUP_ID = "group_id"
|
||||
const val PROFILE_ID = "profile_id"
|
||||
const val ROLE = "role"
|
||||
|
||||
private val allColumns = arrayOf(GROUP_ID, PROFILE_ID, ROLE)
|
||||
|
||||
@JvmField
|
||||
val CREATE_GROUP_MEMBER_TABLE_COMMAND = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$GROUP_ID TEXT NOT NULL,
|
||||
$PROFILE_ID TEXT NOT NULL,
|
||||
$ROLE TEXT NOT NULL,
|
||||
PRIMARY KEY ($GROUP_ID, $PROFILE_ID)
|
||||
)
|
||||
""".trimIndent()
|
||||
|
||||
private fun readGroupMember(cursor: Cursor): GroupMember {
|
||||
return GroupMember(
|
||||
groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)),
|
||||
profileId = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_ID)),
|
||||
role = GroupMemberRole.valueOf(cursor.getString(cursor.getColumnIndexOrThrow(ROLE))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getGroupMemberRoles(groupId: String, profileId: String): List<GroupMemberRole> {
|
||||
val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
|
||||
val args = arrayOf(groupId, profileId)
|
||||
|
||||
val mappings: MutableList<GroupMember> = mutableListOf()
|
||||
|
||||
readableDatabase.query(TABLE_NAME, allColumns, query, args, null, null, null).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
mappings += readGroupMember(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
return mappings.map { it.role }
|
||||
}
|
||||
|
||||
fun addGroupMember(member: GroupMember) {
|
||||
writableDatabase.beginTransaction()
|
||||
try {
|
||||
val values = ContentValues().apply {
|
||||
put(GROUP_ID, member.groupId)
|
||||
put(PROFILE_ID, member.profileId)
|
||||
put(ROLE, member.role.name)
|
||||
}
|
||||
val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
|
||||
val args = arrayOf(member.groupId, member.profileId)
|
||||
|
||||
writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args)
|
||||
writableDatabase.setTransactionSuccessful()
|
||||
} finally {
|
||||
writableDatabase.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -12,7 +12,7 @@ import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.PublicKeyValidation
|
||||
import org.session.libsignal.utilities.Snode
|
||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.removingIdPrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
@@ -127,6 +127,21 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
"""
|
||||
const val INSERT_RECEIVED_HASHES_DATA = "INSERT OR IGNORE INTO $receivedMessageHashValuesTable($publicKey, $receivedMessageHashValues) SELECT $publicKey, $receivedMessageHashValues FROM $legacyReceivedMessageHashValuesTable3;"
|
||||
const val DROP_LEGACY_RECEIVED_HASHES = "DROP TABLE $legacyReceivedMessageHashValuesTable3;"
|
||||
// Open group server capabilities
|
||||
private val serverCapabilitiesTable = "open_group_server_capabilities"
|
||||
private val capabilities = "capabilities"
|
||||
@JvmStatic
|
||||
val createServerCapabilitiesCommand = "CREATE TABLE $serverCapabilitiesTable($server STRING PRIMARY KEY, $capabilities STRING)"
|
||||
// Last inbox message server IDs
|
||||
private val lastInboxMessageServerIdTable = "open_group_last_inbox_message_server_id_cache"
|
||||
private val lastInboxMessageServerId = "last_inbox_message_server_id"
|
||||
@JvmStatic
|
||||
val createLastInboxMessageServerIdCommand = "CREATE TABLE $lastInboxMessageServerIdTable($server STRING PRIMARY KEY, $lastInboxMessageServerId INTEGER DEFAULT 0)"
|
||||
// Last outbox message server IDs
|
||||
private val lastOutboxMessageServerIdTable = "open_group_last_outbox_message_server_id_cache"
|
||||
private val lastOutboxMessageServerId = "last_outbox_message_server_id"
|
||||
@JvmStatic
|
||||
val createLastOutboxMessageServerIdCommand = "CREATE TABLE $lastOutboxMessageServerIdTable($server STRING PRIMARY KEY, $lastOutboxMessageServerId INTEGER DEFAULT 0)"
|
||||
|
||||
// region Deprecated
|
||||
private val deviceLinkCache = "loki_pairing_authorisation_cache"
|
||||
@@ -423,14 +438,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
|
||||
override fun getUserX25519KeyPair(): ECKeyPair {
|
||||
val keyPair = IdentityKeyUtil.getIdentityKeyPair(context)
|
||||
return ECKeyPair(DjbECPublicKey(keyPair.publicKey.serialize().removing05PrefixIfNeeded()), DjbECPrivateKey(keyPair.privateKey.serialize()))
|
||||
return ECKeyPair(DjbECPublicKey(keyPair.publicKey.serialize().removingIdPrefixIfNeeded()), DjbECPrivateKey(keyPair.privateKey.serialize()))
|
||||
}
|
||||
|
||||
fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val timestamp = Date().time.toString()
|
||||
val index = "$groupPublicKey-$timestamp"
|
||||
val encryptionKeyPairPublicKey = encryptionKeyPair.publicKey.serialize().toHexString().removing05PrefixIfNeeded()
|
||||
val encryptionKeyPairPublicKey = encryptionKeyPair.publicKey.serialize().toHexString().removingIdPrefixIfNeeded()
|
||||
val encryptionKeyPairPrivateKey = encryptionKeyPair.privateKey.serialize().toHexString()
|
||||
val row = wrap(mapOf(closedGroupsEncryptionKeyPairIndex to index, Companion.encryptionKeyPairPublicKey to encryptionKeyPairPublicKey,
|
||||
Companion.encryptionKeyPairPrivateKey to encryptionKeyPairPrivateKey ))
|
||||
@@ -481,6 +496,53 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
database.delete(closedGroupPublicKeysTable, "${Companion.groupPublicKey} = ?", wrap(groupPublicKey))
|
||||
}
|
||||
|
||||
fun setServerCapabilities(serverName: String, serverCapabilities: List<String>) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val row = wrap(mapOf(server to serverName, capabilities to serverCapabilities.joinToString(",")))
|
||||
database.insertOrUpdate(serverCapabilitiesTable, row, "$server = ?", wrap(serverName))
|
||||
}
|
||||
|
||||
fun getServerCapabilities(serverName: String): List<String> {
|
||||
val database = databaseHelper.writableDatabase
|
||||
return database.get(serverCapabilitiesTable, "$server = ?", wrap(serverName)) { cursor ->
|
||||
cursor.getString(capabilities)
|
||||
}?.split(",") ?: emptyList()
|
||||
}
|
||||
|
||||
fun setLastInboxMessageId(serverName: String, newValue: Long) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val row = wrap(mapOf(server to serverName, lastInboxMessageServerId to newValue.toString()))
|
||||
database.insertOrUpdate(lastInboxMessageServerIdTable, row, "$server = ?", wrap(serverName))
|
||||
}
|
||||
|
||||
fun getLastInboxMessageId(serverName: String): Long? {
|
||||
val database = databaseHelper.writableDatabase
|
||||
return database.get(lastInboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor ->
|
||||
cursor.getInt(lastInboxMessageServerId)
|
||||
}?.toLong()
|
||||
}
|
||||
|
||||
fun removeLastInboxMessageId(serverName: String) {
|
||||
databaseHelper.writableDatabase.delete(lastInboxMessageServerIdTable, "$server = ?", wrap(serverName))
|
||||
}
|
||||
|
||||
fun setLastOutboxMessageId(serverName: String, newValue: Long) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val row = wrap(mapOf(server to serverName, lastOutboxMessageServerId to newValue.toString()))
|
||||
database.insertOrUpdate(lastOutboxMessageServerIdTable, row, "$server = ?", wrap(serverName))
|
||||
}
|
||||
|
||||
fun getLastOutboxMessageId(serverName: String): Long? {
|
||||
val database = databaseHelper.writableDatabase
|
||||
return database.get(lastOutboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor ->
|
||||
cursor.getInt(lastOutboxMessageServerId)
|
||||
}?.toLong()
|
||||
}
|
||||
|
||||
fun removeLastOutboxMessageId(serverName: String) {
|
||||
databaseHelper.writableDatabase.delete(lastOutboxMessageServerIdTable, "$server = ?", wrap(serverName))
|
||||
}
|
||||
|
||||
override fun getForkInfo(): ForkInfo {
|
||||
val database = databaseHelper.readableDatabase
|
||||
val queryCursor = database.query(FORK_INFO_TABLE, arrayOf(HF_VALUE, SF_VALUE), "$DUMMY_KEY = $DUMMY_VALUE", null, null, null, null)
|
||||
|
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
@@ -30,16 +30,16 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
return DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
|
||||
}
|
||||
|
||||
fun getAllV2OpenGroups(): Map<Long, OpenGroupV2> {
|
||||
fun getAllOpenGroups(): Map<Long, OpenGroup> {
|
||||
val database = databaseHelper.readableDatabase
|
||||
var cursor: Cursor? = null
|
||||
val result = mutableMapOf<Long, OpenGroupV2>()
|
||||
val result = mutableMapOf<Long, OpenGroup>()
|
||||
try {
|
||||
cursor = database.rawQuery("select * from $publicChatTable", null)
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
val threadID = cursor.getLong(threadID)
|
||||
val string = cursor.getString(publicChat)
|
||||
val openGroup = OpenGroupV2.fromJSON(string)
|
||||
val openGroup = OpenGroup.fromJSON(string)
|
||||
if (openGroup != null) result[threadID] = openGroup
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -50,25 +50,25 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
||||
return result
|
||||
}
|
||||
|
||||
fun getOpenGroupChat(threadID: Long): OpenGroupV2? {
|
||||
fun getOpenGroupChat(threadID: Long): OpenGroup? {
|
||||
if (threadID < 0) {
|
||||
return null
|
||||
}
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor ->
|
||||
val json = cursor.getString(publicChat)
|
||||
OpenGroupV2.fromJSON(json)
|
||||
OpenGroup.fromJSON(json)
|
||||
}
|
||||
}
|
||||
|
||||
fun setOpenGroupChat(openGroupV2: OpenGroupV2, threadID: Long) {
|
||||
fun setOpenGroupChat(openGroup: OpenGroup, threadID: Long) {
|
||||
if (threadID < 0) {
|
||||
return
|
||||
}
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues(2)
|
||||
contentValues.put(Companion.threadID, threadID)
|
||||
contentValues.put(publicChat, JsonUtil.toJson(openGroupV2.toJson()))
|
||||
contentValues.put(publicChat, JsonUtil.toJson(openGroup.toJson()))
|
||||
database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString()))
|
||||
}
|
||||
|
||||
|
@@ -42,6 +42,8 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
|
||||
|
||||
public abstract boolean deleteMessage(long messageId);
|
||||
|
||||
public abstract void updateThreadId(long fromId, long toId);
|
||||
|
||||
public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) {
|
||||
try {
|
||||
addToDocument(messageId, MISMATCHED_IDENTITIES,
|
||||
|
@@ -1031,6 +1031,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
return threadDeleted
|
||||
}
|
||||
|
||||
override fun updateThreadId(fromId: Long, toId: Long) {
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(THREAD_ID, toId)
|
||||
|
||||
val db = databaseHelper.writableDatabase
|
||||
db.update(SmsDatabase.TABLE_NAME, contentValues, "$THREAD_ID = ?", arrayOf("$fromId"))
|
||||
notifyConversationListeners(toId)
|
||||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
fun deleteThread(threadId: Long) {
|
||||
deleteThreads(setOf(threadId))
|
||||
}
|
||||
|
@@ -93,12 +93,11 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsDatabase.Reader reader = readerFor(cursor);
|
||||
|
||||
MessageRecord messageRecord;
|
||||
boolean isOwnNumber = Util.isOwnNumber(context, serializedAuthor);
|
||||
|
||||
while ((messageRecord = reader.getNext()) != null) {
|
||||
if ((Util.isOwnNumber(context, serializedAuthor) && messageRecord.isOutgoing()) ||
|
||||
(!Util.isOwnNumber(context, serializedAuthor)
|
||||
&& messageRecord.getIndividualRecipient().getAddress().serialize().equals(serializedAuthor)
|
||||
))
|
||||
if ((isOwnNumber && messageRecord.isOutgoing()) ||
|
||||
(!isOwnNumber && messageRecord.getIndividualRecipient().getAddress().serialize().equals(serializedAuthor)))
|
||||
{
|
||||
return messageRecord;
|
||||
}
|
||||
|
@@ -255,10 +255,6 @@ public class RecipientDatabase extends Database {
|
||||
recipient.resolve().setApproved(approved);
|
||||
}
|
||||
|
||||
public void setAllApproved(List<String> addresses) {
|
||||
|
||||
}
|
||||
|
||||
public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(APPROVED_ME, approvedMe ? 1 : 0);
|
||||
|
@@ -575,6 +575,17 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
return threadDeleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThreadId(long fromId, long toId) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(MmsSmsColumns.THREAD_ID, toId);
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.update(TABLE_NAME, contentValues, THREAD_ID + " = ?", new String[] {fromId + ""});
|
||||
notifyConversationListeners(toId);
|
||||
notifyConversationListListeners();
|
||||
}
|
||||
|
||||
private boolean isDuplicate(IncomingTextMessage message, long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?",
|
||||
|
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.session.libsession.database.StorageProtocol
|
||||
import org.session.libsession.messaging.BlindedIdMapping
|
||||
import org.session.libsession.messaging.calls.CallMessageType
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
||||
@@ -22,12 +23,15 @@ import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
|
||||
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
|
||||
import org.session.libsession.messaging.messages.visible.Attachment
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||
import org.session.libsession.messaging.open_groups.GroupMember
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
import org.session.libsession.messaging.utilities.SessionId
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.utilities.Address
|
||||
@@ -40,6 +44,7 @@ import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||
import org.session.libsignal.messages.SignalServiceAttachmentPointer
|
||||
import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.session.libsignal.utilities.KeyHelper
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
@@ -73,7 +78,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
}
|
||||
|
||||
override fun setUserProfilePictureURL(newValue: String) {
|
||||
val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let {
|
||||
val ourRecipient = fromSerialized(getUserPublicKey()!!).let {
|
||||
Recipient.from(context, it, false)
|
||||
}
|
||||
TextSecurePreferences.setProfilePictureURL(context, newValue)
|
||||
@@ -125,8 +130,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
runIncrement: Boolean,
|
||||
runThreadUpdate: Boolean): Long? {
|
||||
var messageID: Long? = null
|
||||
val senderAddress = Address.fromSerialized(message.sender!!)
|
||||
val senderAddress = fromSerialized(message.sender!!)
|
||||
val isUserSender = (message.sender!! == getUserPublicKey())
|
||||
val isUserBlindedSender = message.threadID?.takeIf { it >= 0 }?.let { getOpenGroup(it)?.publicKey }
|
||||
?.let { SodiumUtilities.sessionId(getUserPublicKey()!!, message.sender!!, it) } ?: false
|
||||
val group: Optional<SignalServiceGroup> = when {
|
||||
openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT))
|
||||
groupPublicKey != null -> {
|
||||
@@ -138,17 +145,17 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
val pointers = attachments.mapNotNull {
|
||||
it.toSignalAttachment()
|
||||
}
|
||||
val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) {
|
||||
Address.fromSerialized(message.syncTarget!!)
|
||||
val targetAddress = if ((isUserSender || isUserBlindedSender) && !message.syncTarget.isNullOrEmpty()) {
|
||||
fromSerialized(message.syncTarget!!)
|
||||
} else if (group.isPresent) {
|
||||
Address.fromSerialized(GroupUtil.getEncodedId(group.get()))
|
||||
fromSerialized(GroupUtil.getEncodedId(group.get()))
|
||||
} else {
|
||||
senderAddress
|
||||
}
|
||||
val targetRecipient = Recipient.from(context, targetAddress, false)
|
||||
if (!targetRecipient.isGroupRecipient) {
|
||||
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
|
||||
if (isUserSender) {
|
||||
if (isUserSender || isUserBlindedSender) {
|
||||
recipientDb.setApproved(targetRecipient, true)
|
||||
} else {
|
||||
recipientDb.setApprovedMe(targetRecipient, true)
|
||||
@@ -158,7 +165,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
val quote: Optional<QuoteModel> = if (quotes != null) Optional.of(quotes) else Optional.absent()
|
||||
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
|
||||
val mmsDatabase = DatabaseComponent.get(context).mmsDatabase()
|
||||
val insertResult = if (message.sender == getUserPublicKey()) {
|
||||
val insertResult = if (isUserSender || isUserBlindedSender) {
|
||||
val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointers, quote.orNull(), linkPreviews.orNull()?.firstOrNull())
|
||||
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!, runThreadUpdate)
|
||||
} else {
|
||||
@@ -176,7 +183,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
val smsDatabase = DatabaseComponent.get(context).smsDatabase()
|
||||
val isOpenGroupInvitation = (message.openGroupInvitation != null)
|
||||
|
||||
val insertResult = if (message.sender == getUserPublicKey()) {
|
||||
val insertResult = if (isUserSender || isUserBlindedSender) {
|
||||
val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, targetRecipient, message.sentTimestamp)
|
||||
else OutgoingTextMessage.from(message, targetRecipient)
|
||||
smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!, runThreadUpdate)
|
||||
@@ -259,12 +266,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseComponent.get(context).lokiAPIDatabase().setAuthToken(id, null)
|
||||
}
|
||||
|
||||
override fun getV2OpenGroup(threadId: Long): OpenGroupV2? {
|
||||
override fun getOpenGroup(threadId: Long): OpenGroup? {
|
||||
if (threadId.toInt() < 0) { return null }
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf( threadId.toString() )) { cursor ->
|
||||
val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat)
|
||||
OpenGroupV2.fromJSON(publicChatAsJson)
|
||||
OpenGroup.fromJSON(publicChatAsJson)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +316,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseComponent.get(context).lokiMessageDatabase().setOriginalThreadID(messageID, serverID, threadID)
|
||||
}
|
||||
|
||||
override fun getOpenGroup(room: String, server: String): OpenGroup? {
|
||||
return getAllOpenGroups().values.firstOrNull { it.server == server && it.room == room }
|
||||
}
|
||||
|
||||
override fun addGroupMember(member: GroupMember) {
|
||||
DatabaseComponent.get(context).groupMemberDatabase().addGroupMember(member)
|
||||
}
|
||||
|
||||
override fun isDuplicateMessage(timestamp: Long): Boolean {
|
||||
return getReceivedMessageTimestamps().contains(timestamp)
|
||||
}
|
||||
@@ -335,7 +350,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
override fun getMessageIdInDatabase(timestamp: Long, author: String): Long? {
|
||||
val database = DatabaseComponent.get(context).mmsSmsDatabase()
|
||||
val address = Address.fromSerialized(author)
|
||||
val address = fromSerialized(author)
|
||||
return database.getMessageFor(timestamp, address)?.getId()
|
||||
}
|
||||
|
||||
@@ -453,7 +468,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
|
||||
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
||||
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
|
||||
val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
|
||||
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
|
||||
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
|
||||
val smsDB = DatabaseComponent.get(context).smsDatabase()
|
||||
@@ -462,7 +477,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
|
||||
val userPublicKey = getUserPublicKey()
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
|
||||
val recipient = Recipient.from(context, fromSerialized(groupID), false)
|
||||
|
||||
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: ""
|
||||
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf())
|
||||
@@ -475,7 +490,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
override fun isClosedGroup(publicKey: String): Boolean {
|
||||
val isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(publicKey)
|
||||
val address = Address.fromSerialized(publicKey)
|
||||
val address = fromSerialized(publicKey)
|
||||
return address.isClosedGroup || isClosedGroup
|
||||
}
|
||||
|
||||
@@ -528,8 +543,20 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration);
|
||||
}
|
||||
|
||||
override fun getAllV2OpenGroups(): Map<Long, OpenGroupV2> {
|
||||
return DatabaseComponent.get(context).lokiThreadDatabase().getAllV2OpenGroups()
|
||||
override fun setServerCapabilities(server: String, capabilities: List<String>) {
|
||||
return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
|
||||
}
|
||||
|
||||
override fun getServerCapabilities(server: String): List<String> {
|
||||
return DatabaseComponent.get(context).lokiAPIDatabase().getServerCapabilities(server)
|
||||
}
|
||||
|
||||
override fun getAllOpenGroups(): Map<Long, OpenGroup> {
|
||||
return DatabaseComponent.get(context).lokiThreadDatabase().getAllOpenGroups()
|
||||
}
|
||||
|
||||
override fun updateOpenGroup(openGroup: OpenGroup) {
|
||||
OpenGroupManager.updateOpenGroup(openGroup, context)
|
||||
}
|
||||
|
||||
override fun getAllGroups(): List<GroupRecord> {
|
||||
@@ -541,7 +568,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
}
|
||||
|
||||
override fun onOpenGroupAdded(urlAsString: String) {
|
||||
val server = OpenGroupV2.getServer(urlAsString)
|
||||
val server = OpenGroup.getServer(urlAsString)
|
||||
OpenGroupManager.restartPollerForServer(server.toString().removeSuffix("/"))
|
||||
}
|
||||
|
||||
@@ -562,20 +589,20 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long {
|
||||
val database = DatabaseComponent.get(context).threadDatabase()
|
||||
if (!openGroupID.isNullOrEmpty()) {
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
|
||||
return database.getThreadIdIfExistsFor(recipient)
|
||||
return if (!openGroupID.isNullOrEmpty()) {
|
||||
val recipient = Recipient.from(context, fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
|
||||
database.getThreadIdIfExistsFor(recipient)
|
||||
} else if (!groupPublicKey.isNullOrEmpty()) {
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
|
||||
return database.getOrCreateThreadIdFor(recipient)
|
||||
val recipient = Recipient.from(context, fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
|
||||
database.getOrCreateThreadIdFor(recipient)
|
||||
} else {
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
|
||||
return database.getOrCreateThreadIdFor(recipient)
|
||||
val recipient = Recipient.from(context, fromSerialized(publicKey), false)
|
||||
database.getOrCreateThreadIdFor(recipient)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getThreadId(publicKeyOrOpenGroupID: String): Long? {
|
||||
val address = Address.fromSerialized(publicKeyOrOpenGroupID)
|
||||
val address = fromSerialized(publicKeyOrOpenGroupID)
|
||||
return getThreadId(address)
|
||||
}
|
||||
|
||||
@@ -622,8 +649,13 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
override fun addContacts(contacts: List<ConfigurationMessage.Contact>) {
|
||||
val recipientDatabase = DatabaseComponent.get(context).recipientDatabase()
|
||||
val threadDatabase = DatabaseComponent.get(context).threadDatabase()
|
||||
for (contact in contacts) {
|
||||
val address = Address.fromSerialized(contact.publicKey)
|
||||
val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase()
|
||||
val moreContacts = contacts.filter { contact ->
|
||||
val id = SessionId(contact.publicKey)
|
||||
id.prefix != IdPrefix.BLINDED || mappingDb.getBlindedIdMapping(contact.publicKey).none { it.sessionId != null }
|
||||
}
|
||||
for (contact in moreContacts) {
|
||||
val address = fromSerialized(contact.publicKey)
|
||||
val recipient = Recipient.from(context, address, true)
|
||||
if (!contact.profilePicture.isNullOrEmpty()) {
|
||||
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
|
||||
@@ -715,12 +747,48 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
threadDB.setHasSent(threadId, true)
|
||||
} else {
|
||||
val mmsDb = DatabaseComponent.get(context).mmsDatabase()
|
||||
val senderAddress = fromSerialized(senderPublicKey)
|
||||
val requestSender = Recipient.from(context, senderAddress, false)
|
||||
recipientDb.setApprovedMe(requestSender, true)
|
||||
val smsDb = DatabaseComponent.get(context).smsDatabase()
|
||||
val sender = Recipient.from(context, fromSerialized(senderPublicKey), false)
|
||||
val threadId = threadDB.getOrCreateThreadIdFor(sender)
|
||||
threadDB.setHasSent(threadId, true)
|
||||
val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase()
|
||||
val mappings = mutableMapOf<String, BlindedIdMapping>()
|
||||
threadDB.readerFor(threadDB.conversationList).use { reader ->
|
||||
while (reader.next != null) {
|
||||
val recipient = reader.current.recipient
|
||||
val address = recipient.address.serialize()
|
||||
val blindedId = when {
|
||||
recipient.isGroupRecipient -> null
|
||||
recipient.isOpenGroupInboxRecipient -> {
|
||||
GroupUtil.getDecodedOpenGroupInbox(address)
|
||||
}
|
||||
else -> {
|
||||
if (SessionId(address).prefix == IdPrefix.BLINDED) {
|
||||
address
|
||||
} else null
|
||||
}
|
||||
} ?: continue
|
||||
mappingDb.getBlindedIdMapping(blindedId).firstOrNull()?.let {
|
||||
mappings[address] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
for (mapping in mappings) {
|
||||
if (!SodiumUtilities.sessionId(senderPublicKey, mapping.value.blindedId, mapping.value.serverId)) {
|
||||
continue
|
||||
}
|
||||
mappingDb.addBlindedIdMapping(mapping.value.copy(sessionId = senderPublicKey))
|
||||
|
||||
val blindedThreadId = threadDB.getOrCreateThreadIdFor(Recipient.from(context, fromSerialized(mapping.key), false))
|
||||
mmsDb.updateThreadId(blindedThreadId, threadId)
|
||||
smsDb.updateThreadId(blindedThreadId, threadId)
|
||||
threadDB.deleteConversation(blindedThreadId)
|
||||
}
|
||||
recipientDb.setApproved(sender, true)
|
||||
recipientDb.setApprovedMe(sender, true)
|
||||
|
||||
val message = IncomingMediaMessage(
|
||||
senderAddress,
|
||||
sender.address,
|
||||
response.sentTimestamp!!,
|
||||
-1,
|
||||
0,
|
||||
@@ -735,7 +803,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
Optional.absent(),
|
||||
Optional.absent()
|
||||
)
|
||||
val threadId = getOrCreateThreadIdFor(senderAddress)
|
||||
mmsDb.insertSecureDecryptedMessageInbox(message, threadId, runIncrement = true, runThreadUpdate = true)
|
||||
}
|
||||
}
|
||||
@@ -748,7 +815,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe)
|
||||
}
|
||||
|
||||
|
||||
override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) {
|
||||
val database = DatabaseComponent.get(context).smsDatabase()
|
||||
val address = fromSerialized(senderPublicKey)
|
||||
@@ -764,4 +830,62 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
return database.getLastSeenAndHasSent(threadId).second() ?: false
|
||||
}
|
||||
|
||||
override fun getLastInboxMessageId(server: String): Long? {
|
||||
return DatabaseComponent.get(context).lokiAPIDatabase().getLastInboxMessageId(server)
|
||||
}
|
||||
|
||||
override fun setLastInboxMessageId(server: String, messageId: Long) {
|
||||
DatabaseComponent.get(context).lokiAPIDatabase().setLastInboxMessageId(server, messageId)
|
||||
}
|
||||
|
||||
override fun removeLastInboxMessageId(server: String) {
|
||||
DatabaseComponent.get(context).lokiAPIDatabase().removeLastInboxMessageId(server)
|
||||
}
|
||||
|
||||
override fun getLastOutboxMessageId(server: String): Long? {
|
||||
return DatabaseComponent.get(context).lokiAPIDatabase().getLastOutboxMessageId(server)
|
||||
}
|
||||
|
||||
override fun setLastOutboxMessageId(server: String, messageId: Long) {
|
||||
DatabaseComponent.get(context).lokiAPIDatabase().setLastOutboxMessageId(server, messageId)
|
||||
}
|
||||
|
||||
override fun removeLastOutboxMessageId(server: String) {
|
||||
DatabaseComponent.get(context).lokiAPIDatabase().removeLastOutboxMessageId(server)
|
||||
}
|
||||
|
||||
override fun getOrCreateBlindedIdMapping(
|
||||
blindedId: String,
|
||||
server: String,
|
||||
serverPublicKey: String,
|
||||
fromOutbox: Boolean
|
||||
): BlindedIdMapping {
|
||||
val db = DatabaseComponent.get(context).blindedIdMappingDatabase()
|
||||
val mapping = db.getBlindedIdMapping(blindedId).firstOrNull() ?: BlindedIdMapping(blindedId, null, server, serverPublicKey)
|
||||
if (mapping.sessionId != null) {
|
||||
return mapping
|
||||
}
|
||||
val threadDb = DatabaseComponent.get(context).threadDatabase()
|
||||
threadDb.readerFor(threadDb.conversationList).use { reader ->
|
||||
while (reader.next != null) {
|
||||
val recipient = reader.current.recipient
|
||||
val sessionId = recipient.address.serialize()
|
||||
if (!recipient.isGroupRecipient && SodiumUtilities.sessionId(sessionId, blindedId, serverPublicKey)) {
|
||||
val contactMapping = mapping.copy(sessionId = sessionId)
|
||||
db.addBlindedIdMapping(contactMapping)
|
||||
return contactMapping
|
||||
}
|
||||
}
|
||||
}
|
||||
db.getBlindedIdMappingsExceptFor(server).forEach {
|
||||
if (SodiumUtilities.sessionId(it.sessionId!!, blindedId, serverPublicKey)) {
|
||||
val otherMapping = mapping.copy(sessionId = it.sessionId)
|
||||
db.addBlindedIdMapping(otherMapping)
|
||||
return otherMapping
|
||||
}
|
||||
}
|
||||
db.addBlindedIdMapping(mapping)
|
||||
return mapping
|
||||
}
|
||||
}
|
@@ -43,6 +43,7 @@ import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsession.utilities.recipients.Recipient.RecipientSettings;
|
||||
import org.session.libsignal.utilities.IdPrefix;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.Pair;
|
||||
import org.session.libsignal.utilities.guava.Optional;
|
||||
@@ -447,6 +448,11 @@ public class ThreadDatabase extends Database {
|
||||
return getConversationList(where);
|
||||
}
|
||||
|
||||
public Cursor getBlindedConversationList() {
|
||||
String where = TABLE_NAME + "." + ADDRESS + " LIKE '" + IdPrefix.BLINDED.getValue() + "%' ";
|
||||
return getConversationList(where);
|
||||
}
|
||||
|
||||
public Cursor getApprovedConversationList() {
|
||||
String where = "((" + MESSAGE_COUNT + " != 0 AND (" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%')) OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " +
|
||||
"AND " + ARCHIVED + " = 0 ";
|
||||
|
@@ -14,8 +14,10 @@ import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.BlindedIdMappingDatabase;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupMemberDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
|
||||
@@ -67,9 +69,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int lokiV32 = 53;
|
||||
private static final int lokiV33 = 54;
|
||||
private static final int lokiV34 = 55;
|
||||
private static final int lokiV35 = 55;
|
||||
|
||||
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||
private static final int DATABASE_VERSION = lokiV34;
|
||||
private static final int DATABASE_VERSION = lokiV35;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
@@ -135,6 +138,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable());
|
||||
db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable());
|
||||
db.execSQL(LokiAPIDatabase.getCreateServerCapabilitiesCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastInboxMessageServerIdCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastOutboxMessageServerIdCommand());
|
||||
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
|
||||
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
|
||||
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
||||
@@ -161,6 +167,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiAPIDatabase.DROP_LEGACY_LAST_HASH);
|
||||
db.execSQL(LokiAPIDatabase.INSERT_RECEIVED_HASHES_DATA);
|
||||
db.execSQL(LokiAPIDatabase.DROP_LEGACY_RECEIVED_HASHES);
|
||||
db.execSQL(BlindedIdMappingDatabase.CREATE_BLINDED_ID_MAPPING_TABLE_COMMAND);
|
||||
db.execSQL(GroupMemberDatabase.CREATE_GROUP_MEMBER_TABLE_COMMAND);
|
||||
|
||||
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
||||
@@ -369,6 +377,14 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiAPIDatabase.DROP_LEGACY_RECEIVED_HASHES);
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV35) {
|
||||
db.execSQL(LokiAPIDatabase.getCreateServerCapabilitiesCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastInboxMessageServerIdCommand());
|
||||
db.execSQL(LokiAPIDatabase.getCreateLastOutboxMessageServerIdCommand());
|
||||
db.execSQL(BlindedIdMappingDatabase.CREATE_BLINDED_ID_MAPPING_TABLE_COMMAND);
|
||||
db.execSQL(GroupMemberDatabase.CREATE_GROUP_MEMBER_TABLE_COMMAND);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@@ -115,6 +115,8 @@ public class ThreadRecord extends DisplayRecord {
|
||||
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified));
|
||||
} else if (SmsDatabase.Types.isIdentityDefault(type)) {
|
||||
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified));
|
||||
} else if (MmsSmsColumns.Types.isMessageRequestResponse(type)) {
|
||||
return emphasisAdded(context.getString(R.string.message_requests_accepted));
|
||||
} else if (getCount() == 0) {
|
||||
return new SpannableString(context.getString(R.string.ThreadRecord_empty_message));
|
||||
} else {
|
||||
|
@@ -42,4 +42,6 @@ interface DatabaseComponent {
|
||||
fun sessionContactDatabase(): SessionContactDatabase
|
||||
fun storage(): Storage
|
||||
fun attachmentProvider(): MessageDataProvider
|
||||
fun blindedIdMappingDatabase(): BlindedIdMappingDatabase
|
||||
fun groupMemberDatabase(): GroupMemberDatabase
|
||||
}
|
@@ -117,6 +117,14 @@ object DatabaseModule {
|
||||
@Singleton
|
||||
fun provideSessionContactDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = SessionContactDatabase(context,openHelper)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBlindedIdMappingDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = BlindedIdMappingDatabase(context, openHelper)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGroupMemberDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = GroupMemberDatabase(context, openHelper)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStorage(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = Storage(context,openHelper)
|
||||
|
@@ -4,19 +4,22 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.thoughtcrime.securesms.util.State
|
||||
|
||||
typealias DefaultGroups = List<OpenGroupAPIV2.DefaultGroup>
|
||||
typealias DefaultGroups = List<OpenGroupApi.DefaultGroup>
|
||||
typealias GroupState = State<DefaultGroups>
|
||||
|
||||
class DefaultGroupsViewModel : ViewModel() {
|
||||
|
||||
init {
|
||||
OpenGroupAPIV2.getDefaultRoomsIfNeeded()
|
||||
OpenGroupApi.getDefaultServerCapabilities().map {
|
||||
OpenGroupApi.getDefaultRoomsIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
val defaultRooms = OpenGroupAPIV2.defaultRooms.map<DefaultGroups, GroupState> {
|
||||
val defaultRooms = OpenGroupApi.defaultRooms.map<DefaultGroups, GroupState> {
|
||||
State.Success(it)
|
||||
}.onStart {
|
||||
emit(State.Loading)
|
||||
|
@@ -26,7 +26,7 @@ import network.loki.messenger.databinding.ActivityJoinPublicChatBinding
|
||||
import network.loki.messenger.databinding.FragmentEnterChatUrlBinding
|
||||
import okhttp3.HttpUrl
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.DefaultGroup
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi.DefaultGroup
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
|
@@ -4,16 +4,17 @@ import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import okhttp3.HttpUrl
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerV2
|
||||
import org.session.libsession.messaging.open_groups.GroupMemberRole
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
object OpenGroupManager {
|
||||
private val executorService = Executors.newScheduledThreadPool(4)
|
||||
private var pollers = mutableMapOf<String, OpenGroupPollerV2>() // One for each server
|
||||
private var pollers = mutableMapOf<String, OpenGroupPoller>() // One for each server
|
||||
private var isPolling = false
|
||||
private val pollUpdaterLock = Any()
|
||||
|
||||
@@ -38,10 +39,10 @@ object OpenGroupManager {
|
||||
if (isPolling) { return }
|
||||
isPolling = true
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val servers = storage.getAllV2OpenGroups().values.map { it.server }.toSet()
|
||||
val servers = storage.getAllOpenGroups().values.map { it.server }.toSet()
|
||||
servers.forEach { server ->
|
||||
pollers[server]?.stop() // Shouldn't be necessary
|
||||
val poller = OpenGroupPollerV2(server, executorService)
|
||||
val poller = OpenGroupPoller(server, executorService)
|
||||
poller.startIfNeeded()
|
||||
pollers[server] = poller
|
||||
}
|
||||
@@ -67,17 +68,21 @@ object OpenGroupManager {
|
||||
// Clear any existing data if needed
|
||||
storage.removeLastDeletionServerID(room, server)
|
||||
storage.removeLastMessageServerID(room, server)
|
||||
storage.removeLastInboxMessageId(server)
|
||||
storage.removeLastOutboxMessageId(server)
|
||||
// Store the public key
|
||||
storage.setOpenGroupPublicKey(server,publicKey)
|
||||
// Get group info
|
||||
OpenGroupAPIV2.getAuthToken(room, server).get()
|
||||
// Get group info
|
||||
val info = OpenGroupAPIV2.getInfo(room, server).get()
|
||||
// Get capabilities
|
||||
val capabilities = OpenGroupApi.getCapabilities(server).get()
|
||||
storage.setServerCapabilities(server, capabilities.capabilities)
|
||||
// Get room info
|
||||
val info = OpenGroupApi.getRoomInfo(room, server).get()
|
||||
storage.setUserCount(room, server, info.activeUsers)
|
||||
// Create the group locally if not available already
|
||||
if (threadID < 0) {
|
||||
threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId
|
||||
}
|
||||
val openGroup = OpenGroupV2(server, room, info.name, publicKey)
|
||||
val openGroup = OpenGroup(server, room, info.name, info.infoUpdates, publicKey)
|
||||
threadDB.setOpenGroupChat(openGroup, threadID)
|
||||
}
|
||||
|
||||
@@ -86,7 +91,7 @@ object OpenGroupManager {
|
||||
synchronized(pollUpdaterLock) {
|
||||
pollers[server]?.stop()
|
||||
pollers[server]?.startIfNeeded() ?: run {
|
||||
val poller = OpenGroupPollerV2(server, executorService)
|
||||
val poller = OpenGroupPoller(server, executorService)
|
||||
pollers[server] = poller
|
||||
poller.startIfNeeded()
|
||||
}
|
||||
@@ -102,7 +107,7 @@ object OpenGroupManager {
|
||||
threadDB.setThreadArchived(threadID)
|
||||
val groupID = recipient.address.serialize()
|
||||
// Stop the poller if needed
|
||||
val openGroups = storage.getAllV2OpenGroups().filter { it.value.server == server }
|
||||
val openGroups = storage.getAllOpenGroups().filter { it.value.server == server }
|
||||
if (openGroups.count() == 1) {
|
||||
synchronized(pollUpdaterLock) {
|
||||
val poller = pollers[server]
|
||||
@@ -113,6 +118,8 @@ object OpenGroupManager {
|
||||
// Delete
|
||||
storage.removeLastDeletionServerID(room, server)
|
||||
storage.removeLastMessageServerID(room, server)
|
||||
storage.removeLastInboxMessageId(server)
|
||||
storage.removeLastOutboxMessageId(server)
|
||||
val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase()
|
||||
lokiThreadDB.removeOpenGroupChat(threadID)
|
||||
ThreadUtils.queue {
|
||||
@@ -123,9 +130,26 @@ object OpenGroupManager {
|
||||
|
||||
fun addOpenGroup(urlAsString: String, context: Context) {
|
||||
val url = HttpUrl.parse(urlAsString) ?: return
|
||||
val server = OpenGroupV2.getServer(urlAsString)
|
||||
val server = OpenGroup.getServer(urlAsString)
|
||||
val room = url.pathSegments().firstOrNull() ?: return
|
||||
val publicKey = url.queryParameter("public_key") ?: return
|
||||
add(server.toString().removeSuffix("/"), room, publicKey, context)
|
||||
}
|
||||
|
||||
fun updateOpenGroup(openGroup: OpenGroup, context: Context) {
|
||||
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
|
||||
val openGroupID = "${openGroup.server}.${openGroup.room}"
|
||||
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
|
||||
threadDB.setOpenGroupChat(openGroup, threadID)
|
||||
}
|
||||
|
||||
fun isUserModerator(context: Context, groupId: String, standardPublicKey: String, blindedPublicKey: String? = null): Boolean {
|
||||
val memberDatabase = DatabaseComponent.get(context).groupMemberDatabase()
|
||||
val standardRoles = memberDatabase.getGroupMemberRoles(groupId, standardPublicKey)
|
||||
val blindedRoles = blindedPublicKey?.let { memberDatabase.getGroupMemberRoles(groupId, it) } ?: emptyList()
|
||||
|
||||
return GroupMemberRole.ADMIN in standardRoles || GroupMemberRole.MODERATOR in standardRoles ||
|
||||
GroupMemberRole.ADMIN in blindedRoles || GroupMemberRole.MODERATOR in blindedRoles
|
||||
}
|
||||
|
||||
}
|
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.groups
|
||||
import android.content.Context
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
@@ -29,8 +29,7 @@ object OpenGroupUtilities {
|
||||
throw IllegalStateException("Attempt to update open group info for non-existent DB record: $groupId")
|
||||
}
|
||||
|
||||
val info = OpenGroupAPIV2.getInfo(room, server).get() // store info again?
|
||||
OpenGroupAPIV2.getMemberCount(room, server).get()
|
||||
val info = OpenGroupApi.getRoomInfo(room, server).get() // store info again?
|
||||
|
||||
EventBus.getDefault().post(GroupInfoUpdatedEvent(server, room = room))
|
||||
}
|
||||
|
@@ -82,7 +82,7 @@ class ConversationView : LinearLayout {
|
||||
}
|
||||
binding.muteIndicatorImageView.setImageResource(drawableRes)
|
||||
val rawSnippet = thread.getDisplayBody(context)
|
||||
val snippet = highlightMentions(rawSnippet, recipient.isOpenGroupRecipient, context)
|
||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
||||
binding.snippetTextView.text = snippet
|
||||
binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
||||
binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
|
||||
|
@@ -21,6 +21,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
@@ -83,8 +84,8 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
|
||||
}
|
||||
nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally
|
||||
|
||||
publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient
|
||||
messageButton.isVisible = !threadRecipient.isOpenGroupRecipient
|
||||
publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient && !threadRecipient.isOpenGroupInboxRecipient
|
||||
messageButton.isVisible = !threadRecipient.isOpenGroupRecipient || IdPrefix.fromValue(publicKey) == IdPrefix.BLINDED
|
||||
publicKeyTextView.text = publicKey
|
||||
publicKeyTextView.setOnLongClickListener {
|
||||
val clipboard =
|
||||
@@ -103,6 +104,7 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
|
||||
)
|
||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId ?: -1)
|
||||
intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID)
|
||||
startActivity(intent)
|
||||
dismiss()
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.session.libsession.messaging.file_server.FileServerAPIV2;
|
||||
import org.session.libsession.messaging.file_server.FileServerApi;
|
||||
|
||||
public class PushMediaConstraints extends MediaConstraints {
|
||||
|
||||
@@ -21,26 +21,26 @@ public class PushMediaConstraints extends MediaConstraints {
|
||||
|
||||
@Override
|
||||
public int getImageMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGifMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVideoMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocumentMaxSize(Context context) {
|
||||
return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
|
||||
return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerV2
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.Log
|
||||
@@ -72,13 +72,13 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
|
||||
|
||||
// Open Groups
|
||||
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
|
||||
val v2OpenGroups = threadDB.getAllV2OpenGroups()
|
||||
val v2OpenGroupServers = v2OpenGroups.map { it.value.server }.toSet()
|
||||
val openGroups = threadDB.getAllOpenGroups()
|
||||
val openGroupServers = openGroups.map { it.value.server }.toSet()
|
||||
|
||||
for (server in v2OpenGroupServers) {
|
||||
val poller = OpenGroupPollerV2(server, null)
|
||||
for (server in openGroupServers) {
|
||||
val poller = OpenGroupPoller(server, null)
|
||||
poller.hasStarted = true
|
||||
promises.add(poller.poll(true))
|
||||
promises.add(poller.poll())
|
||||
}
|
||||
|
||||
// Wait until all the promises are resolved
|
||||
|
@@ -36,15 +36,22 @@ import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.goterl.lazysodium.utils.KeyPair;
|
||||
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup;
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
||||
import org.session.libsession.messaging.utilities.SessionId;
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities;
|
||||
import org.session.libsession.utilities.Address;
|
||||
import org.session.libsession.utilities.Contact;
|
||||
import org.session.libsession.utilities.ServiceUtil;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsignal.utilities.IdPrefix;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.Util;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
@@ -52,6 +59,8 @@ import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities;
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
|
||||
import org.thoughtcrime.securesms.database.LokiThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
@@ -66,9 +75,11 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -491,6 +502,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
||||
MmsSmsDatabase.Reader reader = DatabaseComponent.get(context).mmsSmsDatabase().readerFor(cursor);
|
||||
ThreadDatabase threadDatabase = DatabaseComponent.get(context).threadDatabase();
|
||||
MessageRecord record;
|
||||
Map<Long, String> cache = new HashMap<Long, String>();
|
||||
|
||||
while ((record = reader.getNext()) != null) {
|
||||
long id = record.getId();
|
||||
@@ -534,16 +546,22 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
||||
|
||||
if (threadRecipients == null || !threadRecipients.isMuted()) {
|
||||
if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) {
|
||||
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
String blindedPublicKey = cache.get(threadId);
|
||||
if (blindedPublicKey == null) {
|
||||
blindedPublicKey = generateBlindedId(threadId, context);
|
||||
cache.put(threadId, blindedPublicKey);
|
||||
}
|
||||
// check if mentioned here
|
||||
boolean isQuoteMentioned = false;
|
||||
if (record instanceof MmsMessageRecord) {
|
||||
Quote quote = ((MmsMessageRecord) record).getQuote();
|
||||
Address quoteAddress = quote != null ? quote.getAuthor() : null;
|
||||
String serializedAddress = quoteAddress != null ? quoteAddress.serialize() : null;
|
||||
isQuoteMentioned = serializedAddress != null && Objects.equals(TextSecurePreferences.getLocalNumber(context), serializedAddress);
|
||||
isQuoteMentioned = (serializedAddress!= null && Objects.equals(userPublicKey, serializedAddress)) ||
|
||||
(blindedPublicKey != null && Objects.equals(userPublicKey, blindedPublicKey));
|
||||
}
|
||||
if (body.toString().contains("@"+TextSecurePreferences.getLocalNumber(context))
|
||||
|| isQuoteMentioned) {
|
||||
if (body.toString().contains("@"+userPublicKey) || body.toString().contains("@"+blindedPublicKey) || isQuoteMentioned) {
|
||||
notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck));
|
||||
}
|
||||
} else if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) {
|
||||
@@ -558,6 +576,19 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
||||
return notificationState;
|
||||
}
|
||||
|
||||
private @Nullable String generateBlindedId(long threadId, Context context) {
|
||||
LokiThreadDatabase lokiThreadDatabase = DatabaseComponent.get(context).lokiThreadDatabase();
|
||||
OpenGroup openGroup = lokiThreadDatabase.getOpenGroupChat(threadId);
|
||||
KeyPair edKeyPair = KeyPairUtilities.INSTANCE.getUserED25519KeyPair(context);
|
||||
if (openGroup != null && edKeyPair != null) {
|
||||
KeyPair blindedKeyPair = SodiumUtilities.INSTANCE.blindedKeyPair(openGroup.getPublicKey(), edKeyPair);
|
||||
if (blindedKeyPair != null) {
|
||||
return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).getHexString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updateBadge(Context context, int count) {
|
||||
try {
|
||||
if (count == 0) ShortcutBadger.removeCount(context);
|
||||
|
@@ -1,12 +1,14 @@
|
||||
package org.thoughtcrime.securesms.notifications
|
||||
|
||||
import android.content.Context
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.snode.Version
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
@@ -43,7 +45,7 @@ object LokiPushNotificationManager {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
getResponseBody(request.build()).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code != null && code != 0) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, false)
|
||||
@@ -72,7 +74,7 @@ object LokiPushNotificationManager {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
getResponseBody(request.build()).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code != null && code != 0) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, true)
|
||||
@@ -100,7 +102,7 @@ object LokiPushNotificationManager {
|
||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||
val request = Request.Builder().url(url).post(body)
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
|
||||
getResponseBody(request.build()).map { json ->
|
||||
val code = json["code"] as? Int
|
||||
if (code == null || code == 0) {
|
||||
Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.")
|
||||
@@ -110,4 +112,10 @@ object LokiPushNotificationManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getResponseBody(request: Request): Promise<Map<*, *>, Exception> {
|
||||
return OnionRequestAPI.sendOnionRequest(request, server, pnServerPublicKey, Version.V2).map { response ->
|
||||
JsonUtil.fromJson(response.body, Map::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
|
||||
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.Address
|
||||
@@ -86,7 +86,7 @@ class DefaultConversationRepository @Inject constructor(
|
||||
|
||||
override fun isOxenHostedOpenGroup(threadId: Long): Boolean {
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)
|
||||
return openGroup?.publicKey == OpenGroupAPIV2.defaultServerPublicKey
|
||||
return openGroup?.publicKey == OpenGroupApi.defaultServerPublicKey
|
||||
}
|
||||
|
||||
override fun maybeGetRecipientForThreadId(threadId: Long): Recipient? {
|
||||
@@ -153,7 +153,7 @@ class DefaultConversationRepository @Inject constructor(
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)
|
||||
if (openGroup != null) {
|
||||
lokiMessageDb.getServerID(message.id, !message.isMms)?.let { messageServerID ->
|
||||
OpenGroupAPIV2.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
||||
OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
||||
.success {
|
||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||
continuation.resume(ResultOf.Success(Unit))
|
||||
@@ -205,7 +205,7 @@ class DefaultConversationRepository @Inject constructor(
|
||||
messageServerIDs[messageServerID] = message
|
||||
}
|
||||
for ((messageServerID, message) in messageServerIDs) {
|
||||
OpenGroupAPIV2.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
||||
OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
||||
.success {
|
||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||
}.fail { error ->
|
||||
@@ -228,7 +228,7 @@ class DefaultConversationRepository @Inject constructor(
|
||||
suspendCoroutine { continuation ->
|
||||
val sessionID = recipient.address.toString()
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!!
|
||||
OpenGroupAPIV2.ban(sessionID, openGroup.room, openGroup.server)
|
||||
OpenGroupApi.ban(sessionID, openGroup.room, openGroup.server)
|
||||
.success {
|
||||
continuation.resume(ResultOf.Success(Unit))
|
||||
}.fail { error ->
|
||||
@@ -240,7 +240,7 @@ class DefaultConversationRepository @Inject constructor(
|
||||
suspendCoroutine { continuation ->
|
||||
val sessionID = recipient.address.toString()
|
||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!!
|
||||
OpenGroupAPIV2.banAndDeleteAll(sessionID, openGroup.room, openGroup.server)
|
||||
OpenGroupApi.banAndDeleteAll(sessionID, openGroup.room, openGroup.server)
|
||||
.success {
|
||||
continuation.resume(ResultOf.Success(Unit))
|
||||
}.fail { error ->
|
||||
|
@@ -12,6 +12,7 @@ import android.graphics.drawable.BitmapDrawable
|
||||
import android.text.TextPaint
|
||||
import android.text.TextUtils
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
import java.util.Locale
|
||||
@@ -66,7 +67,7 @@ object AvatarPlaceholderGenerator {
|
||||
fun extractLabel(content: String): String {
|
||||
val trimmedContent = content.trim()
|
||||
if (trimmedContent.isEmpty()) return EMPTY_LABEL
|
||||
return if (trimmedContent.length > 2 && trimmedContent.startsWith("05")) {
|
||||
return if (trimmedContent.length > 2 && IdPrefix.fromValue(trimmedContent) != null) {
|
||||
trimmedContent[2].toString()
|
||||
} else {
|
||||
val splitWords = trimmedContent.split(Regex("\\W"))
|
||||
|
@@ -20,4 +20,5 @@ object ContactUtilities {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user