diff --git a/CHANGELOG.md b/CHANGELOG.md index e5783d3..7116587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,30 @@ 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.17.0 + +Synced to [`flipcash2-protobuf-api@3bb442d3`](https://github.com/code-payments/flipcash2-protobuf-api/commit/3bb442d359dd8452b4e3eca5f5c1ca463e7f5a4b), +picking up [#134](https://github.com/code-payments/flipcash2-protobuf-api/pull/134). Only `chat.v1` +moved. + +### Added + +- Group descriptions, up to 160 characters and moderated like the title. + - `chat.v1.Metadata.description` (= 16). + - Set at creation through `description` on `PublicGroupChatParameters` (= 4) and + `PrivateGroupChatParameters` (= 3). Optional; empty sets none. + - Changed through `EditChatRequest.description` (= 4), a `Description { value }` wrapper: unset + leaves the description alone, and a set wrapper with an empty `value` clears it. + - Announced as `MetadataUpdate.DescriptionChanged` (= 6, `new_description`). +- `DESCRIPTION_MODERATED` on `StartChatResponse.Result` (= 6) and `EditChatResponse.Result` (= 5). + The response's `flagged_category` is set for it, as it is for `TITLE_MODERATED`. + +### Upgrading + +Nothing existing changed: no renames, and no field or enum renumbering. Both new result cases come +after the existing ones. A client that ignores `description` keeps working, but one with an +exhaustive `MetadataUpdate` switch needs a `DescriptionChanged` case. + ## 0.16.0 Synced to [`flipcash2-protobuf-api@ec66e6b1`](https://github.com/code-payments/flipcash2-protobuf-api/commit/ec66e6b12c59d7eae3281341e74fe8853c90a306), diff --git a/Sources/Flipcash2ClientProtocol/ContractInfo.swift b/Sources/Flipcash2ClientProtocol/ContractInfo.swift index 999458b..c054b99 100644 --- a/Sources/Flipcash2ClientProtocol/ContractInfo.swift +++ b/Sources/Flipcash2ClientProtocol/ContractInfo.swift @@ -1,8 +1,8 @@ // Generated by scripts/generate-swift.sh. Do not edit. public enum Flipcash2ContractInfo { - public static let version = "0.16.0-dev" - public static let protoCommit = "ec66e6b12c59d7eae3281341e74fe8853c90a306" + public static let version = "0.17.0-dev" + public static let protoCommit = "3bb442d359dd8452b4e3eca5f5c1ca463e7f5a4b" 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 a6eacd2..70c2d3f 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.grpc.swift @@ -578,8 +578,8 @@ extension Flipcash_Chat_V1_Chat { /// > /// > Only a group chat may be edited, and only by a member the server permits /// > to edit it, as reported by ViewerState.Permissions.can_edit; anyone else - /// > is DENIED. A new title is moderated like - /// > StartChat's. A new picture is a blob the caller has already uploaded via + /// > is DENIED. A new title or description is moderated like StartChat's. A + /// > new picture is a blob the caller has already uploaded via /// > BlobStorage: the client uploads only the ORIGINAL and passes the /// > resulting BlobId once the blob is READY, and the server derives the /// > remaining renditions. Setting a field to the value the chat already has @@ -588,7 +588,8 @@ extension Flipcash_Chat_V1_Chat { /// > /// > Every real change reaches the chat's members, including the caller's /// > other devices, on the event stream as one MetadataUpdate per field - /// > changed: TitleChanged for the title, PictureChanged for the picture. + /// > changed: TitleChanged for the title, PictureChanged for the picture, + /// > DescriptionChanged for the description. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_EditChatRequest` message. @@ -1320,8 +1321,8 @@ extension Flipcash_Chat_V1_Chat { /// > /// > Only a group chat may be edited, and only by a member the server permits /// > to edit it, as reported by ViewerState.Permissions.can_edit; anyone else - /// > is DENIED. A new title is moderated like - /// > StartChat's. A new picture is a blob the caller has already uploaded via + /// > is DENIED. A new title or description is moderated like StartChat's. A + /// > new picture is a blob the caller has already uploaded via /// > BlobStorage: the client uploads only the ORIGINAL and passes the /// > resulting BlobId once the blob is READY, and the server derives the /// > remaining renditions. Setting a field to the value the chat already has @@ -1330,7 +1331,8 @@ extension Flipcash_Chat_V1_Chat { /// > /// > Every real change reaches the chat's members, including the caller's /// > other devices, on the event stream as one MetadataUpdate per field - /// > changed: TitleChanged for the title, PictureChanged for the picture. + /// > changed: TitleChanged for the title, PictureChanged for the picture, + /// > DescriptionChanged for the description. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_EditChatRequest` message. @@ -2120,8 +2122,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > Only a group chat may be edited, and only by a member the server permits /// > to edit it, as reported by ViewerState.Permissions.can_edit; anyone else - /// > is DENIED. A new title is moderated like - /// > StartChat's. A new picture is a blob the caller has already uploaded via + /// > is DENIED. A new title or description is moderated like StartChat's. A + /// > new picture is a blob the caller has already uploaded via /// > BlobStorage: the client uploads only the ORIGINAL and passes the /// > resulting BlobId once the blob is READY, and the server derives the /// > remaining renditions. Setting a field to the value the chat already has @@ -2130,7 +2132,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > Every real change reaches the chat's members, including the caller's /// > other devices, on the event stream as one MetadataUpdate per field - /// > changed: TitleChanged for the title, PictureChanged for the picture. + /// > changed: TitleChanged for the title, PictureChanged for the picture, + /// > DescriptionChanged for the description. /// /// - Parameters: /// - request: A request containing a single `Flipcash_Chat_V1_EditChatRequest` message. @@ -2901,8 +2904,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > Only a group chat may be edited, and only by a member the server permits /// > to edit it, as reported by ViewerState.Permissions.can_edit; anyone else - /// > is DENIED. A new title is moderated like - /// > StartChat's. A new picture is a blob the caller has already uploaded via + /// > is DENIED. A new title or description is moderated like StartChat's. A + /// > new picture is a blob the caller has already uploaded via /// > BlobStorage: the client uploads only the ORIGINAL and passes the /// > resulting BlobId once the blob is READY, and the server derives the /// > remaining renditions. Setting a field to the value the chat already has @@ -2911,7 +2914,8 @@ extension Flipcash_Chat_V1_Chat.ClientProtocol { /// > /// > Every real change reaches the chat's members, including the caller's /// > other devices, on the event stream as one MetadataUpdate per field - /// > changed: TitleChanged for the title, PictureChanged for the picture. + /// > changed: TitleChanged for the title, PictureChanged for the picture, + /// > DescriptionChanged for the description. /// /// - Parameters: /// - message: request message to send. diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift index e28564f..078cc52 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_chat_service.pb.swift @@ -37,11 +37,11 @@ public struct Flipcash_Chat_V1_GetChatRequest: Sendable { /// What the client intends to render of the chat's messaging state — its /// last_message and latest_event_sequence — and so whether they may be /// returned redacted (see messaging.v1.ViewMode). A group's record (title, - /// picture, rules, roster summary) is returned to every registered user - /// whatever the mode; the mode decides only the messaging state, which is - /// withheld from a viewer who may not read the chat under it. Unset (FULL) - /// is the pre-redaction contract: messaging state for a viewer who may - /// read the chat in full, the bare record for anyone else. + /// description, picture, rules, roster summary) is returned to every + /// registered user whatever the mode; the mode decides only the messaging + /// state, which is withheld from a viewer who may not read the chat under + /// it. Unset (FULL) is the pre-redaction contract: messaging state for a + /// viewer who may read the chat in full, the bare record for anyone else. /// /// Must be REDACTED when auth is unset; any other mode is DENIED. public var viewMode: Flipcash_Messaging_V1_ViewMode = .full @@ -678,6 +678,10 @@ public struct Flipcash_Chat_V1_StartChatRequest: Sendable { /// Clears the value of `rules`. Subsequent reads from it will return its default value. public mutating func clearRules() {self._rules = nil} + /// Description for the chat. Optional; empty sets none. Moderated like + /// the title. + public var description_p: String = String() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -726,6 +730,10 @@ public struct Flipcash_Chat_V1_StartChatRequest: Sendable { /// Clears the value of `picture`. Subsequent reads from it will return its default value. public mutating func clearPicture() {self._picture = nil} + /// Description for the chat. Optional; empty sets none. Moderated like + /// the title. + public var description_p: String = String() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -759,8 +767,8 @@ public struct Flipcash_Chat_V1_StartChatResponse: Sendable { public mutating func clearChat() {self._chat = nil} /// The best-fit category that tripped moderation, mirroring the Moderation - /// service's vocabulary. Set only when result == TITLE_MODERATED; NONE - /// otherwise. + /// service's vocabulary. Set only when result == TITLE_MODERATED or + /// DESCRIPTION_MODERATED; NONE otherwise. public var flaggedCategory: Flipcash_Moderation_V1_FlaggedCategory = .none public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -773,6 +781,7 @@ public struct Flipcash_Chat_V1_StartChatResponse: Sendable { case pictureBlobNotAccepted // = 3 case invalidRules // = 4 case rulesNotSatisfied // = 5 + case descriptionModerated // = 6 case UNRECOGNIZED(Int) public init() { @@ -787,6 +796,7 @@ public struct Flipcash_Chat_V1_StartChatResponse: Sendable { case 3: self = .pictureBlobNotAccepted case 4: self = .invalidRules case 5: self = .rulesNotSatisfied + case 6: self = .descriptionModerated default: self = .UNRECOGNIZED(rawValue) } } @@ -799,6 +809,7 @@ public struct Flipcash_Chat_V1_StartChatResponse: Sendable { case .pictureBlobNotAccepted: return 3 case .invalidRules: return 4 case .rulesNotSatisfied: return 5 + case .descriptionModerated: return 6 case .UNRECOGNIZED(let i): return i } } @@ -811,6 +822,7 @@ public struct Flipcash_Chat_V1_StartChatResponse: Sendable { .pictureBlobNotAccepted, .invalidRules, .rulesNotSatisfied, + .descriptionModerated, ] } @@ -1033,6 +1045,16 @@ public struct Flipcash_Chat_V1_EditChatRequest: Sendable { /// Clears the value of `picture`. Subsequent reads from it will return its default value. public mutating func clearPicture() {self._picture = nil} + /// The new description. Left unchanged when unset. + public var description_p: Flipcash_Chat_V1_EditChatRequest.Description { + get {return _description_p ?? Flipcash_Chat_V1_EditChatRequest.Description()} + set {_description_p = newValue} + } + /// Returns true if `description_p` has been explicitly set. + public var hasDescription_p: Bool {return self._description_p != nil} + /// Clears the value of `description_p`. Subsequent reads from it will return its default value. + public mutating func clearDescription_p() {self._description_p = nil} + public var auth: Flipcash_Common_V1_Auth { get {return _auth ?? Flipcash_Common_V1_Auth()} set {_auth = newValue} @@ -1080,11 +1102,25 @@ public struct Flipcash_Chat_V1_EditChatRequest: Sendable { fileprivate var _blobID: Flipcash_Blob_V1_BlobId? = nil } + public struct Description: 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 new description. Empty clears it. + public var value: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + public init() {} fileprivate var _chatID: Flipcash_Common_V1_ChatId? = nil fileprivate var _title: Flipcash_Chat_V1_EditChatRequest.Title? = nil fileprivate var _picture: Flipcash_Chat_V1_EditChatRequest.Picture? = nil + fileprivate var _description_p: Flipcash_Chat_V1_EditChatRequest.Description? = nil fileprivate var _auth: Flipcash_Common_V1_Auth? = nil } @@ -1108,8 +1144,8 @@ public struct Flipcash_Chat_V1_EditChatResponse: Sendable { public mutating func clearChat() {self._chat = nil} /// The best-fit category that tripped moderation, mirroring the Moderation - /// service's vocabulary. Set only when result == TITLE_MODERATED; NONE - /// otherwise. + /// service's vocabulary. Set only when result == TITLE_MODERATED or + /// DESCRIPTION_MODERATED; NONE otherwise. public var flaggedCategory: Flipcash_Moderation_V1_FlaggedCategory = .none public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -1121,6 +1157,7 @@ public struct Flipcash_Chat_V1_EditChatResponse: Sendable { case notFound // = 2 case titleModerated // = 3 case pictureBlobNotAccepted // = 4 + case descriptionModerated // = 5 case UNRECOGNIZED(Int) public init() { @@ -1134,6 +1171,7 @@ public struct Flipcash_Chat_V1_EditChatResponse: Sendable { case 2: self = .notFound case 3: self = .titleModerated case 4: self = .pictureBlobNotAccepted + case 5: self = .descriptionModerated default: self = .UNRECOGNIZED(rawValue) } } @@ -1145,6 +1183,7 @@ public struct Flipcash_Chat_V1_EditChatResponse: Sendable { case .notFound: return 2 case .titleModerated: return 3 case .pictureBlobNotAccepted: return 4 + case .descriptionModerated: return 5 case .UNRECOGNIZED(let i): return i } } @@ -1156,6 +1195,7 @@ public struct Flipcash_Chat_V1_EditChatResponse: Sendable { .notFound, .titleModerated, .pictureBlobNotAccepted, + .descriptionModerated, ] } @@ -2648,7 +2688,7 @@ extension Flipcash_Chat_V1_StartChatRequest: SwiftProtobuf.Message, SwiftProtobu extension Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Flipcash_Chat_V1_StartChatRequest.protoMessageName + ".PublicGroupChatParameters" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0\u{1}rules\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0\u{1}rules\0\u{1}description\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2659,6 +2699,7 @@ extension Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters: SwiftProt case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._picture) }() case 3: try { try decoder.decodeSingularMessageField(value: &self._rules) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.description_p) }() default: break } } @@ -2678,6 +2719,9 @@ extension Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters: SwiftProt try { if let v = self._rules { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() + if !self.description_p.isEmpty { + try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } @@ -2685,6 +2729,7 @@ extension Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters: SwiftProt if lhs.title != rhs.title {return false} if lhs._picture != rhs._picture {return false} if lhs._rules != rhs._rules {return false} + if lhs.description_p != rhs.description_p {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2692,7 +2737,7 @@ extension Flipcash_Chat_V1_StartChatRequest.PublicGroupChatParameters: SwiftProt extension Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Flipcash_Chat_V1_StartChatRequest.protoMessageName + ".PrivateGroupChatParameters" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}title\0\u{1}picture\0\u{1}description\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2702,6 +2747,7 @@ extension Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters: SwiftPro switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.title) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._picture) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.description_p) }() default: break } } @@ -2718,12 +2764,16 @@ extension Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters: SwiftPro try { if let v = self._picture { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + if !self.description_p.isEmpty { + try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters, rhs: Flipcash_Chat_V1_StartChatRequest.PrivateGroupChatParameters) -> Bool { if lhs.title != rhs.title {return false} if lhs._picture != rhs._picture {return false} + if lhs.description_p != rhs.description_p {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2774,7 +2824,7 @@ extension Flipcash_Chat_V1_StartChatResponse: SwiftProtobuf.Message, SwiftProtob } extension Flipcash_Chat_V1_StartChatResponse.Result: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0\u{1}INVALID_RULES\0\u{1}RULES_NOT_SATISFIED\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0\u{1}INVALID_RULES\0\u{1}RULES_NOT_SATISFIED\0\u{1}DESCRIPTION_MODERATED\0") } extension Flipcash_Chat_V1_JoinChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { @@ -2934,7 +2984,7 @@ extension Flipcash_Chat_V1_LeaveChatResponse.Result: SwiftProtobuf._ProtoNamePro extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".EditChatRequest" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}title\0\u{1}picture\0\u{2}\u{7}auth\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}title\0\u{1}picture\0\u{1}description\0\u{2}\u{6}auth\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2945,6 +2995,7 @@ extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf case 1: try { try decoder.decodeSingularMessageField(value: &self._chatID) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._title) }() case 3: try { try decoder.decodeSingularMessageField(value: &self._picture) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._description_p) }() case 10: try { try decoder.decodeSingularMessageField(value: &self._auth) }() default: break } @@ -2965,6 +3016,9 @@ extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf try { if let v = self._picture { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() + try { if let v = self._description_p { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() try { if let v = self._auth { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() @@ -2975,6 +3029,7 @@ extension Flipcash_Chat_V1_EditChatRequest: SwiftProtobuf.Message, SwiftProtobuf if lhs._chatID != rhs._chatID {return false} if lhs._title != rhs._title {return false} if lhs._picture != rhs._picture {return false} + if lhs._description_p != rhs._description_p {return false} if lhs._auth != rhs._auth {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true @@ -3045,6 +3100,36 @@ extension Flipcash_Chat_V1_EditChatRequest.Picture: SwiftProtobuf.Message, Swift } } +extension Flipcash_Chat_V1_EditChatRequest.Description: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_EditChatRequest.protoMessageName + ".Description" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}value\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.decodeSingularStringField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.value.isEmpty { + try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_EditChatRequest.Description, rhs: Flipcash_Chat_V1_EditChatRequest.Description) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Flipcash_Chat_V1_EditChatResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".EditChatResponse" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}chat\0\u{3}flagged_category\0") @@ -3090,7 +3175,7 @@ extension Flipcash_Chat_V1_EditChatResponse: SwiftProtobuf.Message, SwiftProtobu } extension Flipcash_Chat_V1_EditChatResponse.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}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}NOT_FOUND\0\u{1}TITLE_MODERATED\0\u{1}PICTURE_BLOB_NOT_ACCEPTED\0\u{1}DESCRIPTION_MODERATED\0") } extension Flipcash_Chat_V1_MuteChatRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { diff --git a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift index b8df97e..c050f41 100644 --- a/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/chat_v1_model.pb.swift @@ -225,6 +225,17 @@ public struct Flipcash_Chat_V1_Metadata: @unchecked Sendable { set {_uniqueStorage()._inLobby = newValue} } + /// A short free-form description of this chat, written by its creator. + /// Only supported for group chats. Empty when none is set. Part of the + /// group's record, like title, so it is returned to every viewer who can + /// see the chat, including in its public view. + /// + /// Set it on creation (see StartChatRequest) or with Chat.EditChat. + public var description_p: String { + get {return _storage._description_p} + set {_uniqueStorage()._description_p = newValue} + } + /// 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 @@ -628,6 +639,14 @@ public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { set {kind = .pictureChanged(newValue)} } + public var descriptionChanged: Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged { + get { + if case .descriptionChanged(let v)? = kind {return v} + return Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged() + } + set {kind = .descriptionChanged(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Kind: Equatable, Sendable { @@ -636,6 +655,7 @@ public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { case viewerStateChanged(Flipcash_Chat_V1_MetadataUpdate.ViewerStateChanged) case titleChanged(Flipcash_Chat_V1_MetadataUpdate.TitleChanged) case pictureChanged(Flipcash_Chat_V1_MetadataUpdate.PictureChanged) + case descriptionChanged(Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged) } @@ -749,6 +769,24 @@ public struct Flipcash_Chat_V1_MetadataUpdate: Sendable { fileprivate var _newPicture: Flipcash_Blob_V1_Media? = nil } + /// The chat's description has changed (e.g. via Chat.EditChat). Delivered + /// to the chat's members, including the editor's other devices. + /// Best-effort and applied as received; a client that suspects a miss + /// refetches the chat via Chat.GetChat. + public struct DescriptionChanged: 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 new description, replacing Metadata.description. Empty when the + /// description was cleared. + public var newDescription: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + public init() {} } @@ -1389,7 +1427,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{1}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{4}U\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{1}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{4}T\u{1}use_e2ee\0") fileprivate class _StorageClass { var _chatID: Flipcash_Common_V1_ChatId? = nil @@ -1407,6 +1445,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa var _creator: Flipcash_Common_V1_UserId? = nil var _isPrivate: Bool = false var _inLobby: Bool = false + var _description_p: String = String() var _useE2Ee: Bool = false // This property is used as the initial default value for new instances of the type. @@ -1433,6 +1472,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa _creator = source._creator _isPrivate = source._isPrivate _inLobby = source._inLobby + _description_p = source._description_p _useE2Ee = source._useE2Ee } } @@ -1467,6 +1507,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa case 13: try { try decoder.decodeSingularMessageField(value: &_storage._creator) }() case 14: try { try decoder.decodeSingularBoolField(value: &_storage._isPrivate) }() case 15: try { try decoder.decodeSingularBoolField(value: &_storage._inLobby) }() + case 16: try { try decoder.decodeSingularStringField(value: &_storage._description_p) }() case 100: try { try decoder.decodeSingularBoolField(value: &_storage._useE2Ee) }() default: break } @@ -1525,6 +1566,9 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa if _storage._inLobby != false { try visitor.visitSingularBoolField(value: _storage._inLobby, fieldNumber: 15) } + if !_storage._description_p.isEmpty { + try visitor.visitSingularStringField(value: _storage._description_p, fieldNumber: 16) + } if _storage._useE2Ee != false { try visitor.visitSingularBoolField(value: _storage._useE2Ee, fieldNumber: 100) } @@ -1552,6 +1596,7 @@ extension Flipcash_Chat_V1_Metadata: SwiftProtobuf.Message, SwiftProtobuf._Messa if _storage._creator != rhs_storage._creator {return false} if _storage._isPrivate != rhs_storage._isPrivate {return false} if _storage._inLobby != rhs_storage._inLobby {return false} + if _storage._description_p != rhs_storage._description_p {return false} if _storage._useE2Ee != rhs_storage._useE2Ee {return false} return true } @@ -2073,7 +2118,7 @@ extension Flipcash_Chat_V1_RosterSummary: SwiftProtobuf.Message, SwiftProtobuf._ 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}picture_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}picture_changed\0\u{3}description_changed\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2146,6 +2191,19 @@ extension Flipcash_Chat_V1_MetadataUpdate: SwiftProtobuf.Message, SwiftProtobuf. self.kind = .pictureChanged(v) } }() + case 6: try { + var v: Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged? + var hadOneofValue = false + if let current = self.kind { + hadOneofValue = true + if case .descriptionChanged(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.kind = .descriptionChanged(v) + } + }() default: break } } @@ -2177,6 +2235,10 @@ extension Flipcash_Chat_V1_MetadataUpdate: SwiftProtobuf.Message, SwiftProtobuf. guard case .pictureChanged(let v)? = self.kind else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 5) }() + case .descriptionChanged?: try { + guard case .descriptionChanged(let v)? = self.kind else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -2355,6 +2417,36 @@ extension Flipcash_Chat_V1_MetadataUpdate.PictureChanged: SwiftProtobuf.Message, } } +extension Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Chat_V1_MetadataUpdate.protoMessageName + ".DescriptionChanged" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}new_description\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.decodeSingularStringField(value: &self.newDescription) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.newDescription.isEmpty { + try visitor.visitSingularStringField(value: self.newDescription, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged, rhs: Flipcash_Chat_V1_MetadataUpdate.DescriptionChanged) -> Bool { + if lhs.newDescription != rhs.newDescription {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{4}\u{8}roster_summary\0") diff --git a/flipcash2.lock b/flipcash2.lock index c32ab57..cae8c20 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: ec66e6b12c59d7eae3281341e74fe8853c90a306 -subject: Add support for bios and cover pictures for user profiles (#133) +commit: 3bb442d359dd8452b4e3eca5f5c1ca463e7f5a4b +subject: Add support for group descriptions (#134) diff --git a/proto/chat/v1/chat_service.proto b/proto/chat/v1/chat_service.proto index 3f058c3..7bddc6e 100644 --- a/proto/chat/v1/chat_service.proto +++ b/proto/chat/v1/chat_service.proto @@ -153,8 +153,8 @@ service Chat { // // Only a group chat may be edited, and only by a member the server permits // to edit it, as reported by ViewerState.Permissions.can_edit; anyone else - // is DENIED. A new title is moderated like - // StartChat's. A new picture is a blob the caller has already uploaded via + // is DENIED. A new title or description is moderated like StartChat's. A + // new picture is a blob the caller has already uploaded via // BlobStorage: the client uploads only the ORIGINAL and passes the // resulting BlobId once the blob is READY, and the server derives the // remaining renditions. Setting a field to the value the chat already has @@ -163,7 +163,8 @@ service Chat { // // Every real change reaches the chat's members, including the caller's // other devices, on the event stream as one MetadataUpdate per field - // changed: TitleChanged for the title, PictureChanged for the picture. + // changed: TitleChanged for the title, PictureChanged for the picture, + // DescriptionChanged for the description. rpc EditChat(EditChatRequest) returns (EditChatResponse); // MuteChat mutes a chat for the caller, until a time or indefinitely. @@ -298,11 +299,11 @@ message GetChatRequest { // What the client intends to render of the chat's messaging state — its // last_message and latest_event_sequence — and so whether they may be // returned redacted (see messaging.v1.ViewMode). A group's record (title, - // picture, rules, roster summary) is returned to every registered user - // whatever the mode; the mode decides only the messaging state, which is - // withheld from a viewer who may not read the chat under it. Unset (FULL) - // is the pre-redaction contract: messaging state for a viewer who may - // read the chat in full, the bare record for anyone else. + // description, picture, rules, roster summary) is returned to every + // registered user whatever the mode; the mode decides only the messaging + // state, which is withheld from a viewer who may not read the chat under + // it. Unset (FULL) is the pre-redaction contract: messaging state for a + // viewer who may read the chat in full, the bare record for anyone else. // // Must be REDACTED when auth is unset; any other mode is DENIED. messaging.v1.ViewMode view_mode = 2 [(validate.rules).enum = { @@ -521,6 +522,12 @@ message StartChatRequest { // chat has no participation requirements. Caller must satisfy the rules // in order for the chat to be started. Rules rules = 3; + + // Description for the chat. Optional; empty sets none. Moderated like + // the title. + string description = 4 [(validate.rules).string = { + max_len: 160 + }]; } // Parameters for creating a private group chat (see Metadata.is_private): @@ -554,6 +561,12 @@ message StartChatRequest { // The blob holding the ORIGINAL picture the caller uploaded. Optional. // If set, it must be owned by the caller and READY. blob.v1.BlobId picture = 2; + + // Description for the chat. Optional; empty sets none. Moderated like + // the title. + string description = 3 [(validate.rules).string = { + max_len: 160 + }]; } // Key for retrying this request safely. A retry with the same key from the @@ -573,6 +586,7 @@ message StartChatResponse { PICTURE_BLOB_NOT_ACCEPTED = 3; INVALID_RULES = 4; RULES_NOT_SATISFIED = 5; + DESCRIPTION_MODERATED = 6; } // The metadata for the newly created chat, including the server-generated @@ -581,8 +595,8 @@ message StartChatResponse { Metadata chat = 2; // The best-fit category that tripped moderation, mirroring the Moderation - // service's vocabulary. Set only when result == TITLE_MODERATED; NONE - // otherwise. + // service's vocabulary. Set only when result == TITLE_MODERATED or + // DESCRIPTION_MODERATED; NONE otherwise. moderation.v1.FlaggedCategory flagged_category = 3; } @@ -630,6 +644,9 @@ message EditChatRequest { // The new picture. Left unchanged when unset. Picture picture = 3; + // The new description. Left unchanged when unset. + Description description = 4; + message Title { string value = 1 [(validate.rules).string = { min_len: 1 @@ -644,6 +661,13 @@ message EditChatRequest { blob.v1.BlobId blob_id = 1 [(validate.rules).message.required = true]; } + message Description { + // The new description. Empty clears it. + string value = 1 [(validate.rules).string = { + max_len: 160 + }]; + } + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; } @@ -655,6 +679,7 @@ message EditChatResponse { NOT_FOUND = 2; TITLE_MODERATED = 3; PICTURE_BLOB_NOT_ACCEPTED = 4; + DESCRIPTION_MODERATED = 5; } // The metadata for the chat after the edit, as seen by the caller, @@ -663,8 +688,8 @@ message EditChatResponse { Metadata chat = 2; // The best-fit category that tripped moderation, mirroring the Moderation - // service's vocabulary. Set only when result == TITLE_MODERATED; NONE - // otherwise. + // service's vocabulary. Set only when result == TITLE_MODERATED or + // DESCRIPTION_MODERATED; NONE otherwise. moderation.v1.FlaggedCategory flagged_category = 3; } diff --git a/proto/chat/v1/model.proto b/proto/chat/v1/model.proto index 533c4ce..b9fd1e9 100644 --- a/proto/chat/v1/model.proto +++ b/proto/chat/v1/model.proto @@ -106,6 +106,16 @@ message Metadata { // group. bool in_lobby = 15; + // A short free-form description of this chat, written by its creator. + // Only supported for group chats. Empty when none is set. Part of the + // group's record, like title, so it is returned to every viewer who can + // see the chat, including in its public view. + // + // Set it on creation (see StartChatRequest) or with Chat.EditChat. + string description = 16 [(validate.rules).string = { + max_len: 160 + }]; + // 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 @@ -293,6 +303,7 @@ message MetadataUpdate { ViewerStateChanged viewer_state_changed = 3; TitleChanged title_changed = 4; PictureChanged picture_changed = 5; + DescriptionChanged description_changed = 6; } // Refreshes the entire chat metadata @@ -331,6 +342,18 @@ message MetadataUpdate { // Metadata.picture. blob.v1.Media new_picture = 1 [(validate.rules).message.required = true]; } + + // The chat's description has changed (e.g. via Chat.EditChat). Delivered + // to the chat's members, including the editor's other devices. + // Best-effort and applied as received; a client that suspects a miss + // refetches the chat via Chat.GetChat. + message DescriptionChanged { + // The new description, replacing Metadata.description. Empty when the + // description was cleared. + string new_description = 1 [(validate.rules).string = { + max_len: 160 + }]; + } } // RosterUpdate is a best-effort, real-time change to a chat's roster — a