org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of signal-service-java Show documentation
Show all versions of signal-service-java Show documentation
Signal Service communication library for Java, unofficial fork
/*
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.api.messages
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
import org.whispersystems.signalservice.api.messages.shared.SharedContact
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional
import org.whispersystems.signalservice.api.util.OptionalUtil.emptyIfStringEmpty
import org.whispersystems.signalservice.internal.push.BodyRange
import java.util.LinkedList
import java.util.Optional
import org.whispersystems.signalservice.internal.push.DataMessage.Payment as PaymentProto
import org.whispersystems.signalservice.internal.push.DataMessage.Quote as QuoteProto
/**
* Represents a decrypted Signal Service data message.
*
* @param timestamp The sent timestamp.
* @param groupContext The group information (or null if none).
* @param attachments The attachments (or null if none).
* @param body The message contents.
* @param isEndSession Flag indicating whether this message should close a session.
* @param expiresInSeconds Number of seconds in which the message should disappear after being seen.
*/
class SignalServiceDataMessage private constructor(
val timestamp: Long,
val groupContext: Optional,
val attachments: Optional>,
val body: Optional,
val isEndSession: Boolean,
val expiresInSeconds: Int,
val isExpirationUpdate: Boolean,
val profileKey: Optional,
val isProfileKeyUpdate: Boolean,
val quote: Optional,
val sharedContacts: Optional>,
val previews: Optional>,
val mentions: Optional>,
val sticker: Optional,
val isViewOnce: Boolean,
val reaction: Optional,
val remoteDelete: Optional,
val groupCallUpdate: Optional,
val payment: Optional,
val storyContext: Optional,
val giftBadge: Optional,
val bodyRanges: Optional>
) {
val isActivatePaymentsRequest: Boolean = payment.map { it.isActivationRequest }.orElse(false)
val isPaymentsActivated: Boolean = payment.map { it.isActivation }.orElse(false)
val groupId: Optional = groupContext.map {
if (it.groupV1.isPresent) {
it.groupV1.get().groupId
} else {
GroupSecretParams.deriveFromMasterKey(it.groupV2.get().masterKey).publicParams.groupIdentifier.serialize()
}
}
val isGroupV2Message: Boolean = groupContext.isPresent && groupContext.get().groupV2.isPresent
/** Contains some user data that affects the conversation */
private val hasRenderableContent: Boolean =
this.attachments.isPresent ||
this.body.isPresent ||
this.quote.isPresent ||
this.sharedContacts.isPresent ||
this.previews.isPresent ||
this.mentions.isPresent ||
this.sticker.isPresent ||
this.reaction.isPresent ||
this.remoteDelete.isPresent
val isGroupV2Update: Boolean = groupContext.isPresent && groupContext.get().groupV2.isPresent && groupContext.get().groupV2.get().hasSignedGroupChange() && !hasRenderableContent
val isEmptyGroupV2Message: Boolean = isGroupV2Message && !isGroupV2Update && !hasRenderableContent
class Builder {
private var timestamp: Long = 0
private var groupV1: SignalServiceGroup? = null
private var groupV2: SignalServiceGroupV2? = null
private val attachments: MutableList = LinkedList()
private var body: String? = null
private var endSession: Boolean = false
private var expiresInSeconds: Int = 0
private var expirationUpdate: Boolean = false
private var profileKey: ByteArray? = null
private var profileKeyUpdate: Boolean = false
private var quote: Quote? = null
private val sharedContacts: MutableList = LinkedList()
private val previews: MutableList = LinkedList()
private val mentions: MutableList = LinkedList()
private var sticker: Sticker? = null
private var viewOnce: Boolean = false
private var reaction: Reaction? = null
private var remoteDelete: RemoteDelete? = null
private var groupCallUpdate: GroupCallUpdate? = null
private var payment: Payment? = null
private var storyContext: StoryContext? = null
private var giftBadge: GiftBadge? = null
private var bodyRanges: MutableList = LinkedList()
fun withTimestamp(timestamp: Long): Builder {
this.timestamp = timestamp
return this
}
fun asGroupMessage(group: SignalServiceGroup?): Builder {
groupV1 = group
return this
}
fun asGroupMessage(group: SignalServiceGroupV2?): Builder {
groupV2 = group
return this
}
fun withAttachment(attachment: SignalServiceAttachment?): Builder {
attachment?.let { attachments.add(attachment) }
return this
}
fun withAttachments(attachments: List?): Builder {
attachments?.let { this.attachments.addAll(attachments) }
return this
}
fun withBody(body: String?): Builder {
this.body = body
return this
}
@JvmOverloads
fun asEndSessionMessage(endSession: Boolean = true): Builder {
this.endSession = endSession
return this
}
@JvmOverloads
fun asExpirationUpdate(expirationUpdate: Boolean = true): Builder {
this.expirationUpdate = expirationUpdate
return this
}
fun withExpiration(expiresInSeconds: Int): Builder {
this.expiresInSeconds = expiresInSeconds
return this
}
fun withProfileKey(profileKey: ByteArray?): Builder {
this.profileKey = profileKey
return this
}
fun asProfileKeyUpdate(profileKeyUpdate: Boolean): Builder {
this.profileKeyUpdate = profileKeyUpdate
return this
}
fun withQuote(quote: Quote?): Builder {
this.quote = quote
return this
}
fun withSharedContact(contact: SharedContact?): Builder {
contact?.let { sharedContacts.add(contact) }
return this
}
fun withSharedContacts(contacts: List?): Builder {
contacts?.let { sharedContacts.addAll(contacts) }
return this
}
fun withPreviews(previews: List?): Builder {
previews?.let { this.previews.addAll(previews) }
return this
}
fun withMentions(mentions: List?): Builder {
mentions?.let { this.mentions.addAll(mentions) }
return this
}
fun withSticker(sticker: Sticker?): Builder {
this.sticker = sticker
return this
}
fun withViewOnce(viewOnce: Boolean): Builder {
this.viewOnce = viewOnce
return this
}
fun withReaction(reaction: Reaction?): Builder {
this.reaction = reaction
return this
}
fun withRemoteDelete(remoteDelete: RemoteDelete?): Builder {
this.remoteDelete = remoteDelete
return this
}
fun withGroupCallUpdate(groupCallUpdate: GroupCallUpdate?): Builder {
this.groupCallUpdate = groupCallUpdate
return this
}
fun withPayment(payment: Payment?): Builder {
this.payment = payment
return this
}
fun withStoryContext(storyContext: StoryContext?): Builder {
this.storyContext = storyContext
return this
}
fun withGiftBadge(giftBadge: GiftBadge?): Builder {
this.giftBadge = giftBadge
return this
}
fun withBodyRanges(bodyRanges: List?): Builder {
bodyRanges?.let { this.bodyRanges.addAll(bodyRanges) }
return this
}
fun build(): SignalServiceDataMessage {
if (timestamp == 0L) {
timestamp = System.currentTimeMillis()
}
return SignalServiceDataMessage(
timestamp = timestamp,
groupContext = SignalServiceGroupContext.createOptional(groupV1, groupV2),
attachments = attachments.asOptional(),
body = body.emptyIfStringEmpty(),
isEndSession = endSession,
expiresInSeconds = expiresInSeconds,
isExpirationUpdate = expirationUpdate,
profileKey = profileKey.asOptional(),
isProfileKeyUpdate = profileKeyUpdate,
quote = quote.asOptional(),
sharedContacts = sharedContacts.asOptional(),
previews = previews.asOptional(),
mentions = mentions.asOptional(),
sticker = sticker.asOptional(),
isViewOnce = viewOnce,
reaction = reaction.asOptional(),
remoteDelete = remoteDelete.asOptional(),
groupCallUpdate = groupCallUpdate.asOptional(),
payment = payment.asOptional(),
storyContext = storyContext.asOptional(),
giftBadge = giftBadge.asOptional(),
bodyRanges = bodyRanges.asOptional()
)
}
}
data class Quote(
val id: Long,
val author: ServiceId?,
val text: String,
val attachments: List?,
val mentions: List?,
val type: Type,
val bodyRanges: List?
) {
enum class Type(val protoType: QuoteProto.Type) {
NORMAL(QuoteProto.Type.NORMAL),
GIFT_BADGE(QuoteProto.Type.GIFT_BADGE);
companion object {
@JvmStatic
fun fromProto(protoType: QuoteProto.Type): Type {
return values().firstOrNull { it.protoType == protoType } ?: NORMAL
}
}
}
data class QuotedAttachment(val contentType: String, val fileName: String?, val thumbnail: SignalServiceAttachment?)
}
class Sticker(val packId: ByteArray?, val packKey: ByteArray?, val stickerId: Int, val emoji: String?, val attachment: SignalServiceAttachment?)
data class Reaction(val emoji: String, val isRemove: Boolean, val targetAuthor: ServiceId, val targetSentTimestamp: Long)
data class RemoteDelete(val targetSentTimestamp: Long)
data class Mention(val serviceId: ServiceId, val start: Int, val length: Int)
data class GroupCallUpdate(val eraId: String?)
class PaymentNotification(val receipt: ByteArray, val note: String)
data class PaymentActivation(val type: PaymentProto.Activation.Type)
class Payment(paymentNotification: PaymentNotification?, paymentActivation: PaymentActivation?) {
val paymentNotification: Optional = Optional.ofNullable(paymentNotification)
val paymentActivation: Optional = Optional.ofNullable(paymentActivation)
val isActivationRequest: Boolean = paymentActivation != null && paymentActivation.type == PaymentProto.Activation.Type.REQUEST
val isActivation: Boolean = paymentActivation != null && paymentActivation.type == PaymentProto.Activation.Type.ACTIVATED
}
data class StoryContext(val authorServiceId: ServiceId, val sentTimestamp: Long)
data class GiftBadge(val receiptCredentialPresentation: ReceiptCredentialPresentation)
companion object {
@JvmStatic
fun newBuilder(): Builder {
return Builder()
}
}
}