Add basic pinned message support.

This commit is contained in:
Michelle Tang
2025-11-24 13:18:36 -05:00
committed by jeffrey-signal
parent 22701da765
commit 80598d42cc
70 changed files with 2162 additions and 89 deletions

View File

@@ -1279,6 +1279,31 @@ public class SignalServiceMessageSender {
.build());
}
if (message.getPinnedMessage().isPresent()) {
SignalServiceDataMessage.PinnedMessage pinnedMessage = message.getPinnedMessage().get();
if (Boolean.TRUE.equals(pinnedMessage.getForever())) {
builder.pinMessage(new DataMessage.PinMessage.Builder()
.targetAuthorAciBinary(pinnedMessage.getTargetAuthor().toByteString())
.targetSentTimestamp(pinnedMessage.getTargetSentTimestamp())
.pinDurationForever(true)
.build());
} else {
builder.pinMessage(new DataMessage.PinMessage.Builder()
.targetAuthorAciBinary(pinnedMessage.getTargetAuthor().toByteString())
.targetSentTimestamp(pinnedMessage.getTargetSentTimestamp())
.pinDurationSeconds(pinnedMessage.getPinDurationInSeconds())
.build());
}
}
if (message.getUnpinnedMessage().isPresent()) {
SignalServiceDataMessage.UnpinnedMessage unpinnedMessage = message.getUnpinnedMessage().get();
builder.unpinMessage(new DataMessage.UnpinMessage.Builder()
.targetAuthorAciBinary(unpinnedMessage.getTargetAuthor().toByteString())
.targetSentTimestamp(unpinnedMessage.getTargetSentTimestamp())
.build());
}
builder.timestamp(message.getTimestamp());
return builder;

View File

@@ -155,6 +155,14 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Invalid poll vote!")
}
if (dataMessage.pinMessage != null && (dataMessage.pinMessage.targetAuthorAciBinary.isNullOrInvalidAci() || dataMessage.pinMessage.targetSentTimestamp == null || (dataMessage.pinMessage.pinDurationSeconds == null && dataMessage.pinMessage.pinDurationForever == null))) {
return Result.Invalid("[DataMessage] Invalid pin message!")
}
if (dataMessage.unpinMessage != null && (dataMessage.unpinMessage.targetAuthorAciBinary.isNullOrInvalidAci() || dataMessage.unpinMessage.targetSentTimestamp == null)) {
return Result.Invalid("[DataMessage] Invalid unpin message!")
}
return Result.Valid
}

View File

@@ -53,7 +53,9 @@ class SignalServiceDataMessage private constructor(
val bodyRanges: Optional<List<BodyRange>>,
val pollCreate: Optional<PollCreate>,
val pollVote: Optional<PollVote>,
val pollTerminate: Optional<PollTerminate>
val pollTerminate: Optional<PollTerminate>,
val pinnedMessage: Optional<PinnedMessage>,
val unpinnedMessage: Optional<UnpinnedMessage>
) {
val isActivatePaymentsRequest: Boolean = payment.map { it.isActivationRequest }.orElse(false)
val isPaymentsActivated: Boolean = payment.map { it.isActivation }.orElse(false)
@@ -74,7 +76,9 @@ class SignalServiceDataMessage private constructor(
this.remoteDelete.isPresent ||
this.pollCreate.isPresent ||
this.pollVote.isPresent ||
this.pollTerminate.isPresent
this.pollTerminate.isPresent ||
this.pinnedMessage.isPresent ||
this.unpinnedMessage.isPresent
val isGroupV2Update: Boolean = groupContext.isPresent && groupContext.get().hasSignedGroupChange() && !hasRenderableContent
val isEmptyGroupV2Message: Boolean = isGroupV2Message && !isGroupV2Update && !hasRenderableContent
@@ -106,6 +110,8 @@ class SignalServiceDataMessage private constructor(
private var pollCreate: PollCreate? = null
private var pollVote: PollVote? = null
private var pollTerminate: PollTerminate? = null
private var pinnedMessage: PinnedMessage? = null
private var unpinnedMessage: UnpinnedMessage? = null
fun withTimestamp(timestamp: Long): Builder {
this.timestamp = timestamp
@@ -244,6 +250,16 @@ class SignalServiceDataMessage private constructor(
return this
}
fun withPinnedMessage(pinnedMessage: PinnedMessage?): Builder {
this.pinnedMessage = pinnedMessage
return this
}
fun withUnpinnedMessage(unpinnedMessage: UnpinnedMessage?): Builder {
this.unpinnedMessage = unpinnedMessage
return this
}
fun build(): SignalServiceDataMessage {
if (timestamp == 0L) {
timestamp = System.currentTimeMillis()
@@ -275,7 +291,9 @@ class SignalServiceDataMessage private constructor(
bodyRanges = bodyRanges.asOptional(),
pollCreate = pollCreate.asOptional(),
pollVote = pollVote.asOptional(),
pollTerminate = pollTerminate.asOptional()
pollTerminate = pollTerminate.asOptional(),
pinnedMessage = pinnedMessage.asOptional(),
unpinnedMessage = unpinnedMessage.asOptional()
)
}
}
@@ -322,6 +340,8 @@ class SignalServiceDataMessage private constructor(
data class PollCreate(val question: String, val allowMultiple: Boolean, val options: List<String>)
data class PollVote(val targetAuthor: ServiceId, val targetSentTimestamp: Long, val optionIndexes: List<Int>, val voteCount: Int)
data class PollTerminate(val targetSentTimestamp: Long)
data class PinnedMessage(val targetAuthor: ServiceId, val targetSentTimestamp: Long, val pinDurationInSeconds: Int?, val forever: Boolean?)
data class UnpinnedMessage(val targetAuthor: ServiceId, val targetSentTimestamp: Long)
companion object {
@JvmStatic

View File

@@ -335,6 +335,20 @@ message DataMessage {
optional uint32 voteCount = 4; // increment this by 1 each time you vote on a given poll
}
message PinMessage {
optional bytes targetAuthorAciBinary = 1; // 16-byte UUID
optional uint64 targetSentTimestamp = 2;
oneof pinDuration {
uint32 pinDurationSeconds = 3;
bool pinDurationForever = 4;
}
}
message UnpinMessage {
optional bytes targetAuthorAciBinary = 1; // 16-byte UUID
optional uint64 targetSentTimestamp = 2;
}
optional string body = 1;
repeated AttachmentPointer attachments = 2;
reserved /*groupV1*/ 3;
@@ -360,7 +374,9 @@ message DataMessage {
optional PollCreate pollCreate = 24;
optional PollTerminate pollTerminate = 25;
optional PollVote pollVote = 26;
// NEXT ID: 27
optional PinMessage pinMessage = 27;
optional UnpinMessage unpinMessage = 28;
// NEXT ID: 29
}
message NullMessage {