From 1239fae0a1f412b10c6fb5c81e595567cce80f05 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 29 Oct 2026 17:26:36 -0400 Subject: [PATCH 1/2] chore(contract): sync flipcash2 protos to 0e167fa6c01e --- .../ContractInfo.swift | 2 +- .../chat_v1_chat_service.grpc.swift | 602 +++++++++++++++- .../chat_v1_chat_service.pb.swift | 651 ++++++++++++++++++ .../chat_v1_model.pb.swift | 496 ++++++++++++- flipcash2.lock | 4 +- proto/chat/v1/chat_service.proto | 157 +++++ proto/chat/v1/model.proto | 101 +++ 7 files changed, 2006 insertions(+), 7 deletions(-) diff --git a/Sources/Flipcash2ClientProtocol/ContractInfo.swift b/Sources/Flipcash2ClientProtocol/ContractInfo.swift index 9b010dc..8d77262 100644 --- a/Sources/Flipcash2ClientProtocol/ContractInfo.swift +++ b/Sources/Flipcash2ClientProtocol/ContractInfo.swift @@ -2,7 +2,7 @@ public enum Flipcash2ContractInfo { public static let version = "0.23.0-dev" - public static let protoCommit = "6de4f9adfd110a55b56fbfc99a5a466b3da789d1" + public static let protoCommit = "0e167fa6c01edcfb5e617974fb6d2b30f9f55b98" public static var isLocal: Bool { protoCommit == localSentinel } diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift index d892f55..1cd0de6 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift @@ -380,6 +380,42 @@ public enum Flipcash_Chat_V1_Chat { method: "GetRestrictedUsers" ) } + /// Namespace for "PinMessage" metadata. + public enum PinMessage { + /// Request type for "PinMessage". + public typealias Input = Flipcash_Chat_V1_PinMessageRequest + /// Response type for "PinMessage". + public typealias Output = Flipcash_Chat_V1_PinMessageResponse + /// Descriptor for "PinMessage". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "PinMessage" + ) + } + /// Namespace for "UnpinMessage" metadata. + public enum UnpinMessage { + /// Request type for "UnpinMessage". + public typealias Input = Flipcash_Chat_V1_UnpinMessageRequest + /// Response type for "UnpinMessage". + public typealias Output = Flipcash_Chat_V1_UnpinMessageResponse + /// Descriptor for "UnpinMessage". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "UnpinMessage" + ) + } + /// Namespace for "GetPinnedMessages" metadata. + public enum GetPinnedMessages { + /// Request type for "GetPinnedMessages". + public typealias Input = Flipcash_Chat_V1_GetPinnedMessagesRequest + /// Response type for "GetPinnedMessages". + public typealias Output = Flipcash_Chat_V1_GetPinnedMessagesResponse + /// Descriptor for "GetPinnedMessages". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.chat.v1.Chat"), + method: "GetPinnedMessages" + ) + } /// Descriptors for all methods in the "flipcash.chat.v1.Chat" service. public static let descriptors: [GRPCCore.MethodDescriptor] = [ GetChat.descriptor, @@ -411,7 +447,10 @@ public enum Flipcash_Chat_V1_Chat { UnsilenceUser.descriptor, BanUser.descriptor, UnbanUser.descriptor, - GetRestrictedUsers.descriptor + GetRestrictedUsers.descriptor, + PinMessage.descriptor, + UnpinMessage.descriptor, + GetPinnedMessages.descriptor ] } } @@ -1614,6 +1653,126 @@ extension Flipcash_Chat_V1_Chat { options: GRPCCore.CallOptions, onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result ) async throws -> Result where Result: Sendable + + /// Call the "PinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > PinMessage pins a message in a chat, making it the chat's most + /// > recently pinned message (see Metadata.pin_summary). A message must be in + /// > the chat (MESSAGE_NOT_FOUND otherwise) and of a kind that may be pinned: + /// > a deleted message and a system message may not, and are refused with + /// > MESSAGE_NOT_ALLOWED, as is any kind of content this server does not + /// > know to be pinnable. Every other message may be pinned, whatever its + /// > content (text, media, a reply, a payment, a widget, encrypted content). + /// > + /// > Only a member the server permits to pin in the chat may call it, as + /// > reported by ViewerState.Permissions.can_pin; anyone else is DENIED. + /// > Pinning a message that is already pinned is a no-op that returns OK and + /// > leaves it where it is in the order. The server caps how many messages + /// > one chat may have pinned, 5 today, and refuses a pin past it with + /// > TOO_MANY_PINS. The cap is the server's and may be raised, so clients + /// > must not hardcode it: on TOO_MANY_PINS a client asks the user to unpin + /// > a message first, and GetPinnedMessages pages the pins whatever their + /// > number. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, which the + /// > client takes by version as it would any other. A client holding the + /// > chat's pins at version v may also apply its own pin to them at once: + /// > a summary at v + 1 means exactly one change was made, the pin it asked + /// > for (made by this call or an identical one racing it), so the message + /// > is now summary.latest; a summary at v means the message was already + /// > pinned and nothing changed; a summary further ahead means other changes + /// > were made too, which the client learns from the event stream or by + /// > refetching its pins with GetPinnedMessages. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_PinMessageRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_PinMessageRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_PinMessageResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func pinMessage( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "UnpinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > UnpinMessage unpins a message in a chat, whoever pinned it. Permitted + /// > exactly as PinMessage is. Unpinning a message that is not pinned is a + /// > no-op that returns OK. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, applied as + /// > for PinMessage: a summary at v + 1 means the message was unpinned, one + /// > at v that it was not pinned, and one further ahead that other changes + /// > were made too. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_UnpinMessageRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_UnpinMessageRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_UnpinMessageResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func unpinMessage( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable + + /// Call the "GetPinnedMessages" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPinnedMessages pages a chat's pinned messages, most recently pinned + /// > first, each Pin carrying its message. + /// > + /// > Allowed to whoever may read the chat's messages under view_mode, as for + /// > messaging.v1.Messaging.GetMessages, and redacted the same way; anyone + /// > else is DENIED. + /// > + /// > Pins change while they are paged. Every page carries the chat's + /// > PinSummary as of its read, and the cursor is the last pin returned, so + /// > a pin made during the walk lands ahead of it and arrives only as a + /// > MetadataUpdate.PinsChanged, and a pin removed during the walk may be + /// > absent from a later page. Clients merge pages with the event stream by + /// > PinSummary.version, as they do roster pages by RosterSummary.version. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetPinnedMessagesRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetPinnedMessagesRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetPinnedMessagesResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + func getPinnedMessages( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result + ) async throws -> Result where Result: Sendable } /// Generated client for the "flipcash.chat.v1.Chat" service. @@ -3145,6 +3304,159 @@ extension Flipcash_Chat_V1_Chat { onResponse: handleResponse ) } + + /// Call the "PinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > PinMessage pins a message in a chat, making it the chat's most + /// > recently pinned message (see Metadata.pin_summary). A message must be in + /// > the chat (MESSAGE_NOT_FOUND otherwise) and of a kind that may be pinned: + /// > a deleted message and a system message may not, and are refused with + /// > MESSAGE_NOT_ALLOWED, as is any kind of content this server does not + /// > know to be pinnable. Every other message may be pinned, whatever its + /// > content (text, media, a reply, a payment, a widget, encrypted content). + /// > + /// > Only a member the server permits to pin in the chat may call it, as + /// > reported by ViewerState.Permissions.can_pin; anyone else is DENIED. + /// > Pinning a message that is already pinned is a no-op that returns OK and + /// > leaves it where it is in the order. The server caps how many messages + /// > one chat may have pinned, 5 today, and refuses a pin past it with + /// > TOO_MANY_PINS. The cap is the server's and may be raised, so clients + /// > must not hardcode it: on TOO_MANY_PINS a client asks the user to unpin + /// > a message first, and GetPinnedMessages pages the pins whatever their + /// > number. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, which the + /// > client takes by version as it would any other. A client holding the + /// > chat's pins at version v may also apply its own pin to them at once: + /// > a summary at v + 1 means exactly one change was made, the pin it asked + /// > for (made by this call or an identical one racing it), so the message + /// > is now summary.latest; a summary at v means the message was already + /// > pinned and nothing changed; a summary further ahead means other changes + /// > were made too, which the client learns from the event stream or by + /// > refetching its pins with GetPinnedMessages. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_PinMessageRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_PinMessageRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_PinMessageResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func pinMessage( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.PinMessage.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "UnpinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > UnpinMessage unpins a message in a chat, whoever pinned it. Permitted + /// > exactly as PinMessage is. Unpinning a message that is not pinned is a + /// > no-op that returns OK. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, applied as + /// > for PinMessage: a summary at v + 1 means the message was unpinned, one + /// > at v that it was not pinned, and one further ahead that other changes + /// > were made too. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_UnpinMessageRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_UnpinMessageRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_UnpinMessageResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func unpinMessage( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.UnpinMessage.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPinnedMessages" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPinnedMessages pages a chat's pinned messages, most recently pinned + /// > first, each Pin carrying its message. + /// > + /// > Allowed to whoever may read the chat's messages under view_mode, as for + /// > messaging.v1.Messaging.GetMessages, and redacted the same way; anyone + /// > else is DENIED. + /// > + /// > Pins change while they are paged. Every page carries the chat's + /// > PinSummary as of its read, and the cursor is the last pin returned, so + /// > a pin made during the walk lands ahead of it and arrives only as a + /// > MetadataUpdate.PinsChanged, and a pin removed during the walk may be + /// > absent from a later page. Clients merge pages with the event stream by + /// > PinSummary.version, as they do roster pages by RosterSummary.version. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetPinnedMessagesRequest` message. + /// - serializer: A serializer for `Flipcash_Chat_V1_GetPinnedMessagesRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Chat_V1_GetPinnedMessagesResponse` messages. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getPinnedMessages( + request: GRPCCore.ClientRequest, + serializer: some GRPCCore.MessageSerializer, + deserializer: some GRPCCore.MessageDeserializer, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.client.unary( + request: request, + descriptor: Flipcash_Chat_V1_Chat.Method.GetPinnedMessages.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } } } @@ -4514,6 +4826,144 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { onResponse: handleResponse ) } + + /// Call the "PinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > PinMessage pins a message in a chat, making it the chat's most + /// > recently pinned message (see Metadata.pin_summary). A message must be in + /// > the chat (MESSAGE_NOT_FOUND otherwise) and of a kind that may be pinned: + /// > a deleted message and a system message may not, and are refused with + /// > MESSAGE_NOT_ALLOWED, as is any kind of content this server does not + /// > know to be pinnable. Every other message may be pinned, whatever its + /// > content (text, media, a reply, a payment, a widget, encrypted content). + /// > + /// > Only a member the server permits to pin in the chat may call it, as + /// > reported by ViewerState.Permissions.can_pin; anyone else is DENIED. + /// > Pinning a message that is already pinned is a no-op that returns OK and + /// > leaves it where it is in the order. The server caps how many messages + /// > one chat may have pinned, 5 today, and refuses a pin past it with + /// > TOO_MANY_PINS. The cap is the server's and may be raised, so clients + /// > must not hardcode it: on TOO_MANY_PINS a client asks the user to unpin + /// > a message first, and GetPinnedMessages pages the pins whatever their + /// > number. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, which the + /// > client takes by version as it would any other. A client holding the + /// > chat's pins at version v may also apply its own pin to them at once: + /// > a summary at v + 1 means exactly one change was made, the pin it asked + /// > for (made by this call or an identical one racing it), so the message + /// > is now summary.latest; a summary at v means the message was already + /// > pinned and nothing changed; a summary further ahead means other changes + /// > were made too, which the client learns from the event stream or by + /// > refetching its pins with GetPinnedMessages. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_PinMessageRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func pinMessage( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.pinMessage( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "UnpinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > UnpinMessage unpins a message in a chat, whoever pinned it. Permitted + /// > exactly as PinMessage is. Unpinning a message that is not pinned is a + /// > no-op that returns OK. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, applied as + /// > for PinMessage: a summary at v + 1 means the message was unpinned, one + /// > at v that it was not pinned, and one further ahead that other changes + /// > were made too. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_UnpinMessageRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func unpinMessage( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.unpinMessage( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPinnedMessages" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPinnedMessages pages a chat's pinned messages, most recently pinned + /// > first, each Pin carrying its message. + /// > + /// > Allowed to whoever may read the chat's messages under view_mode, as for + /// > messaging.v1.Messaging.GetMessages, and redacted the same way; anyone + /// > else is DENIED. + /// > + /// > Pins change while they are paged. Every page carries the chat's + /// > PinSummary as of its read, and the cursor is the last pin returned, so + /// > a pin made during the walk lands ahead of it and arrives only as a + /// > MetadataUpdate.PinsChanged, and a pin removed during the walk may be + /// > absent from a later page. Clients merge pages with the event stream by + /// > PinSummary.version, as they do roster pages by RosterSummary.version. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Chat_V1_GetPinnedMessagesRequest` message. + /// - options: Options to apply to this RPC. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getPinnedMessages( + request: GRPCCore.ClientRequest, + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + try await self.getPinnedMessages( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } } // Helpers providing sugared APIs for 'ClientProtocol' methods. @@ -6002,4 +6452,154 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { onResponse: handleResponse ) } + + /// Call the "PinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > PinMessage pins a message in a chat, making it the chat's most + /// > recently pinned message (see Metadata.pin_summary). A message must be in + /// > the chat (MESSAGE_NOT_FOUND otherwise) and of a kind that may be pinned: + /// > a deleted message and a system message may not, and are refused with + /// > MESSAGE_NOT_ALLOWED, as is any kind of content this server does not + /// > know to be pinnable. Every other message may be pinned, whatever its + /// > content (text, media, a reply, a payment, a widget, encrypted content). + /// > + /// > Only a member the server permits to pin in the chat may call it, as + /// > reported by ViewerState.Permissions.can_pin; anyone else is DENIED. + /// > Pinning a message that is already pinned is a no-op that returns OK and + /// > leaves it where it is in the order. The server caps how many messages + /// > one chat may have pinned, 5 today, and refuses a pin past it with + /// > TOO_MANY_PINS. The cap is the server's and may be raised, so clients + /// > must not hardcode it: on TOO_MANY_PINS a client asks the user to unpin + /// > a message first, and GetPinnedMessages pages the pins whatever their + /// > number. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, which the + /// > client takes by version as it would any other. A client holding the + /// > chat's pins at version v may also apply its own pin to them at once: + /// > a summary at v + 1 means exactly one change was made, the pin it asked + /// > for (made by this call or an identical one racing it), so the message + /// > is now summary.latest; a summary at v means the message was already + /// > pinned and nothing changed; a summary further ahead means other changes + /// > were made too, which the client learns from the event stream or by + /// > refetching its pins with GetPinnedMessages. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func pinMessage( + _ message: Flipcash_Chat_V1_PinMessageRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.pinMessage( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "UnpinMessage" method. + /// + /// > Source IDL Documentation: + /// > + /// > UnpinMessage unpins a message in a chat, whoever pinned it. Permitted + /// > exactly as PinMessage is. Unpinning a message that is not pinned is a + /// > no-op that returns OK. + /// > + /// > Every real change reaches the chat's members, including all of the + /// > caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + /// > + /// > The response carries the chat's PinSummary after the call, applied as + /// > for PinMessage: a summary at v + 1 means the message was unpinned, one + /// > at v that it was not pinned, and one further ahead that other changes + /// > were made too. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func unpinMessage( + _ message: Flipcash_Chat_V1_UnpinMessageRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.unpinMessage( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPinnedMessages" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPinnedMessages pages a chat's pinned messages, most recently pinned + /// > first, each Pin carrying its message. + /// > + /// > Allowed to whoever may read the chat's messages under view_mode, as for + /// > messaging.v1.Messaging.GetMessages, and redacted the same way; anyone + /// > else is DENIED. + /// > + /// > Pins change while they are paged. Every page carries the chat's + /// > PinSummary as of its read, and the cursor is the last pin returned, so + /// > a pin made during the walk lands ahead of it and arrives only as a + /// > MetadataUpdate.PinsChanged, and a pin removed during the walk may be + /// > absent from a later page. Clients merge pages with the event stream by + /// > PinSummary.version, as they do roster pages by RosterSummary.version. + /// + /// - Parameters: + /// - message: request message to send. + /// - metadata: Additional metadata to send, defaults to empty. + /// - options: Options to apply to this RPC, defaults to `.defaults`. + /// - handleResponse: A closure which handles the response, the result of which is + /// returned to the caller. Returning from the closure will cancel the RPC if it + /// hasn't already finished. + /// - Returns: The result of `handleResponse`. + public func getPinnedMessages( + _ message: Flipcash_Chat_V1_GetPinnedMessagesRequest, + metadata: GRPCCore.Metadata = [:], + options: GRPCCore.CallOptions = .defaults, + onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse) async throws -> Result = { response in + try response.message + } + ) async throws -> Result where Result: Sendable { + let request = GRPCCore.ClientRequest( + message: message, + metadata: metadata + ) + return try await self.getPinnedMessages( + request: request, + options: options, + onResponse: handleResponse + ) + } } \ No newline at end of file diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift index 2618840..196faff 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift @@ -3749,6 +3749,376 @@ public struct Flipcash_Chat_V1_GetRestrictedUsersResponse: Sendable { fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = nil } +public struct Flipcash_Chat_V1_PinMessageRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + public var messageID: Flipcash_Messaging_V1_MessageId { + get {return _messageID ?? Flipcash_Messaging_V1_MessageId()} + set {_messageID = newValue} + } + /// Returns true if `messageID` has been explicitly set. + public var hasMessageID: Bool {return self._messageID != nil} + /// Clears the value of `messageID`. Subsequent reads from it will return its default value. + public mutating func clearMessageID() {self._messageID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _messageID: Flipcash_Messaging_V1_MessageId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_PinMessageResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_PinMessageResponse.Result = .ok + + /// The chat's pin summary after the call (see PinMessage for how the + /// client applies it). Set only when result == OK. + public var pinSummary: Flipcash_Chat_V1_PinSummary { + get {return _pinSummary ?? Flipcash_Chat_V1_PinSummary()} + set {_pinSummary = newValue} + } + /// Returns true if `pinSummary` has been explicitly set. + public var hasPinSummary: Bool {return self._pinSummary != nil} + /// Clears the value of `pinSummary`. Subsequent reads from it will return its default value. + public mutating func clearPinSummary() {self._pinSummary = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller may not pin in this chat (see ViewerState.Permissions.can_pin) + case denied // = 1 + case notFound // = 2 + + /// The message is not in this chat + case messageNotFound // = 3 + + /// The chat has as many pinned messages as the server allows + case tooManyPins // = 4 + + /// The message may not be pinned: it is deleted, or a system message (see PinMessage) + case messageNotAllowed // = 5 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + case 3: self = .messageNotFound + case 4: self = .tooManyPins + case 5: self = .messageNotAllowed + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .messageNotFound: return 3 + case .tooManyPins: return 4 + case .messageNotAllowed: return 5 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_PinMessageResponse.Result] = [ + .ok, + .denied, + .notFound, + .messageNotFound, + .tooManyPins, + .messageNotAllowed, + ] + + } + + public init() {} + + fileprivate var _pinSummary: Flipcash_Chat_V1_PinSummary? = nil +} + +public struct Flipcash_Chat_V1_UnpinMessageRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + public var messageID: Flipcash_Messaging_V1_MessageId { + get {return _messageID ?? Flipcash_Messaging_V1_MessageId()} + set {_messageID = newValue} + } + /// Returns true if `messageID` has been explicitly set. + public var hasMessageID: Bool {return self._messageID != nil} + /// Clears the value of `messageID`. Subsequent reads from it will return its default value. + public mutating func clearMessageID() {self._messageID = nil} + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _messageID: Flipcash_Messaging_V1_MessageId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_UnpinMessageResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_UnpinMessageResponse.Result = .ok + + /// The chat's pin summary after the call (see UnpinMessage for how the + /// client applies it). Set only when result == OK. + public var pinSummary: Flipcash_Chat_V1_PinSummary { + get {return _pinSummary ?? Flipcash_Chat_V1_PinSummary()} + set {_pinSummary = newValue} + } + /// Returns true if `pinSummary` has been explicitly set. + public var hasPinSummary: Bool {return self._pinSummary != nil} + /// Clears the value of `pinSummary`. Subsequent reads from it will return its default value. + public mutating func clearPinSummary() {self._pinSummary = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller may not pin in this chat (see ViewerState.Permissions.can_pin) + case denied // = 1 + case notFound // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_UnpinMessageResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} + + fileprivate var _pinSummary: Flipcash_Chat_V1_PinSummary? = nil +} + +public struct Flipcash_Chat_V1_GetPinnedMessagesRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Flipcash_Common_V1_ChatId { + get {return _chatID ?? Flipcash_Common_V1_ChatId()} + set {_chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return self._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {self._chatID = nil} + + /// QueryOptions controls page_size, capped at 100. Ordering is fixed to + /// most recently pinned first and is not client-selectable. + /// + /// Leave query_options.paging_token unset on the first request. On every + /// subsequent request, set it to the paging_token from the most recent + /// response. The token is opaque and server-generated; do not construct + /// it. It is bound to chat_id and refused with another chat. + public var queryOptions: Flipcash_Common_V1_QueryOptions { + get {return _queryOptions ?? Flipcash_Common_V1_QueryOptions()} + set {_queryOptions = newValue} + } + /// Returns true if `queryOptions` has been explicitly set. + public var hasQueryOptions: Bool {return self._queryOptions != nil} + /// Clears the value of `queryOptions`. Subsequent reads from it will return its default value. + public mutating func clearQueryOptions() {self._queryOptions = nil} + + /// What the client intends to render of the pinned messages, and so + /// whether they may be returned redacted (see messaging.v1.ViewMode). + public var viewMode: Flipcash_Messaging_V1_ViewMode = .full + + public var auth: Flipcash_Common_V1_Auth { + get {return _auth ?? Flipcash_Common_V1_Auth()} + set {_auth = newValue} + } + /// Returns true if `auth` has been explicitly set. + public var hasAuth: Bool {return self._auth != nil} + /// Clears the value of `auth`. Subsequent reads from it will return its default value. + public mutating func clearAuth() {self._auth = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil + fileprivate var _queryOptions: Flipcash_Common_V1_QueryOptions? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Chat_V1_GetPinnedMessagesResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var result: Flipcash_Chat_V1_GetPinnedMessagesResponse.Result = .ok + + /// The page, most recently pinned first, each with its message. Set when + /// result is OK. + public var pins: [Flipcash_Chat_V1_Pin] = [] + + /// The chat's pin summary as of this page's read. Set when result is OK. + /// Applied by version as described on PinSummary; see GetPinnedMessages + /// for how the page relates to it. + public var pinSummary: Flipcash_Chat_V1_PinSummary { + get {return _pinSummary ?? Flipcash_Chat_V1_PinSummary()} + set {_pinSummary = newValue} + } + /// Returns true if `pinSummary` has been explicitly set. + public var hasPinSummary: Bool {return self._pinSummary != nil} + /// Clears the value of `pinSummary`. Subsequent reads from it will return its default value. + public mutating func clearPinSummary() {self._pinSummary = nil} + + /// PagingToken carries the cursor after the last pin returned. The client + /// MUST send it back in query_options.paging_token on the next + /// GetPinnedMessagesRequest. Set when result is OK and has_more is true. + public var pagingToken: Flipcash_Common_V1_PagingToken { + get {return _pagingToken ?? Flipcash_Common_V1_PagingToken()} + set {_pagingToken = newValue} + } + /// Returns true if `pagingToken` has been explicitly set. + public var hasPagingToken: Bool {return self._pagingToken != nil} + /// Clears the value of `pagingToken`. Subsequent reads from it will return its default value. + public mutating func clearPagingToken() {self._pagingToken = nil} + + /// HasMore indicates whether further pages remain. When true, the client + /// should issue another GetPinnedMessagesRequest with the returned + /// paging_token. + public var hasMore_p: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// Caller may not read the chat under view_mode + case denied // = 1 + case notFound // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .notFound + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Chat_V1_GetPinnedMessagesResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} + + fileprivate var _pinSummary: Flipcash_Chat_V1_PinSummary? = nil + fileprivate var _pagingToken: Flipcash_Common_V1_PagingToken? = nil +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "flipcash.chat.v1" @@ -6738,3 +7108,284 @@ extension Flipcash_Chat_V1_GetRestrictedUsersResponse: SwiftProtobuf.Message, Sw extension Flipcash_Chat_V1_GetRestrictedUsersResponse.Result: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") } + +extension Flipcash_Chat_V1_PinMessageRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PinMessageRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}message_id\0\u{2}\u{8}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._messageID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._messageID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_PinMessageRequest, rhs: Flipcash_Chat_V1_PinMessageRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._messageID != rhs._messageID {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_PinMessageResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PinMessageResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}pin_summary\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._pinSummary) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + try { if let v = self._pinSummary { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_PinMessageResponse, rhs: Flipcash_Chat_V1_PinMessageResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._pinSummary != rhs._pinSummary {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_PinMessageResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}MESSAGE_NOT_FOUND\0\u{1}TOO_MANY_PINS\0\u{1}MESSAGE_NOT_ALLOWED\0") +} + +extension Flipcash_Chat_V1_UnpinMessageRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".UnpinMessageRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}message_id\0\u{2}\u{8}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._messageID) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._messageID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_UnpinMessageRequest, rhs: Flipcash_Chat_V1_UnpinMessageRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._messageID != rhs._messageID {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_UnpinMessageResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".UnpinMessageResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}pin_summary\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._pinSummary) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + try { if let v = self._pinSummary { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_UnpinMessageResponse, rhs: Flipcash_Chat_V1_UnpinMessageResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._pinSummary != rhs._pinSummary {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_UnpinMessageResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} + +extension Flipcash_Chat_V1_GetPinnedMessagesRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetPinnedMessagesRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}query_options\0\u{3}view_mode\0\u{2}\u{7}auth\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._queryOptions) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.viewMode) }() + case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._queryOptions { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if self.viewMode != .full { + try visitor.visitSingularEnumField(value: self.viewMode, fieldNumber: 3) + } + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetPinnedMessagesRequest, rhs: Flipcash_Chat_V1_GetPinnedMessagesRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._queryOptions != rhs._queryOptions {return false} + if lhs.viewMode != rhs.viewMode {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetPinnedMessagesResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetPinnedMessagesResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}pins\0\u{3}pin_summary\0\u{3}paging_token\0\u{3}has_more\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.pins) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._pinSummary) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._pagingToken) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.hasMore_p) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if !self.pins.isEmpty { + try visitor.visitRepeatedMessageField(value: self.pins, fieldNumber: 2) + } + try { if let v = self._pinSummary { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = self._pagingToken { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + if self.hasMore_p != false { + try visitor.visitSingularBoolField(value: self.hasMore_p, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_GetPinnedMessagesResponse, rhs: Flipcash_Chat_V1_GetPinnedMessagesResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.pins != rhs.pins {return false} + if lhs._pinSummary != rhs._pinSummary {return false} + if lhs._pagingToken != rhs._pagingToken {return false} + if lhs.hasMore_p != rhs.hasMore_p {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_GetPinnedMessagesResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0") +} diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift index 413cb90..4411ae5 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift @@ -294,6 +294,28 @@ public struct Flipcash_Chat_V1_Metadata: @unchecked Sendable { /// Clears the value of `holdingsSummary`. Subsequent reads from it will return its default value. public mutating func clearHoldingsSummary() {_uniqueStorage()._holdingsSummary = nil} + /// The chat's pinned messages, summarized: how many there are and the + /// most recently pinned one, with its message, for a client to show + /// above the conversation. Chat.GetPinnedMessages pages them all. + /// + /// Messaging state, like last_message: withheld from a viewer who may not + /// read the chat, and the pinned message may be redacted (see + /// messaging.v1.Message.redacted) under the same view mode. Otherwise + /// always set, with count 0 and no latest when nothing is pinned. + /// + /// It may be unset on a chat returned by an RPC meant for list views + /// (e.g. Chat.GetDmChatFeed, Chat.GetGroupChatFeed), so an unset value + /// there does not mean nothing is pinned. Fetch it with Chat.GetChat, and + /// don't let a list result clear a summary the client already holds. + public var pinSummary: Flipcash_Chat_V1_PinSummary { + get {return _storage._pinSummary ?? Flipcash_Chat_V1_PinSummary()} + set {_uniqueStorage()._pinSummary = newValue} + } + /// Returns true if `pinSummary` has been explicitly set. + public var hasPinSummary: Bool {return _storage._pinSummary != nil} + /// Clears the value of `pinSummary`. Subsequent reads from it will return its default value. + public mutating func clearPinSummary() {_uniqueStorage()._pinSummary = nil} + /// Whether messages in this chat are end-to-end encrypted (see /// messaging.v1.EncryptedContent). Only supported for DMs (CONTACT_DM or DM); /// always false for group chats. A private group is always end-to-end @@ -751,6 +773,124 @@ public struct Flipcash_Chat_V1_HoldingsSummary: Sendable { fileprivate var _asOf: SwiftProtobuf.Google_Protobuf_Timestamp? = nil } +/// Pin is one pinned message in a chat (see Chat.PinMessage). A chat's pins +/// are ordered most recently pinned first; pinning a message that is already +/// pinned does not move it. +public struct Flipcash_Chat_V1_Pin: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The pinned message. + public var messageID: Flipcash_Messaging_V1_MessageId { + get {return _messageID ?? Flipcash_Messaging_V1_MessageId()} + set {_messageID = newValue} + } + /// Returns true if `messageID` has been explicitly set. + public var hasMessageID: Bool {return self._messageID != nil} + /// Clears the value of `messageID`. Subsequent reads from it will return its default value. + public mutating func clearMessageID() {self._messageID = nil} + + /// The user who pinned it. + public var pinnedBy: Flipcash_Common_V1_UserId { + get {return _pinnedBy ?? Flipcash_Common_V1_UserId()} + set {_pinnedBy = newValue} + } + /// Returns true if `pinnedBy` has been explicitly set. + public var hasPinnedBy: Bool {return self._pinnedBy != nil} + /// Clears the value of `pinnedBy`. Subsequent reads from it will return its default value. + public mutating func clearPinnedBy() {self._pinnedBy = nil} + + /// When it was pinned. + public var pinnedAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _pinnedAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_pinnedAt = newValue} + } + /// Returns true if `pinnedAt` has been explicitly set. + public var hasPinnedAt: Bool {return self._pinnedAt != nil} + /// Clears the value of `pinnedAt`. Subsequent reads from it will return its default value. + public mutating func clearPinnedAt() {self._pinnedAt = nil} + + /// The pinned message itself, as the viewer may read it: redacted (see + /// messaging.v1.Message.redacted) when the read that returned it was + /// redacted. Always set. + /// + /// A snapshot as of the read or event that carried it. Editing or + /// deleting a pinned message does not move PinSummary.version and + /// publishes no MetadataUpdate.PinsChanged; the change arrives on the + /// chat's event log like any other (see messaging.v1.Messaging.GetDelta), + /// and clients apply it to the copies they hold in their pins. + /// + /// A message deleted after it was pinned stays pinned, carrying its + /// tombstone, until someone permitted to pin unpins it, and it still + /// counts toward the cap on a chat's pins. Clients should not show such a + /// pin. When PinSummary.latest is one, a client that shows the next pin + /// fetches it with Chat.GetPinnedMessages. + public var message: Flipcash_Messaging_V1_Message { + get {return _message ?? Flipcash_Messaging_V1_Message()} + set {_message = newValue} + } + /// Returns true if `message` has been explicitly set. + public var hasMessage: Bool {return self._message != nil} + /// Clears the value of `message`. Subsequent reads from it will return its default value. + public mutating func clearMessage() {self._message = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _messageID: Flipcash_Messaging_V1_MessageId? = nil + fileprivate var _pinnedBy: Flipcash_Common_V1_UserId? = nil + fileprivate var _pinnedAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _message: Flipcash_Messaging_V1_Message? = nil +} + +/// PinSummary is the state of a chat's pinned messages that is shown with the +/// chat (see Metadata.pin_summary). +public struct Flipcash_Chat_V1_PinSummary: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The most recently pinned message. Unset when nothing is pinned. + public var latest: Flipcash_Chat_V1_Pin { + get {return _storage._latest ?? Flipcash_Chat_V1_Pin()} + set {_uniqueStorage()._latest = newValue} + } + /// Returns true if `latest` has been explicitly set. + public var hasLatest: Bool {return _storage._latest != nil} + /// Clears the value of `latest`. Subsequent reads from it will return its default value. + public mutating func clearLatest() {_uniqueStorage()._latest = nil} + + /// The number of pinned messages. The server caps how many messages a + /// chat may have pinned, 5 today (see Chat.PinMessage). + public var count: UInt32 { + get {return _storage._count} + set {_uniqueStorage()._count = newValue} + } + + /// Version of the chat's pins: advanced by exactly one on every pin and + /// unpin, never by a no-op, and 0 for a chat that has never had a pin. + /// A pinned message being edited or deleted does not move it (see + /// Pin.message). + /// + /// Compared like RosterSummary.version: apply the greater value and drop + /// the rest, so delivery order does not matter. It is not the chat event + /// sequence and has no delta to fetch against it; a client that finds + /// itself behind refetches its pins with Chat.GetPinnedMessages (see + /// MetadataUpdate.PinsChanged). + public var version: UInt64 { + get {return _storage._version} + set {_uniqueStorage()._version = newValue} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -822,6 +962,14 @@ public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { set {kind = .rulesChanged(newValue)} } + public var pinsChanged: Flipcash_Chat_V1_MetadataUpdate.PinsChanged { + get { + if case .pinsChanged(let v)? = kind {return v} + return Flipcash_Chat_V1_MetadataUpdate.PinsChanged() + } + set {kind = .pinsChanged(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Kind: Equatable, Sendable { @@ -833,6 +981,7 @@ public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { case descriptionChanged(Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged) case coverPictureChanged(Flipcash_Chat_V1_MetadataUpdate.CoverPictureChanged) case rulesChanged(Flipcash_Chat_V1_MetadataUpdate.RulesChanged) + case pinsChanged(Flipcash_Chat_V1_MetadataUpdate.PinsChanged) } @@ -1017,6 +1166,70 @@ public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { fileprivate var _newRules: Flipcash_Chat_V1_Rules? = nil } + /// A message was pinned or unpinned (see Chat.PinMessage and + /// Chat.UnpinMessage). Delivered to the chat's members, including the + /// caller's other devices, and to a chat preview, where the pinned + /// message is redacted as the preview's messages are. Best-effort. + /// + /// A client holding the chat's pins at summary.version - 1 applies change + /// to them and takes summary. A client further behind may have missed a + /// change, so it takes summary and refetches its pins with + /// Chat.GetPinnedMessages. A client at summary.version or later has + /// already seen it and drops it. + public struct PinsChanged: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var change: OneOf_Change? { + get {return _storage._change} + set {_uniqueStorage()._change = newValue} + } + + /// The pin made, now the chat's most recently pinned message. + public var pinned: Flipcash_Chat_V1_Pin { + get { + if case .pinned(let v)? = _storage._change {return v} + return Flipcash_Chat_V1_Pin() + } + set {_uniqueStorage()._change = .pinned(newValue)} + } + + /// The message unpinned. + public var unpinned: Flipcash_Messaging_V1_MessageId { + get { + if case .unpinned(let v)? = _storage._change {return v} + return Flipcash_Messaging_V1_MessageId() + } + set {_uniqueStorage()._change = .unpinned(newValue)} + } + + /// The chat's pin summary after the change, replacing + /// Metadata.pin_summary. + public var summary: Flipcash_Chat_V1_PinSummary { + get {return _storage._summary ?? Flipcash_Chat_V1_PinSummary()} + set {_uniqueStorage()._summary = newValue} + } + /// Returns true if `summary` has been explicitly set. + public var hasSummary: Bool {return _storage._summary != nil} + /// Clears the value of `summary`. Subsequent reads from it will return its default value. + public mutating func clearSummary() {_uniqueStorage()._summary = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Change: Equatable, Sendable { + /// The pin made, now the chat's most recently pinned message. + case pinned(Flipcash_Chat_V1_Pin) + /// The message unpinned. + case unpinned(Flipcash_Messaging_V1_MessageId) + + } + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance + } + public init() {} } @@ -1301,6 +1514,11 @@ public struct Flipcash_Chat_V1_ViewerState: Sendable { /// Invite). public var canInvite: Bool = false + /// Whether the viewer may call Chat.PinMessage and Chat.UnpinMessage + /// on this chat. Today that is either member of a DM, and a group's + /// creator while a member: false for every other group member. + public var canPin: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -2417,7 +2635,7 @@ extension Flipcash_Chat_V1_ChatType: SwiftProtobuf._ProtoNameProviding { extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Metadata" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}type\0\u{1}members\0\u{3}last_message\0\u{3}last_activity\0\u{3}latest_event_sequence\0\u{3}is_hidden\0\u{1}title\0\u{3}profile_picture\0\u{3}roster_summary\0\u{1}rules\0\u{3}viewer_state\0\u{1}creator\0\u{3}is_private\0\u{3}in_lobby\0\u{1}description\0\u{3}cover_picture\0\u{3}holdings_summary\0\u{4}R\u{1}use_e2ee\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}type\0\u{1}members\0\u{3}last_message\0\u{3}last_activity\0\u{3}latest_event_sequence\0\u{3}is_hidden\0\u{1}title\0\u{3}profile_picture\0\u{3}roster_summary\0\u{1}rules\0\u{3}viewer_state\0\u{1}creator\0\u{3}is_private\0\u{3}in_lobby\0\u{1}description\0\u{3}cover_picture\0\u{3}holdings_summary\0\u{3}pin_summary\0\u{4}Q\u{1}use_e2ee\0") fileprivate class _StorageClass { var _chatID: Flipcash_Common_V1_ChatId? = nil @@ -2438,6 +2656,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa var _description_p: String = String() var _coverPicture: Flipcash_Blob_V1_Media? = nil var _holdingsSummary: Flipcash_Chat_V1_HoldingsSummary? = nil + var _pinSummary: Flipcash_Chat_V1_PinSummary? = nil var _useE2Ee: Bool = false // This property is used as the initial default value for new instances of the type. @@ -2467,6 +2686,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa _description_p = source._description_p _coverPicture = source._coverPicture _holdingsSummary = source._holdingsSummary + _pinSummary = source._pinSummary _useE2Ee = source._useE2Ee } } @@ -2504,6 +2724,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa case 16: try { try decoder.decodeSingularStringField(value: &_storage._description_p) }() case 17: try { try decoder.decodeSingularMessageField(value: &_storage._coverPicture) }() case 18: try { try decoder.decodeSingularMessageField(value: &_storage._holdingsSummary) }() + case 19: try { try decoder.decodeSingularMessageField(value: &_storage._pinSummary) }() case 100: try { try decoder.decodeSingularBoolField(value: &_storage._useE2Ee) }() default: break } @@ -2571,6 +2792,9 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa try { if let v = _storage._holdingsSummary { try visitor.visitSingularMessageField(value: v, fieldNumber: 18) } }() + try { if let v = _storage._pinSummary { + try visitor.visitSingularMessageField(value: v, fieldNumber: 19) + } }() if _storage._useE2Ee != false { try visitor.visitSingularBoolField(value: _storage._useE2Ee, fieldNumber: 100) } @@ -2601,6 +2825,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa if _storage._description_p != rhs_storage._description_p {return false} if _storage._coverPicture != rhs_storage._coverPicture {return false} if _storage._holdingsSummary != rhs_storage._holdingsSummary {return false} + if _storage._pinSummary != rhs_storage._pinSummary {return false} if _storage._useE2Ee != rhs_storage._useE2Ee {return false} return true } @@ -3253,9 +3478,142 @@ extension Flipcash_Chat_V1_HoldingsSummary: SwiftProtobuf.Message, SwiftProtobuf } } +extension Flipcash_Chat_V1_Pin: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Pin" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}message_id\0\u{3}pinned_by\0\u{3}pinned_at\0\u{1}message\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._messageID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._pinnedBy) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._pinnedAt) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._message) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._messageID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._pinnedBy { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._pinnedAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = self._message { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_Pin, rhs: Flipcash_Chat_V1_Pin) -> Bool { + if lhs._messageID != rhs._messageID {return false} + if lhs._pinnedBy != rhs._pinnedBy {return false} + if lhs._pinnedAt != rhs._pinnedAt {return false} + if lhs._message != rhs._message {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Chat_V1_PinSummary: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PinSummary" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}latest\0\u{1}count\0\u{1}version\0") + + fileprivate class _StorageClass { + var _latest: Flipcash_Chat_V1_Pin? = nil + var _count: UInt32 = 0 + var _version: UInt64 = 0 + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _latest = source._latest + _count = source._count + _version = source._version + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &_storage._latest) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &_storage._count) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &_storage._version) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = _storage._latest { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if _storage._count != 0 { + try visitor.visitSingularUInt32Field(value: _storage._count, fieldNumber: 2) + } + if _storage._version != 0 { + try visitor.visitSingularUInt64Field(value: _storage._version, fieldNumber: 3) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_PinSummary, rhs: Flipcash_Chat_V1_PinSummary) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._latest != rhs_storage._latest {return false} + if _storage._count != rhs_storage._count {return false} + if _storage._version != rhs_storage._version {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Flipcash_Chat_V1_MetadataUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".MetadataUpdate" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}full_refresh\0\u{3}last_activity_changed\0\u{3}viewer_state_changed\0\u{3}title_changed\0\u{3}profile_picture_changed\0\u{3}description_changed\0\u{3}cover_picture_changed\0\u{3}rules_changed\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}full_refresh\0\u{3}last_activity_changed\0\u{3}viewer_state_changed\0\u{3}title_changed\0\u{3}profile_picture_changed\0\u{3}description_changed\0\u{3}cover_picture_changed\0\u{3}rules_changed\0\u{3}pins_changed\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3367,6 +3725,19 @@ extension Flipcash_Chat_V1_MetadataUpdate: SwiftProtobuf.Message, SwiftProtobuf. self.kind = .rulesChanged(v) } }() + case 9: try { + var v: Flipcash_Chat_V1_MetadataUpdate.PinsChanged? + var hadOneofValue = false + if let current = self.kind { + hadOneofValue = true + if case .pinsChanged(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.kind = .pinsChanged(v) + } + }() default: break } } @@ -3410,6 +3781,10 @@ extension Flipcash_Chat_V1_MetadataUpdate: SwiftProtobuf.Message, SwiftProtobuf. guard case .rulesChanged(let v)? = self.kind else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 8) }() + case .pinsChanged?: try { + guard case .pinsChanged(let v)? = self.kind else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -3686,6 +4061,116 @@ extension Flipcash_Chat_V1_MetadataUpdate.RulesChanged: SwiftProtobuf.Message, S } } +extension Flipcash_Chat_V1_MetadataUpdate.PinsChanged: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_MetadataUpdate.protoMessageName + ".PinsChanged" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}pinned\0\u{1}unpinned\0\u{1}summary\0") + + fileprivate class _StorageClass { + var _change: Flipcash_Chat_V1_MetadataUpdate.PinsChanged.OneOf_Change? + var _summary: Flipcash_Chat_V1_PinSummary? = nil + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _change = source._change + _summary = source._summary + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { + var v: Flipcash_Chat_V1_Pin? + var hadOneofValue = false + if let current = _storage._change { + hadOneofValue = true + if case .pinned(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._change = .pinned(v) + } + }() + case 2: try { + var v: Flipcash_Messaging_V1_MessageId? + var hadOneofValue = false + if let current = _storage._change { + hadOneofValue = true + if case .unpinned(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._change = .unpinned(v) + } + }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._summary) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch _storage._change { + case .pinned?: try { + guard case .pinned(let v)? = _storage._change else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .unpinned?: try { + guard case .unpinned(let v)? = _storage._change else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + try { if let v = _storage._summary { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_MetadataUpdate.PinsChanged, rhs: Flipcash_Chat_V1_MetadataUpdate.PinsChanged) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._change != rhs_storage._change {return false} + if _storage._summary != rhs_storage._summary {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Flipcash_Chat_V1_RosterUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".RosterUpdate" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}member_joined\0\u{3}member_left\0\u{3}membership_changed\0\u{4}\u{7}roster_summary\0") @@ -3982,7 +4467,7 @@ extension Flipcash_Chat_V1_ViewerState.Settings: SwiftProtobuf.Message, SwiftPro extension Flipcash_Chat_V1_ViewerState.Permissions: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Flipcash_Chat_V1_ViewerState.protoMessageName + ".Permissions" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}can_edit\0\u{1}moderation\0\u{3}can_invite\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}can_edit\0\u{1}moderation\0\u{3}can_invite\0\u{3}can_pin\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3993,6 +4478,7 @@ extension Flipcash_Chat_V1_ViewerState.Permissions: SwiftProtobuf.Message, Swift case 1: try { try decoder.decodeSingularBoolField(value: &self.canEdit) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._moderation) }() case 3: try { try decoder.decodeSingularBoolField(value: &self.canInvite) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.canPin) }() default: break } } @@ -4012,6 +4498,9 @@ extension Flipcash_Chat_V1_ViewerState.Permissions: SwiftProtobuf.Message, Swift if self.canInvite != false { try visitor.visitSingularBoolField(value: self.canInvite, fieldNumber: 3) } + if self.canPin != false { + try visitor.visitSingularBoolField(value: self.canPin, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } @@ -4019,6 +4508,7 @@ extension Flipcash_Chat_V1_ViewerState.Permissions: SwiftProtobuf.Message, Swift if lhs.canEdit != rhs.canEdit {return false} if lhs._moderation != rhs._moderation {return false} if lhs.canInvite != rhs.canInvite {return false} + if lhs.canPin != rhs.canPin {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/flipcash2.lock b/flipcash2.lock index 748ea88..65f3e0a 100644 --- a/flipcash2.lock +++ b/flipcash2.lock @@ -1,4 +1,4 @@ # Pinned upstream contract. Regenerate with scripts/sync-protos.sh . upstream: code-payments/flipcash2-protobuf-api -commit: 6de4f9adfd110a55b56fbfc99a5a466b3da789d1 -subject: Add SendTestPush RPC (#163) +commit: 0e167fa6c01edcfb5e617974fb6d2b30f9f55b98 +subject: Add support for pinning messages in a chat (#164) diff --git a/proto/chat/v1/chat_service.proto b/proto/chat/v1/chat_service.proto index 0aa95c9..048958c 100644 --- a/proto/chat/v1/chat_service.proto +++ b/proto/chat/v1/chat_service.proto @@ -598,6 +598,66 @@ service Chat { // briefly, so a user just restricted may be absent and one just // released may be present. rpc GetRestrictedUsers(GetRestrictedUsersRequest) returns (GetRestrictedUsersResponse); + + // PinMessage pins a message in a chat, making it the chat's most + // recently pinned message (see Metadata.pin_summary). A message must be in + // the chat (MESSAGE_NOT_FOUND otherwise) and of a kind that may be pinned: + // a deleted message and a system message may not, and are refused with + // MESSAGE_NOT_ALLOWED, as is any kind of content this server does not + // know to be pinnable. Every other message may be pinned, whatever its + // content (text, media, a reply, a payment, a widget, encrypted content). + // + // Only a member the server permits to pin in the chat may call it, as + // reported by ViewerState.Permissions.can_pin; anyone else is DENIED. + // Pinning a message that is already pinned is a no-op that returns OK and + // leaves it where it is in the order. The server caps how many messages + // one chat may have pinned, 5 today, and refuses a pin past it with + // TOO_MANY_PINS. The cap is the server's and may be raised, so clients + // must not hardcode it: on TOO_MANY_PINS a client asks the user to unpin + // a message first, and GetPinnedMessages pages the pins whatever their + // number. + // + // Every real change reaches the chat's members, including all of the + // caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + // + // The response carries the chat's PinSummary after the call, which the + // client takes by version as it would any other. A client holding the + // chat's pins at version v may also apply its own pin to them at once: + // a summary at v + 1 means exactly one change was made, the pin it asked + // for (made by this call or an identical one racing it), so the message + // is now summary.latest; a summary at v means the message was already + // pinned and nothing changed; a summary further ahead means other changes + // were made too, which the client learns from the event stream or by + // refetching its pins with GetPinnedMessages. + rpc PinMessage(PinMessageRequest) returns (PinMessageResponse); + + // UnpinMessage unpins a message in a chat, whoever pinned it. Permitted + // exactly as PinMessage is. Unpinning a message that is not pinned is a + // no-op that returns OK. + // + // Every real change reaches the chat's members, including all of the + // caller's devices, on the event stream as a MetadataUpdate.PinsChanged. + // + // The response carries the chat's PinSummary after the call, applied as + // for PinMessage: a summary at v + 1 means the message was unpinned, one + // at v that it was not pinned, and one further ahead that other changes + // were made too. + rpc UnpinMessage(UnpinMessageRequest) returns (UnpinMessageResponse); + + // GetPinnedMessages pages a chat's pinned messages, most recently pinned + // first, each Pin carrying its message. + // + // Allowed to whoever may read the chat's messages under view_mode, as for + // messaging.v1.Messaging.GetMessages, and redacted the same way; anyone + // else is DENIED. + // + // Pins change while they are paged. Every page carries the chat's + // PinSummary as of its read, and the cursor is the last pin returned, so + // a pin made during the walk lands ahead of it and arrives only as a + // MetadataUpdate.PinsChanged, and a pin removed during the walk may be + // absent from a later page. Clients merge pages with the event stream by + // PinSummary.version, as they do roster pages by RosterSummary.version. + rpc GetPinnedMessages(GetPinnedMessagesRequest) returns (GetPinnedMessagesResponse); } message GetChatRequest { @@ -1662,3 +1722,100 @@ message GetRestrictedUsersResponse { // paging_token. bool has_more = 4; } + +message PinMessageRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + messaging.v1.MessageId message_id = 2 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message PinMessageResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller may not pin in this chat (see ViewerState.Permissions.can_pin) + NOT_FOUND = 2; + MESSAGE_NOT_FOUND = 3; // The message is not in this chat + TOO_MANY_PINS = 4; // The chat has as many pinned messages as the server allows + MESSAGE_NOT_ALLOWED = 5; // The message may not be pinned: it is deleted, or a system message (see PinMessage) + } + + // The chat's pin summary after the call (see PinMessage for how the + // client applies it). Set only when result == OK. + PinSummary pin_summary = 2; +} + +message UnpinMessageRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + messaging.v1.MessageId message_id = 2 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message UnpinMessageResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller may not pin in this chat (see ViewerState.Permissions.can_pin) + NOT_FOUND = 2; + } + + // The chat's pin summary after the call (see UnpinMessage for how the + // client applies it). Set only when result == OK. + PinSummary pin_summary = 2; +} + +message GetPinnedMessagesRequest { + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + // QueryOptions controls page_size, capped at 100. Ordering is fixed to + // most recently pinned first and is not client-selectable. + // + // Leave query_options.paging_token unset on the first request. On every + // subsequent request, set it to the paging_token from the most recent + // response. The token is opaque and server-generated; do not construct + // it. It is bound to chat_id and refused with another chat. + common.v1.QueryOptions query_options = 2; + + // What the client intends to render of the pinned messages, and so + // whether they may be returned redacted (see messaging.v1.ViewMode). + messaging.v1.ViewMode view_mode = 3 [(validate.rules).enum = { + in: [0, 1, 2] // FULL, FULL_OR_REDACTED, REDACTED + }]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message GetPinnedMessagesResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; // Caller may not read the chat under view_mode + NOT_FOUND = 2; + } + + // The page, most recently pinned first, each with its message. Set when + // result is OK. + repeated Pin pins = 2 [(validate.rules).repeated = { + min_items: 0 + max_items: 100 + }]; + + // The chat's pin summary as of this page's read. Set when result is OK. + // Applied by version as described on PinSummary; see GetPinnedMessages + // for how the page relates to it. + PinSummary pin_summary = 3; + + // PagingToken carries the cursor after the last pin returned. The client + // MUST send it back in query_options.paging_token on the next + // GetPinnedMessagesRequest. Set when result is OK and has_more is true. + common.v1.PagingToken paging_token = 4; + + // HasMore indicates whether further pages remain. When true, the client + // should issue another GetPinnedMessagesRequest with the returned + // paging_token. + bool has_more = 5; +} diff --git a/proto/chat/v1/model.proto b/proto/chat/v1/model.proto index ac8e037..c91063e 100644 --- a/proto/chat/v1/model.proto +++ b/proto/chat/v1/model.proto @@ -160,6 +160,21 @@ message Metadata { // announces a change, and a client refetches the chat to refresh it. HoldingsSummary holdings_summary = 18; + // The chat's pinned messages, summarized: how many there are and the + // most recently pinned one, with its message, for a client to show + // above the conversation. Chat.GetPinnedMessages pages them all. + // + // Messaging state, like last_message: withheld from a viewer who may not + // read the chat, and the pinned message may be redacted (see + // messaging.v1.Message.redacted) under the same view mode. Otherwise + // always set, with count 0 and no latest when nothing is pinned. + // + // It may be unset on a chat returned by an RPC meant for list views + // (e.g. Chat.GetDmChatFeed, Chat.GetGroupChatFeed), so an unset value + // there does not mean nothing is pinned. Fetch it with Chat.GetChat, and + // don't let a list result clear a summary the client already holds. + PinSummary pin_summary = 19; + // Whether messages in this chat are end-to-end encrypted (see // messaging.v1.EncryptedContent). Only supported for DMs (CONTACT_DM or DM); // always false for group chats. A private group is always end-to-end @@ -387,6 +402,60 @@ message HoldingsSummary { google.protobuf.Timestamp as_of = 4 [(validate.rules).timestamp.required = true]; } +// Pin is one pinned message in a chat (see Chat.PinMessage). A chat's pins +// are ordered most recently pinned first; pinning a message that is already +// pinned does not move it. +message Pin { + // The pinned message. + messaging.v1.MessageId message_id = 1 [(validate.rules).message.required = true]; + + // The user who pinned it. + common.v1.UserId pinned_by = 2 [(validate.rules).message.required = true]; + + // When it was pinned. + google.protobuf.Timestamp pinned_at = 3 [(validate.rules).timestamp.required = true]; + + // The pinned message itself, as the viewer may read it: redacted (see + // messaging.v1.Message.redacted) when the read that returned it was + // redacted. Always set. + // + // A snapshot as of the read or event that carried it. Editing or + // deleting a pinned message does not move PinSummary.version and + // publishes no MetadataUpdate.PinsChanged; the change arrives on the + // chat's event log like any other (see messaging.v1.Messaging.GetDelta), + // and clients apply it to the copies they hold in their pins. + // + // A message deleted after it was pinned stays pinned, carrying its + // tombstone, until someone permitted to pin unpins it, and it still + // counts toward the cap on a chat's pins. Clients should not show such a + // pin. When PinSummary.latest is one, a client that shows the next pin + // fetches it with Chat.GetPinnedMessages. + messaging.v1.Message message = 4 [(validate.rules).message.required = true]; +} + +// PinSummary is the state of a chat's pinned messages that is shown with the +// chat (see Metadata.pin_summary). +message PinSummary { + // The most recently pinned message. Unset when nothing is pinned. + Pin latest = 1; + + // The number of pinned messages. The server caps how many messages a + // chat may have pinned, 5 today (see Chat.PinMessage). + uint32 count = 2; + + // Version of the chat's pins: advanced by exactly one on every pin and + // unpin, never by a no-op, and 0 for a chat that has never had a pin. + // A pinned message being edited or deleted does not move it (see + // Pin.message). + // + // Compared like RosterSummary.version: apply the greater value and drop + // the rest, so delivery order does not matter. It is not the chat event + // sequence and has no delta to fetch against it; a client that finds + // itself behind refetches its pins with Chat.GetPinnedMessages (see + // MetadataUpdate.PinsChanged). + uint64 version = 3; +} + message MetadataUpdate { oneof kind { option (validate.required) = true; @@ -399,6 +468,7 @@ message MetadataUpdate { DescriptionChanged description_changed = 6; CoverPictureChanged cover_picture_changed = 7; RulesChanged rules_changed = 8; + PinsChanged pins_changed = 9; } // Refreshes the entire chat metadata @@ -468,6 +538,32 @@ message MetadataUpdate { // The chat's rules in full, replacing Metadata.rules. Rules new_rules = 1 [(validate.rules).message.required = true]; } + + // A message was pinned or unpinned (see Chat.PinMessage and + // Chat.UnpinMessage). Delivered to the chat's members, including the + // caller's other devices, and to a chat preview, where the pinned + // message is redacted as the preview's messages are. Best-effort. + // + // A client holding the chat's pins at summary.version - 1 applies change + // to them and takes summary. A client further behind may have missed a + // change, so it takes summary and refetches its pins with + // Chat.GetPinnedMessages. A client at summary.version or later has + // already seen it and drops it. + message PinsChanged { + oneof change { + option (validate.required) = true; + + // The pin made, now the chat's most recently pinned message. + Pin pinned = 1; + + // The message unpinned. + messaging.v1.MessageId unpinned = 2; + } + + // The chat's pin summary after the change, replacing + // Metadata.pin_summary. + PinSummary summary = 3 [(validate.rules).message.required = true]; + } } // RosterUpdate is a best-effort, real-time change to a chat's roster — a @@ -598,6 +694,11 @@ message ViewerState { // it to anyone else would let them hand joiners a wrong chat key (see // Invite). bool can_invite = 3; + + // Whether the viewer may call Chat.PinMessage and Chat.UnpinMessage + // on this chat. Today that is either member of a DM, and a group's + // creator while a member: false for every other group member. + bool can_pin = 4; } // ModerationPermissions are the moderation actions the viewer may take From 2dd7f5484fae4eaa5093084d0b7a3d4a86e37e8e Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 29 Oct 2026 17:50:22 -0400 Subject: [PATCH 2/2] docs(changelog): add 0.24.0 entry for chat pins --- CHANGELOG.md | 39 +++++++++++++++++++ .../ContractInfo.swift | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9545bb..bc2e8a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,45 @@ called out explicitly even when nothing else did. release notes, so a version with no entry here does not release. Write the entry in the same PR that syncs the contract, while the diff is still in front of you. +## 0.24.0 + +Synced to [`flipcash2-protobuf-api@0e167fa6`](https://github.com/code-payments/flipcash2-protobuf-api/commit/0e167fa6c01edcfb5e617974fb6d2b30f9f55b98), +picking up [#164](https://github.com/code-payments/flipcash2-protobuf-api/pull/164). Only +`chat.v1` moved. + +This release adds pinned messages to chats. Nothing existing changed on the wire. The one +source break is a new oneof case, appended like every other addition here. + +### Breaking + +- `chat.v1.MetadataUpdate`'s `kind` oneof gains `pins_changed` (field 9). An exhaustive `switch` + over the Swift oneof, or a `when` over Kotlin's `KindCase` without an `else`, needs a case for + it before it compiles. A `when` with an `else` compiles but routes pin events into that + branch, so check what it does. + +### Added + +- `chat.v1.Chat.PinMessage`, `UnpinMessage` and `GetPinnedMessages`, with their request and + response messages. `PinMessageResponse.Result` is `OK`, `DENIED`, `NOT_FOUND`, + `MESSAGE_NOT_FOUND`, `TOO_MANY_PINS`, `MESSAGE_NOT_ALLOWED` (0–5); the other two results are + `OK`, `DENIED`, `NOT_FOUND` (0–2). Pin and unpin are no-ops returning `OK` when nothing would + change, and both return the chat's `PinSummary` on `OK`. The server caps a chat's pins (5 + today) and may raise the cap, so clients must not hardcode it. +- `chat.v1.Pin` (`message_id`, `pinned_by`, `pinned_at`, `message`) and `chat.v1.PinSummary` + (`latest`, `count`, `version`). `version` moves by one on every real pin or unpin and is + compared like `RosterSummary.version`: keep the greater. +- `chat.v1.Metadata.pin_summary` (field 19). It may be unset on feed RPCs such as + `GetDmChatFeed` and `GetGroupChatFeed`, and an unset value there must not clear a summary the + client already holds. +- `chat.v1.MetadataUpdate.PinsChanged`, with `pinned` or `unpinned` and the summary after the + change. +- `chat.v1.ViewerState.Permissions.can_pin` (field 4). Today it is true for either member of a + DM and for a group's creator while they are a member. + +### Unchanged + +Nothing was renumbered. No service, RPC, field or enum case was removed or changed type. + ## 0.23.0 Synced to [`flipcash2-protobuf-api@6de4f9ad`](https://github.com/code-payments/flipcash2-protobuf-api/commit/6de4f9adfd110a55b56fbfc99a5a466b3da789d1), diff --git a/Sources/Flipcash2ClientProtocol/ContractInfo.swift b/Sources/Flipcash2ClientProtocol/ContractInfo.swift index 8d77262..6202156 100644 --- a/Sources/Flipcash2ClientProtocol/ContractInfo.swift +++ b/Sources/Flipcash2ClientProtocol/ContractInfo.swift @@ -1,7 +1,7 @@ // Generated by scripts/generate-swift.sh. Do not edit. public enum Flipcash2ContractInfo { - public static let version = "0.23.0-dev" + public static let version = "0.24.0-dev" public static let protoCommit = "0e167fa6c01edcfb5e617974fb6d2b30f9f55b98" public static var isLocal: Bool { protoCommit == localSentinel }