diff --git a/CHANGELOG.md b/CHANGELOG.md index 821a3a4..17bb9de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,68 @@ 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.19.0 + +Synced to [`flipcash2-protobuf-api@1897108a`](https://github.com/code-payments/flipcash2-protobuf-api/commit/1897108ae75e032fc5f7de13cf6e9cadcec9deda), +picking up [#139](https://github.com/code-payments/flipcash2-protobuf-api/pull/139) (docs only), +[#140](https://github.com/code-payments/flipcash2-protobuf-api/pull/140), +[#141](https://github.com/code-payments/flipcash2-protobuf-api/pull/141), +[#142](https://github.com/code-payments/flipcash2-protobuf-api/pull/142), +[#143](https://github.com/code-payments/flipcash2-protobuf-api/pull/143), +[#144](https://github.com/code-payments/flipcash2-protobuf-api/pull/144), +[#145](https://github.com/code-payments/flipcash2-protobuf-api/pull/145) and +[#146](https://github.com/code-payments/flipcash2-protobuf-api/pull/146). `poll.v1` is new; +`intent.v1` and `messaging.v1` moved; `chat.v1` changed only in comments. + +This release adds polls, payments in group chats, link previews and structured system messages. One +enum moved, which is wire-compatible but breaks source in both languages. + +### Moved + +- `ChatMetadata.DmPayment.Action` is now the top-level enum `PaymentAction`, shared by DM and + group payments. Its cases keep their values (`DEFAULT = 0`, `SEND = 1`, `TIP = 2`) and + `DmPayment.action` keeps field 2, so the wire format is unchanged. Code naming the old type has + to be updated: `Flipcash_Intent_V1_ChatMetadata.DmPayment.Action` becomes + `Flipcash_Intent_V1_PaymentAction` in Swift, and `ChatMetadata.DmPayment.Action` becomes + `PaymentAction` in Kotlin. + +### Added + +- A `Poll` service in `poll.v1` with `CreatePoll`, `GetPoll`, `GetPolls` and `Vote`, and its model: + count or balance-weighted polls of 2 to 10 options, single or multiple choice, a required end + time 5 minutes to 30 days out, and question text that is plaintext in a group that is not + private and encrypted in a private one. Results come back as count, balance, ranked or withheld, + and every vote result model carries `total_voters`. +- `WidgetContent.poll` (field 2), a `PollWidget` carrying only the poll's ID. In a group that is not + private, `SendMessage` accepts it only from the poll's creator, in the poll's own chat, while the + poll is `OPEN`. A private group sends it inside `EncryptedContent`, where clients do those checks + and render a mismatch as unsupported. +- `ChatMetadata.group_payment` (field 4), a `GroupPayment` for paying another member of a group + chat. The payment's destination owner names the recipient, who must be a member. Only `DEFAULT` + and `SEND` are accepted; the server denies `TIP`. +- `CashContent.receiver` (field 3, previously reserved), the user who received the cash. It is + always set in a group chat. In a DM it is set on new messages only, so a client reading older DM + messages still has to treat the other member as the receiver when it is unset. +- `TextContent.link_previews` (field 2), at most one `LinkPreview` with a `url`, `title`, optional + `description` and optional `image`. The sending device builds the preview, so its title, + description and image are the sender's claim; the comments on `LinkPreview` spell out the + validation rules and how clients have to render the host. +- `SystemContent.type`, a required oneof whose first case is `user_joined` (field 2). `UserJoined` + names the user and how they joined: `direct`, or `admitted` with the user who let them in. + `fallback_text` is unchanged and still always set; a client renders it only for a variant it + does not recognize. + +### Changed + +- `EncryptedContent.ciphertext` may now be up to 28672 bytes, up from 17408, to fit a maximum-length + text with a link preview. + +### Unchanged + +Nothing was renumbered. Every result enum in this release is new, so no existing case moved. Apart +from the `PaymentAction` move, no existing field changed type or meaning, and no service, RPC, +message or enum case was removed. + ## 0.18.0 Synced to [`flipcash2-protobuf-api@9d44350b`](https://github.com/code-payments/flipcash2-protobuf-api/commit/9d44350b8d4d8eda9d63cff405238bd0e2920409), diff --git a/Sources/Flipcash2ClientProtocol/ContractInfo.swift b/Sources/Flipcash2ClientProtocol/ContractInfo.swift index 519248f..a787f99 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.18.0-dev" - public static let protoCommit = "9f67566fd0297a27ad2de7a98474634adf4d3a24" + public static let version = "0.19.0-dev" + public static let protoCommit = "1897108ae75e032fc5f7de13cf6e9cadcec9deda" public static var isLocal: Bool { protoCommit == localSentinel } diff --git a/Sources/Flipcash2ClientProtocol/intent_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/intent_v1_model.pb.swift index e394a34..4ea61b1 100644 --- a/Sources/Flipcash2ClientProtocol/intent_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/intent_v1_model.pb.swift @@ -20,6 +20,47 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } +/// The action being performed for a payment in a chat +public enum Flipcash_Intent_V1_PaymentAction: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// Same as SEND: the payment is shown as "sent", not "tipped" + case `default` // = 0 + case send // = 1 + case tip // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .default + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .default + case 1: self = .send + case 2: self = .tip + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .default: return 0 + case .send: return 1 + case .tip: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Intent_V1_PaymentAction] = [ + .default, + .send, + .tip, + ] + +} + public struct Flipcash_Intent_V1_AppMetadata: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -78,11 +119,20 @@ public struct Flipcash_Intent_V1_ChatMetadata: Sendable { set {type = .dmPayment(newValue)} } + public var groupPayment: Flipcash_Intent_V1_ChatMetadata.GroupPayment { + get { + if case .groupPayment(let v)? = type {return v} + return Flipcash_Intent_V1_ChatMetadata.GroupPayment() + } + set {type = .groupPayment(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Type: Equatable, Sendable { case contactDmPayment(Flipcash_Intent_V1_ChatMetadata.ContactDmPayment) case dmPayment(Flipcash_Intent_V1_ChatMetadata.DmPayment) + case groupPayment(Flipcash_Intent_V1_ChatMetadata.GroupPayment) } @@ -129,7 +179,8 @@ public struct Flipcash_Intent_V1_ChatMetadata: Sendable { public var location: Flipcash_Intent_V1_ChatMetadata.DmPayment.Location = .flipcard - public var action: Flipcash_Intent_V1_ChatMetadata.DmPayment.Action = .default + /// The action being performed for the payment + public var action: Flipcash_Intent_V1_PaymentAction = .default public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -168,46 +219,26 @@ public struct Flipcash_Intent_V1_ChatMetadata: Sendable { } - /// The action being performed for the payment - public enum Action: SwiftProtobuf.Enum, Swift.CaseIterable { - public typealias RawValue = Int - - /// Same as SEND: the payment is shown as "sent", not "tipped" - case `default` // = 0 - case send // = 1 - case tip // = 2 - case UNRECOGNIZED(Int) - - public init() { - self = .default - } + public init() {} + } - public init?(rawValue: Int) { - switch rawValue { - case 0: self = .default - case 1: self = .send - case 2: self = .tip - default: self = .UNRECOGNIZED(rawValue) - } - } + /// For sending a payment directly to another member of a group chat. The + /// chat_id must be a group, and the payment's destination owner account + /// names the recipient, who must be a Flipcash user and a member of the + /// group. The sender must be able to speak in the group: the payment posts + /// a CashContent message into it as the sender, naming the recipient as + /// its receiver. + public struct GroupPayment: 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 rawValue: Int { - switch self { - case .default: return 0 - case .send: return 1 - case .tip: return 2 - case .UNRECOGNIZED(let i): return i - } - } + /// The action being performed for the payment. Only DEFAULT and SEND + /// are supported today, and the payment is shown as SENT; the server + /// denies TIP. + public var action: Flipcash_Intent_V1_PaymentAction = .default - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Flipcash_Intent_V1_ChatMetadata.DmPayment.Action] = [ - .default, - .send, - .tip, - ] - - } + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} } @@ -221,6 +252,10 @@ public struct Flipcash_Intent_V1_ChatMetadata: Sendable { fileprivate let _protobuf_package = "flipcash.intent.v1" +extension Flipcash_Intent_V1_PaymentAction: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0DEFAULT\0\u{1}SEND\0\u{1}TIP\0") +} + extension Flipcash_Intent_V1_AppMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".AppMetadata" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}chat\0") @@ -269,7 +304,7 @@ extension Flipcash_Intent_V1_AppMetadata: SwiftProtobuf.Message, SwiftProtobuf._ extension Flipcash_Intent_V1_ChatMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ChatMetadata" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}contact_dm_payment\0\u{3}dm_payment\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{3}contact_dm_payment\0\u{3}dm_payment\0\u{3}group_payment\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -304,6 +339,19 @@ extension Flipcash_Intent_V1_ChatMetadata: SwiftProtobuf.Message, SwiftProtobuf. self.type = .dmPayment(v) } }() + case 4: try { + var v: Flipcash_Intent_V1_ChatMetadata.GroupPayment? + var hadOneofValue = false + if let current = self.type { + hadOneofValue = true + if case .groupPayment(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.type = .groupPayment(v) + } + }() default: break } } @@ -326,6 +374,10 @@ extension Flipcash_Intent_V1_ChatMetadata: SwiftProtobuf.Message, SwiftProtobuf. guard case .dmPayment(let v)? = self.type else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 3) }() + case .groupPayment?: try { + guard case .groupPayment(let v)? = self.type else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -417,6 +469,32 @@ extension Flipcash_Intent_V1_ChatMetadata.DmPayment.Location: SwiftProtobuf._Pro public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0FLIPCARD\0\u{1}CHAT\0") } -extension Flipcash_Intent_V1_ChatMetadata.DmPayment.Action: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0DEFAULT\0\u{1}SEND\0\u{1}TIP\0") +extension Flipcash_Intent_V1_ChatMetadata.GroupPayment: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Intent_V1_ChatMetadata.protoMessageName + ".GroupPayment" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}action\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.action) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.action != .default { + try visitor.visitSingularEnumField(value: self.action, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Intent_V1_ChatMetadata.GroupPayment, rhs: Flipcash_Intent_V1_ChatMetadata.GroupPayment) -> Bool { + if lhs.action != rhs.action {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } } diff --git a/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift b/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift index a9708a3..d1cc2f5 100644 --- a/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift +++ b/Sources/Flipcash2ClientProtocol/messaging_v1_messaging_service.pb.swift @@ -407,6 +407,9 @@ public struct Flipcash_Messaging_V1_SendMessageRequest: Sendable { /// - TextContent /// - ReplyContent /// - MediaContent + /// - WidgetContent with a PollWidget, in group chats that are not private + /// only. A private group sends it inside EncryptedContent. See + /// PollWidget for when it is accepted. /// - EncryptedContent, in DMs and private groups only. A private group /// accepts nothing else. /// diff --git a/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift index eb7a6ca..c2a53b1 100644 --- a/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift +++ b/Sources/Flipcash2ClientProtocol/messaging_v1_model.pb.swift @@ -368,11 +368,112 @@ public struct Flipcash_Messaging_V1_TextContent: Sendable { public var text: String = String() + /// Previews of links in `text`, generated by the sending device (see + /// LinkPreview). One today; raising this cap later allows several, carried + /// in the order their links appear in `text`. Each preview's url is a + /// distinct link in `text`, as LinkPreview.url defines it, or the send is + /// DENIED. A client that finds more previews than it supports renders the + /// first and ignores the rest. + /// + /// Allowed on a message's text and on a reply's text body; a media caption + /// carries none. A preview is optional: a text that holds a link is always + /// sendable without one, and the sender may drop one it generated. + public var linkPreviews: [Flipcash_Messaging_V1_LinkPreview] = [] + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} } +/// A preview of a link in the text that carries it, as the sending device +/// rendered it from the page's Open Graph tags (or their fallbacks: the page +/// title and meta description): a title, an optional description and an +/// optional image. The sending device fetches the page, not the server and +/// never the recipient, so no one but the sender ever touches the link, and +/// the server cannot moderate a preview beyond its image or vouch that the +/// preview matches the page. +/// +/// What the server guarantees is only that `url` is a link the recipient can +/// read in the text (see url below) and, outside EncryptedContent, that the +/// image is one the sender uploaded and the server moderated. The title, +/// description and image are the sender's claim about the page. Clients +/// therefore show the host of `url` on the card, always and never the title +/// alone, and open `url` when the card is tapped, so a forged card sits beside +/// a true domain and leads only where the text already leads. They render the +/// host apart from the sender's text, in a style the title and description +/// never take, and clamp the title and description to a few lines each, so +/// neither can pass for the host or push it out of view. +public struct Flipcash_Messaging_V1_LinkPreview: 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 link the preview describes. Its scheme is http or https, and it + /// carries no userinfo, whitespace or bidirectional control (U+061C, + /// U+200E, U+200F, U+202A–U+202E, U+2066–U+2069). Its host is a plain + /// domain name: dot-separated labels of ASCII letters, digits and hyphens, + /// none empty, with an optional trailing dot, so no IP literal, no + /// percent-escape and no non-ASCII (a sender uses the punycode form). The + /// host's last label is not a number (all digits, or `0x` and hex + /// digits), which a browser reads as an IPv4 address. A port, if given, is + /// 1–65535. + /// + /// It appears in the text that holds the preview exactly as written here, + /// as a whole link: byte for byte, with no case folding, percent-decoding + /// or other normalization, preceded by + /// the start of the text or whitespace with only opening brackets or + /// quotes between (any run of `(`, `[`, `{`, `<`, `"`, `'`), and followed + /// by the end of the text or whitespace with only trailing punctuation + /// between (any run of `.`, `,`, `;`, `:`, `!`, `?`, `)`, `]`, `}`, `>`, + /// `"`, `'`). So a whole link is one whitespace-separated word of the + /// text, less the brackets, quotes and punctuation around it: "see + /// (https://a.example)." carries a link to https://a.example, and + /// "https://a.example.evil.example" and + /// "https://evil.example/(https://a.example)" do not. A sender that + /// linkifies the text differently generates no preview for what it cannot + /// send. + public var url: String = String() + + /// The page's title. Something visible: at least one character that is + /// neither whitespace nor an invisible format character (such as a + /// zero-width space). One line: no line break (line feed, U+2028 or + /// U+2029), so a title cannot draw a line beneath it that reads as the + /// host. No control characters, and no bidirectional embedding, override + /// or isolate (U+202A–U+202E, U+2066–U+2069); the marks U+200E, U+200F + /// and U+061C are allowed. A sender removes what is refused from the + /// title it scraped (replacing a line break with a space), or drops the + /// preview. + public var title: String = String() + + /// The page's description, if it has one. Empty for none; otherwise held + /// to the same rules as the title, except that it may hold line feeds. + public var description_p: String = String() + + /// The page's image, if it has one. Like a MediaContent item: on + /// SendMessage the client supplies exactly one ORIGINAL rendition, a blob + /// it uploaded like any chat image, which the server moderated and derives + /// renditions from; the server fills the rendition's metadata and appends + /// the derived renditions on every read. Inside EncryptedContent the + /// sender sets the ORIGINAL's metadata itself and there are no derived + /// renditions; see EncryptedContent. The sending device downloads the + /// page's image, downscales it and strips its metadata before uploading, + /// as it would a photo. + public var image: Flipcash_Blob_V1_Media { + get {return _image ?? Flipcash_Blob_V1_Media()} + set {_image = newValue} + } + /// Returns true if `image` has been explicitly set. + public var hasImage: Bool {return self._image != nil} + /// Clears the value of `image`. Subsequent reads from it will return its default value. + public mutating func clearImage() {self._image = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _image: Flipcash_Blob_V1_Media? = nil +} + /// Cash content public struct Flipcash_Messaging_V1_CashContent: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the @@ -399,6 +500,18 @@ public struct Flipcash_Messaging_V1_CashContent: Sendable { /// Clears the value of `amount`. Subsequent reads from it will return its default value. public mutating func clearAmount() {self._amount = nil} + /// The user who received the cash. Always set in a group chat, where it + /// names which member was paid. In a DM it is set on new messages but unset + /// on older ones, whose receiver is the DM member who is not the sender. + public var receiver: Flipcash_Common_V1_UserId { + get {return _receiver ?? Flipcash_Common_V1_UserId()} + set {_receiver = newValue} + } + /// Returns true if `receiver` has been explicitly set. + public var hasReceiver: Bool {return self._receiver != nil} + /// Clears the value of `receiver`. Subsequent reads from it will return its default value. + public mutating func clearReceiver() {self._receiver = nil} + public var verb: Flipcash_Messaging_V1_CashContent.Verb = .sent public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -443,6 +556,7 @@ public struct Flipcash_Messaging_V1_CashContent: Sendable { fileprivate var _intentID: Flipcash_Common_V1_IntentId? = nil fileprivate var _amount: Flipcash_Common_V1_CryptoPaymentAmount? = nil + fileprivate var _receiver: Flipcash_Common_V1_UserId? = nil } /// Reply content @@ -513,15 +627,120 @@ public struct Flipcash_Messaging_V1_SystemContent: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// Best-effort, server-rendered text in the user's locale setting. Today this - /// is the only way to display a system message; once the structured `event` - /// oneof exists it becomes a fallback, rendered ONLY when the client does not - /// recognize the variant (old client, new server). It is not localized per + /// Best-effort, server-rendered text in the user's locale setting. Always + /// set. It is a fallback, rendered ONLY when the client does not recognize + /// the `type` variant (old client, new server). It is not localized per /// viewer — clients that know a variant render their own localized string. + /// It names no one: users are carried in the variant, so clients render + /// their current profiles, and the text never goes stale on a rename. public var fallbackText: String = String() + /// What this system message describes, in structured form. Always set. + public var type: Flipcash_Messaging_V1_SystemContent.OneOf_Type? = nil + + public var userJoined: Flipcash_Messaging_V1_SystemContent.UserJoined { + get { + if case .userJoined(let v)? = type {return v} + return Flipcash_Messaging_V1_SystemContent.UserJoined() + } + set {type = .userJoined(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() + /// What this system message describes, in structured form. Always set. + public enum OneOf_Type: Equatable, Sendable { + case userJoined(Flipcash_Messaging_V1_SystemContent.UserJoined) + + } + + /// A user joined the chat. + public struct UserJoined: 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 user that joined + public var userID: Flipcash_Common_V1_UserId { + get {return _userID ?? Flipcash_Common_V1_UserId()} + set {_userID = newValue} + } + /// Returns true if `userID` has been explicitly set. + public var hasUserID: Bool {return self._userID != nil} + /// Clears the value of `userID`. Subsequent reads from it will return its default value. + public mutating func clearUserID() {self._userID = nil} + + /// How they joined. Always set. A client that finds it unset (a way of + /// joining it does not know yet) renders a plain join. + public var method: Flipcash_Messaging_V1_SystemContent.UserJoined.OneOf_Method? = nil + + public var direct: Flipcash_Messaging_V1_SystemContent.UserJoined.Direct { + get { + if case .direct(let v)? = method {return v} + return Flipcash_Messaging_V1_SystemContent.UserJoined.Direct() + } + set {method = .direct(newValue)} + } + + public var admitted: Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted { + get { + if case .admitted(let v)? = method {return v} + return Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted() + } + set {method = .admitted(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + /// How they joined. Always set. A client that finds it unset (a way of + /// joining it does not know yet) renders a plain join. + public enum OneOf_Method: Equatable, Sendable { + case direct(Flipcash_Messaging_V1_SystemContent.UserJoined.Direct) + case admitted(Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted) + + } + + /// They joined on their own (e.g. a private group's creator rejoining + /// their group). + public struct Direct: 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 unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + + /// Someone let them in (e.g. a private group's creator admitting them + /// from its lobby). + public struct Admitted: 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 user that let them in + public var admittedBy: Flipcash_Common_V1_UserId { + get {return _admittedBy ?? Flipcash_Common_V1_UserId()} + set {_admittedBy = newValue} + } + /// Returns true if `admittedBy` has been explicitly set. + public var hasAdmittedBy: Bool {return self._admittedBy != nil} + /// Clears the value of `admittedBy`. Subsequent reads from it will return its default value. + public mutating func clearAdmittedBy() {self._admittedBy = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _admittedBy: Flipcash_Common_V1_UserId? = nil + } + + public init() {} + + fileprivate var _userID: Flipcash_Common_V1_UserId? = nil + } + public init() {} } @@ -543,10 +762,19 @@ public struct Flipcash_Messaging_V1_WidgetContent: Sendable { set {type = .shareProfile(newValue)} } + public var poll: Flipcash_Messaging_V1_PollWidget { + get { + if case .poll(let v)? = type {return v} + return Flipcash_Messaging_V1_PollWidget() + } + set {type = .poll(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Type: Equatable, Sendable { case shareProfile(Flipcash_Messaging_V1_ShareProfileWidget) + case poll(Flipcash_Messaging_V1_PollWidget) } @@ -576,6 +804,42 @@ public struct Flipcash_Messaging_V1_ShareProfileWidget: Sendable { fileprivate var _username: Flipcash_Common_V1_Username? = nil } +/// A widget that shows a poll. It carries only the poll's ID: clients get the +/// question, options, and state from poll.v1.Poll's GetPoll or GetPolls. +/// +/// The creator sends it after calling poll.v1.Poll.CreatePoll, and may send the +/// same poll in its chat any number of times. Deleting a message that carries +/// it does not delete the poll. +/// +/// In a group that is not private, it is sent as plain WidgetContent: +/// SendMessage accepts it only from the poll's creator, in the chat the poll +/// was created for, while the poll is OPEN; otherwise the send is DENIED. +/// +/// In a private group, it is sent inside EncryptedContent, so the server cannot +/// check any of this. Clients render the widget as unsupported if the poll's +/// chat_id is not the chat the message is in, or the poll's creator is not the +/// message's sender. +public struct Flipcash_Messaging_V1_PollWidget: 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 pollID: Flipcash_Poll_V1_PollId { + get {return _pollID ?? Flipcash_Poll_V1_PollId()} + set {_pollID = newValue} + } + /// Returns true if `pollID` has been explicitly set. + public var hasPollID: Bool {return self._pollID != nil} + /// Clears the value of `pollID`. Subsequent reads from it will return its default value. + public mutating func clearPollID() {self._pollID = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _pollID: Flipcash_Poll_V1_PollId? = nil +} + /// Deleted message content public struct Flipcash_Messaging_V1_DeletedContent: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the @@ -628,12 +892,15 @@ public struct Flipcash_Messaging_V1_DeletedContent: Sendable { /// /// The plaintext is a serialized Content message. The allowed plaintext types /// are: -/// - TextContent +/// - TextContent, with or without link previews; a preview's image is +/// described under "Media" below /// - MediaContent, as described under "Media" below /// - ReplyContent, whose own content is a TextContent or a MediaContent -/// The server cannot enforce this. A client that decrypts any other type, or -/// cannot decrypt the payload at all, renders the message as unsupported. A -/// decrypted Content never itself holds EncryptedContent. +/// The server cannot enforce this, nor anything TextContent says of its link +/// previews: a recipient applies those rules itself after decrypting, and +/// renders a preview that breaks one as unsupported. A client that decrypts +/// any other type, or cannot decrypt the payload at all, renders the message +/// as unsupported. A decrypted Content never itself holds EncryptedContent. /// /// DMs /// @@ -725,8 +992,10 @@ public struct Flipcash_Messaging_V1_DeletedContent: Sendable { /// /// Media /// -/// A MediaContent plaintext references blobs the sender uploaded for this chat -/// with blob.v1.InitiateExternalUploadRequest.end_to_end_encrypted_for. The +/// A MediaContent plaintext, and a LinkPreview.image in a TextContent +/// plaintext, reference blobs the sender uploaded for this chat with +/// blob.v1.InitiateExternalUploadRequest.end_to_end_encrypted_for; everything +/// below applies to both. The /// scheme also determines the format of every blob the plaintext references. /// Clients never take the format from the blob's server-side metadata, which /// the server could alter. For scheme X25519_XCHACHA20POLY1305, each blob is @@ -792,9 +1061,12 @@ public struct Flipcash_Messaging_V1_EncryptedContent: Sendable { public var nonce: Data = Data() /// The encrypted, serialized Content with the 16-byte Poly1305 tag - /// appended. The upper bound fits a MediaContent with a maximum-length - /// caption (4096 characters of up to 4 bytes each) wrapped in a - /// ReplyContent, plus the tag. + /// appended. The upper bound fits a TextContent of maximum length (4096 + /// characters of up to 4 bytes each) carrying one LinkPreview of maximum + /// length (a 2048-character url, a 256-character title and a + /// 512-character description, each of up to 4 bytes a character, and an + /// image rendition with its sender-set metadata) wrapped in a + /// ReplyContent, plus the tag. It is raised when the preview cap is. public var ciphertext: Data = Data() public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -1803,7 +2075,7 @@ extension Flipcash_Messaging_V1_Content: SwiftProtobuf.Message, SwiftProtobuf._M extension Flipcash_Messaging_V1_TextContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".TextContent" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}text\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}text\0\u{3}link_previews\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1812,6 +2084,7 @@ extension Flipcash_Messaging_V1_TextContent: SwiftProtobuf.Message, SwiftProtobu // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.text) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.linkPreviews) }() default: break } } @@ -1821,11 +2094,64 @@ extension Flipcash_Messaging_V1_TextContent: SwiftProtobuf.Message, SwiftProtobu if !self.text.isEmpty { try visitor.visitSingularStringField(value: self.text, fieldNumber: 1) } + if !self.linkPreviews.isEmpty { + try visitor.visitRepeatedMessageField(value: self.linkPreviews, fieldNumber: 2) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Flipcash_Messaging_V1_TextContent, rhs: Flipcash_Messaging_V1_TextContent) -> Bool { if lhs.text != rhs.text {return false} + if lhs.linkPreviews != rhs.linkPreviews {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Messaging_V1_LinkPreview: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LinkPreview" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}url\0\u{1}title\0\u{1}description\0\u{1}image\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.url) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.title) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.description_p) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._image) }() + 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.url.isEmpty { + try visitor.visitSingularStringField(value: self.url, fieldNumber: 1) + } + if !self.title.isEmpty { + try visitor.visitSingularStringField(value: self.title, fieldNumber: 2) + } + if !self.description_p.isEmpty { + try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 3) + } + try { if let v = self._image { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Messaging_V1_LinkPreview, rhs: Flipcash_Messaging_V1_LinkPreview) -> Bool { + if lhs.url != rhs.url {return false} + if lhs.title != rhs.title {return false} + if lhs.description_p != rhs.description_p {return false} + if lhs._image != rhs._image {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1833,7 +2159,7 @@ extension Flipcash_Messaging_V1_TextContent: SwiftProtobuf.Message, SwiftProtobu extension Flipcash_Messaging_V1_CashContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CashContent" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}intent_id\0\u{1}amount\0\u{2}\u{2}verb\0\u{c}\u{3}\u{1}") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}intent_id\0\u{1}amount\0\u{1}receiver\0\u{1}verb\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1843,6 +2169,7 @@ extension Flipcash_Messaging_V1_CashContent: SwiftProtobuf.Message, SwiftProtobu switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._intentID) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._amount) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._receiver) }() case 4: try { try decoder.decodeSingularEnumField(value: &self.verb) }() default: break } @@ -1860,6 +2187,9 @@ extension Flipcash_Messaging_V1_CashContent: SwiftProtobuf.Message, SwiftProtobu try { if let v = self._amount { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + try { if let v = self._receiver { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() if self.verb != .sent { try visitor.visitSingularEnumField(value: self.verb, fieldNumber: 4) } @@ -1869,6 +2199,7 @@ extension Flipcash_Messaging_V1_CashContent: SwiftProtobuf.Message, SwiftProtobu public static func ==(lhs: Flipcash_Messaging_V1_CashContent, rhs: Flipcash_Messaging_V1_CashContent) -> Bool { if lhs._intentID != rhs._intentID {return false} if lhs._amount != rhs._amount {return false} + if lhs._receiver != rhs._receiver {return false} if lhs.verb != rhs.verb {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true @@ -1959,7 +2290,7 @@ extension Flipcash_Messaging_V1_MediaContent: SwiftProtobuf.Message, SwiftProtob extension Flipcash_Messaging_V1_SystemContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".SystemContent" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}fallback_text\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}fallback_text\0\u{3}user_joined\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1968,20 +2299,166 @@ extension Flipcash_Messaging_V1_SystemContent: SwiftProtobuf.Message, SwiftProto // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.fallbackText) }() + case 2: try { + var v: Flipcash_Messaging_V1_SystemContent.UserJoined? + var hadOneofValue = false + if let current = self.type { + hadOneofValue = true + if case .userJoined(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.type = .userJoined(v) + } + }() 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.fallbackText.isEmpty { try visitor.visitSingularStringField(value: self.fallbackText, fieldNumber: 1) } + try { if case .userJoined(let v)? = self.type { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Flipcash_Messaging_V1_SystemContent, rhs: Flipcash_Messaging_V1_SystemContent) -> Bool { if lhs.fallbackText != rhs.fallbackText {return false} + if lhs.type != rhs.type {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Messaging_V1_SystemContent.UserJoined: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Messaging_V1_SystemContent.protoMessageName + ".UserJoined" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_id\0\u{1}direct\0\u{1}admitted\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._userID) }() + case 2: try { + var v: Flipcash_Messaging_V1_SystemContent.UserJoined.Direct? + var hadOneofValue = false + if let current = self.method { + hadOneofValue = true + if case .direct(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.method = .direct(v) + } + }() + case 3: try { + var v: Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted? + var hadOneofValue = false + if let current = self.method { + hadOneofValue = true + if case .admitted(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.method = .admitted(v) + } + }() + 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._userID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + switch self.method { + case .direct?: try { + guard case .direct(let v)? = self.method else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .admitted?: try { + guard case .admitted(let v)? = self.method else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Messaging_V1_SystemContent.UserJoined, rhs: Flipcash_Messaging_V1_SystemContent.UserJoined) -> Bool { + if lhs._userID != rhs._userID {return false} + if lhs.method != rhs.method {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Messaging_V1_SystemContent.UserJoined.Direct: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Messaging_V1_SystemContent.UserJoined.protoMessageName + ".Direct" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Messaging_V1_SystemContent.UserJoined.Direct, rhs: Flipcash_Messaging_V1_SystemContent.UserJoined.Direct) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Flipcash_Messaging_V1_SystemContent.UserJoined.protoMessageName + ".Admitted" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}admitted_by\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._admittedBy) }() + 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._admittedBy { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted, rhs: Flipcash_Messaging_V1_SystemContent.UserJoined.Admitted) -> Bool { + if lhs._admittedBy != rhs._admittedBy {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1989,7 +2466,7 @@ extension Flipcash_Messaging_V1_SystemContent: SwiftProtobuf.Message, SwiftProto extension Flipcash_Messaging_V1_WidgetContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".WidgetContent" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}share_profile\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}share_profile\0\u{1}poll\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2010,6 +2487,19 @@ extension Flipcash_Messaging_V1_WidgetContent: SwiftProtobuf.Message, SwiftProto self.type = .shareProfile(v) } }() + case 2: try { + var v: Flipcash_Messaging_V1_PollWidget? + var hadOneofValue = false + if let current = self.type { + hadOneofValue = true + if case .poll(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.type = .poll(v) + } + }() default: break } } @@ -2020,9 +2510,17 @@ extension Flipcash_Messaging_V1_WidgetContent: SwiftProtobuf.Message, SwiftProto // 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 case .shareProfile(let v)? = self.type { + switch self.type { + case .shareProfile?: try { + guard case .shareProfile(let v)? = self.type else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() + }() + case .poll?: try { + guard case .poll(let v)? = self.type else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } try unknownFields.traverse(visitor: &visitor) } @@ -2067,6 +2565,40 @@ extension Flipcash_Messaging_V1_ShareProfileWidget: SwiftProtobuf.Message, Swift } } +extension Flipcash_Messaging_V1_PollWidget: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PollWidget" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}poll_id\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._pollID) }() + 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._pollID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Messaging_V1_PollWidget, rhs: Flipcash_Messaging_V1_PollWidget) -> Bool { + if lhs._pollID != rhs._pollID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Flipcash_Messaging_V1_DeletedContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".DeletedContent" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}deleted_ts\0\u{3}deleted_by\0") diff --git a/Sources/Flipcash2ClientProtocol/poll_v1_model.pb.swift b/Sources/Flipcash2ClientProtocol/poll_v1_model.pb.swift new file mode 100644 index 0000000..e4ab39b --- /dev/null +++ b/Sources/Flipcash2ClientProtocol/poll_v1_model.pb.swift @@ -0,0 +1,1382 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: poll/v1/model.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +/// Mode is how votes are weighted, which also determines the shape of the +/// results (see Results). +public enum Flipcash_Poll_V1_Mode: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case unknown // = 0 + + /// Each voter adds 1 to every option they chose. Results are CountResults, + /// or WithheldResults if too few voters were counted. + case count // = 1 + + /// Each voter adds their balance of BalanceParameters.mint to every option + /// they chose. Balances are read once, when the poll closes, not when the + /// vote is cast: every token sits in exactly one wallet at that moment, so + /// moving funds between accounts cannot count the same tokens twice. A + /// voter who chose several options adds their full balance to each. + /// Results are RankedResults, which order the options by the balance + /// behind them and show how many voted but no amounts, so they are never + /// withheld. + case balance // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .unknown + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .count + case 2: self = .balance + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .unknown: return 0 + case .count: return 1 + case .balance: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Poll_V1_Mode] = [ + .unknown, + .count, + .balance, + ] + +} + +/// PollId is the server-assigned identity of a poll. It is what a +/// messaging.v1.PollWidget carries, and what every Poll RPC is keyed by. +public struct Flipcash_Poll_V1_PollId: 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 value: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// IdempotencyKey lets a client safely retry CreatePoll. The server derives the +/// poll's identity from the caller and this key, so a retry with the same key +/// returns the original poll instead of creating a duplicate. Which parameters +/// were sent is not part of the identity: a retry with different parameters +/// still returns the original. +/// +/// The key is owned by the client and is typically a randomly generated UUID. +/// It is never exposed as the poll's identity. +public struct Flipcash_Poll_V1_IdempotencyKey: 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 value: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// PollDefinition is what the creator chose for the poll. It is set on +/// CreatePoll and never changes afterward. +/// +/// The question and option text are in `text`, which is plaintext in a group +/// that is not private and encrypted in a private group. Everything else is +/// plaintext everywhere, because the server needs it to accept votes and count +/// them. +public struct Flipcash_Poll_V1_PollDefinition: 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 number of options. Votes and results refer to options by their + /// position, from 0 to option_count - 1. For plaintext text, it must equal + /// the number of options in it. For encrypted text, the server cannot check + /// it, so clients render a poll whose decrypted option count differs as + /// unsupported. + public var optionCount: UInt32 = 0 + + /// How votes are weighted. + public var mode: Flipcash_Poll_V1_Mode = .unknown + + /// Whether a voter may choose more than one option. If false, a vote + /// selects exactly one option. + public var allowsMultipleChoices: Bool = false + + /// When the poll closes. It must be at least 5 minutes and at most 30 days + /// after the poll is created. This is the only way a poll closes: nobody, + /// including the creator, can close it earlier, so nobody controls when a + /// BALANCE poll's balances are read. + public var endsAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _endsAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_endsAt = newValue} + } + /// Returns true if `endsAt` has been explicitly set. + public var hasEndsAt: Bool {return self._endsAt != nil} + /// Clears the value of `endsAt`. Subsequent reads from it will return its default value. + public mutating func clearEndsAt() {self._endsAt = nil} + + /// Required when mode == BALANCE, and must be unset otherwise. + public var balanceParameters: Flipcash_Poll_V1_BalanceParameters { + get {return _balanceParameters ?? Flipcash_Poll_V1_BalanceParameters()} + set {_balanceParameters = newValue} + } + /// Returns true if `balanceParameters` has been explicitly set. + public var hasBalanceParameters: Bool {return self._balanceParameters != nil} + /// Clears the value of `balanceParameters`. Subsequent reads from it will return its default value. + public mutating func clearBalanceParameters() {self._balanceParameters = nil} + + /// The question and option text. + public var text: Flipcash_Poll_V1_PollDefinition.OneOf_Text? = nil + + /// Required in a group that is not private, and moderated. + public var plaintext: Flipcash_Poll_V1_PollText { + get { + if case .plaintext(let v)? = text {return v} + return Flipcash_Poll_V1_PollText() + } + set {text = .plaintext(newValue)} + } + + /// Required in a private group. Not moderated, since the server cannot + /// read it. + public var encrypted: Flipcash_Poll_V1_EncryptedPollText { + get { + if case .encrypted(let v)? = text {return v} + return Flipcash_Poll_V1_EncryptedPollText() + } + set {text = .encrypted(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + /// The question and option text. + public enum OneOf_Text: Equatable, Sendable { + /// Required in a group that is not private, and moderated. + case plaintext(Flipcash_Poll_V1_PollText) + /// Required in a private group. Not moderated, since the server cannot + /// read it. + case encrypted(Flipcash_Poll_V1_EncryptedPollText) + + } + + public init() {} + + fileprivate var _endsAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _balanceParameters: Flipcash_Poll_V1_BalanceParameters? = nil +} + +/// PollText is the human-readable part of a poll. +public struct Flipcash_Poll_V1_PollText: 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 question being asked. + public var question: String = String() + + /// The options voters choose between, in the order votes and results refer + /// to them. + public var options: [Flipcash_Poll_V1_Option] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// Option is one choice in a poll. +public struct Flipcash_Poll_V1_Option: 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 text: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// EncryptedPollText is a private group's PollText, encrypted with the chat +/// key every member holds. The server stores it as-is and cannot read it. +/// +/// For scheme CHAT_KEY_XCHACHA20POLY1305, the creator encrypts with +/// XChaCha20-Poly1305, as for messaging.v1.EncryptedContent but with its own +/// aad: +/// key = the group's chat key +/// nonce = 24 fresh random bytes, set in `nonce` below +/// plaintext = the serialized PollText +/// aad = "flipcash-group-poll-e2ee-v1" || chat_id || creator_id +/// chat_id is the raw bytes of common.v1.ChatId.value, and creator_id is the +/// raw bytes of common.v1.UserId.value for PollMetadata.creator. Never reuse a +/// nonce, and do not substitute a 12-byte-nonce AEAD. +/// +/// A member decrypts with the chat key and the same aad, so the server cannot +/// move the text to another chat or attribute it to another creator, and the +/// distinct prefix keeps poll text and message content from being swapped for +/// each other. The poll ID is not bound, since the server assigns it after the +/// text is encrypted. +public struct Flipcash_Poll_V1_EncryptedPollText: 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 encryption scheme used. Clients render a poll with an unrecognized + /// scheme as unsupported. + public var scheme: Flipcash_Poll_V1_EncryptedPollText.Scheme = .unknown + + /// The random XChaCha20-Poly1305 nonce, unique per encryption. + public var nonce: Data = Data() + + /// The encrypted, serialized PollText with the 16-byte Poly1305 tag + /// appended. The upper bound fits a PollText with a maximum-length question + /// and the maximum number of maximum-length options (up to 4 bytes per + /// character), plus the tag. + public var ciphertext: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Scheme: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case unknown // = 0 + + /// The private group's chat key. + case chatKeyXchacha20Poly1305 // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .unknown + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .chatKeyXchacha20Poly1305 + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .unknown: return 0 + case .chatKeyXchacha20Poly1305: return 1 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Poll_V1_EncryptedPollText.Scheme] = [ + .unknown, + .chatKeyXchacha20Poly1305, + ] + + } + + public init() {} +} + +/// BalanceParameters configures a BALANCE poll. +public struct Flipcash_Poll_V1_BalanceParameters: 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 mint whose balance is a voter's weight. Clients default to the mint + /// in the group's MinimumBalanceRequirement. + public var mint: Flipcash_Common_V1_PublicKey { + get {return _mint ?? Flipcash_Common_V1_PublicKey()} + set {_mint = newValue} + } + /// Returns true if `mint` has been explicitly set. + public var hasMint: Bool {return self._mint != nil} + /// Clears the value of `mint`. Subsequent reads from it will return its default value. + public mutating func clearMint() {self._mint = nil} + + /// ISO 4217 alpha-3 currency code that the results' native amounts are + /// valued in, at the exchange rate when the poll closes. + public var currency: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _mint: Flipcash_Common_V1_PublicKey? = nil +} + +/// PollMetadata is a poll: its identity, definition, and current state. +public struct Flipcash_Poll_V1_PollMetadata: @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 pollID: Flipcash_Poll_V1_PollId { + get {return _storage._pollID ?? Flipcash_Poll_V1_PollId()} + set {_uniqueStorage()._pollID = newValue} + } + /// Returns true if `pollID` has been explicitly set. + public var hasPollID: Bool {return _storage._pollID != nil} + /// Clears the value of `pollID`. Subsequent reads from it will return its default value. + public mutating func clearPollID() {_uniqueStorage()._pollID = nil} + + /// The chat the poll was created for. Anyone who can read this chat in + /// full can see the poll: its members and anyone who satisfies its + /// listener rules in a group that is not private, and only its members in + /// a private group. Someone who can only preview the chat sees the + /// PollWidget but not the poll. Only the chat's members can vote. Clients + /// render a PollWidget whose poll belongs to a different chat than the + /// message carrying it as unsupported. + public var chatID: Flipcash_Common_V1_ChatId { + get {return _storage._chatID ?? Flipcash_Common_V1_ChatId()} + set {_uniqueStorage()._chatID = newValue} + } + /// Returns true if `chatID` has been explicitly set. + public var hasChatID: Bool {return _storage._chatID != nil} + /// Clears the value of `chatID`. Subsequent reads from it will return its default value. + public mutating func clearChatID() {_uniqueStorage()._chatID = nil} + + /// The user that created the poll, who in a group that is not private is + /// the only user that can send it. + public var creator: Flipcash_Common_V1_UserId { + get {return _storage._creator ?? Flipcash_Common_V1_UserId()} + set {_uniqueStorage()._creator = newValue} + } + /// Returns true if `creator` has been explicitly set. + public var hasCreator: Bool {return _storage._creator != nil} + /// Clears the value of `creator`. Subsequent reads from it will return its default value. + public mutating func clearCreator() {_uniqueStorage()._creator = nil} + + /// What the creator chose. Never changes. + public var definition: Flipcash_Poll_V1_PollDefinition { + get {return _storage._definition ?? Flipcash_Poll_V1_PollDefinition()} + set {_uniqueStorage()._definition = newValue} + } + /// Returns true if `definition` has been explicitly set. + public var hasDefinition: Bool {return _storage._definition != nil} + /// Clears the value of `definition`. Subsequent reads from it will return its default value. + public mutating func clearDefinition() {_uniqueStorage()._definition = nil} + + /// When the poll was created. + public var createdAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _storage._createdAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_uniqueStorage()._createdAt = newValue} + } + /// Returns true if `createdAt` has been explicitly set. + public var hasCreatedAt: Bool {return _storage._createdAt != nil} + /// Clears the value of `createdAt`. Subsequent reads from it will return its default value. + public mutating func clearCreatedAt() {_uniqueStorage()._createdAt = nil} + + /// The poll's current state. + public var state: Flipcash_Poll_V1_PollState { + get {return _storage._state ?? Flipcash_Poll_V1_PollState()} + set {_uniqueStorage()._state = newValue} + } + /// Returns true if `state` has been explicitly set. + public var hasState: Bool {return _storage._state != nil} + /// Clears the value of `state`. Subsequent reads from it will return its default value. + public mutating func clearState() {_uniqueStorage()._state = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +/// PollState is the changing part of a poll. +/// +/// Results are hidden while the poll is open: nobody, including the creator, +/// can see any totals or how many people have voted until the poll is CLOSED. +/// Individual votes are never revealed to anyone but the voter, before or after +/// the poll closes. +/// +/// PollState deliberately carries no version: a value that changed on every +/// vote would let anyone watching an open poll see when votes are cast. Status +/// only moves forward (OPEN, then TALLYING, then CLOSED), so clients keep the +/// copy with the most advanced status and never move a poll's status +/// backward. For self_vote, a client that has called Vote trusts its latest +/// VoteResponse over any GetPoll or GetPolls response it requested before +/// that Vote completed. +public struct Flipcash_Poll_V1_PollState: 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 status: Flipcash_Poll_V1_PollState.Status = .unknown + + /// Positions of the options the caller has currently selected. Empty if + /// the caller hasn't voted or removed their vote. + /// + /// Per-viewer, like messaging.v1.EmojiReaction.self_reactor: everything + /// else in PollState is the same for every viewer, but this is computed + /// for the caller. + public var selfVote: [UInt32] = [] + + /// When the poll stopped accepting votes. Set once status is TALLYING or + /// CLOSED. At or shortly after PollDefinition.ends_at. + public var closedAt: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _closedAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_closedAt = newValue} + } + /// Returns true if `closedAt` has been explicitly set. + public var hasClosedAt: Bool {return self._closedAt != nil} + /// Clears the value of `closedAt`. Subsequent reads from it will return its default value. + public mutating func clearClosedAt() {self._closedAt = nil} + + /// The final results. Set only when status == CLOSED. + public var results: Flipcash_Poll_V1_Results { + get {return _results ?? Flipcash_Poll_V1_Results()} + set {_results = newValue} + } + /// Returns true if `results` has been explicitly set. + public var hasResults: Bool {return self._results != nil} + /// Clears the value of `results`. Subsequent reads from it will return its default value. + public mutating func clearResults() {self._results = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Status: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case unknown // = 0 + + /// Accepting votes. + case `open` // = 1 + + /// No longer accepting votes, and results are being computed. A + /// BALANCE poll reads every counted voter's balance at this point, so + /// results may take a moment to appear. + case tallying // = 2 + + /// Results are available and final. + case closed // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .unknown + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .open + case 2: self = .tallying + case 3: self = .closed + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .unknown: return 0 + case .open: return 1 + case .tallying: return 2 + case .closed: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Poll_V1_PollState.Status] = [ + .unknown, + .open, + .tallying, + .closed, + ] + + } + + public init() {} + + fileprivate var _closedAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + fileprivate var _results: Flipcash_Poll_V1_Results? = nil +} + +/// Results are a closed poll's final totals. Every vote is counted: only +/// members can vote, and a vote still counts if the voter has since left the +/// chat. The variant is CountResults for a COUNT poll, unless its totals are +/// withheld, and RankedResults for a BALANCE poll. +public struct Flipcash_Poll_V1_Results: 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 type: Flipcash_Poll_V1_Results.OneOf_Type? = nil + + public var count: Flipcash_Poll_V1_CountResults { + get { + if case .count(let v)? = type {return v} + return Flipcash_Poll_V1_CountResults() + } + set {type = .count(newValue)} + } + + /// Not sent to clients: the server keeps a BALANCE poll's totals and + /// shows only their ranking (see RankedResults). + public var balance: Flipcash_Poll_V1_BalanceResults { + get { + if case .balance(let v)? = type {return v} + return Flipcash_Poll_V1_BalanceResults() + } + set {type = .balance(newValue)} + } + + public var withheld: Flipcash_Poll_V1_WithheldResults { + get { + if case .withheld(let v)? = type {return v} + return Flipcash_Poll_V1_WithheldResults() + } + set {type = .withheld(newValue)} + } + + public var ranked: Flipcash_Poll_V1_RankedResults { + get { + if case .ranked(let v)? = type {return v} + return Flipcash_Poll_V1_RankedResults() + } + set {type = .ranked(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum OneOf_Type: Equatable, Sendable { + case count(Flipcash_Poll_V1_CountResults) + /// Not sent to clients: the server keeps a BALANCE poll's totals and + /// shows only their ranking (see RankedResults). + case balance(Flipcash_Poll_V1_BalanceResults) + case withheld(Flipcash_Poll_V1_WithheldResults) + case ranked(Flipcash_Poll_V1_RankedResults) + + } + + public init() {} +} + +/// WithheldResults replaces a closed COUNT poll's totals when too few voters +/// were counted for the totals to be shown without exposing how individual +/// members voted. With only one voter, for example, any totals would be that +/// voter's own choice. A BALANCE poll's results are never withheld, since its +/// RankedResults show no totals. +/// +/// The minimum number of counted voters is set by the server and is not part +/// of the contract. Withheld results reveal only that fewer voters than that +/// minimum were counted. They are final: a poll whose results are withheld +/// never shows totals. +public struct Flipcash_Poll_V1_WithheldResults: 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 unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// Results for a COUNT poll. +public struct Flipcash_Poll_V1_CountResults: 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 number of distinct voters counted. This is the denominator for each + /// option's percentage. When multiple choices are allowed, option + /// percentages can add up to more than 100%. + public var totalVoters: UInt64 = 0 + + /// The number of voters that chose each option, in option order. + public var optionVoters: [UInt64] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// The totals behind a BALANCE poll's results. The server records these when +/// the poll closes but does not send them to clients, who see only +/// RankedResults: members' balances can often be looked up, so the totals could +/// reveal who voted and how. +public struct Flipcash_Poll_V1_BalanceResults: 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 sum of the counted voters' balances when the poll closed, with each + /// voter counted once even if they chose several options. This is the + /// denominator for each option's percentage. When multiple choices are + /// allowed, option percentages can add up to more than 100%. + public var total: Flipcash_Common_V1_CryptoPaymentAmount { + get {return _total ?? Flipcash_Common_V1_CryptoPaymentAmount()} + set {_total = newValue} + } + /// Returns true if `total` has been explicitly set. + public var hasTotal: Bool {return self._total != nil} + /// Clears the value of `total`. Subsequent reads from it will return its default value. + public mutating func clearTotal() {self._total = nil} + + /// The balance behind each option, in option order. + public var optionBalances: [Flipcash_Common_V1_CryptoPaymentAmount] = [] + + /// The number of distinct voters counted, including those holding none of + /// the mint, whose votes add nothing to any balance. It is what + /// RankedResults.total_voters shows. + public var totalVoters: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _total: Flipcash_Common_V1_CryptoPaymentAmount? = nil +} + +/// Results for a BALANCE poll: the options ranked by the balance behind each, +/// and how many voted, with no amounts and no per-option voter counts, so they +/// reveal neither how much was voted nor by whom. +public struct Flipcash_Poll_V1_RankedResults: 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. + + /// Each option's rank, in option order. 1 is the option with the most + /// balance behind it. Options with equal balances share a rank, and the + /// next rank skips the tied positions: balances of 30, 20, 20 and 10 rank + /// 1, 2, 2 and 4. Every option is ranked, including those with no balance + /// behind them, which share the last rank. + public var optionRanks: [UInt32] = [] + + /// The number of distinct voters counted, as for CountResults.total_voters. + /// Every voter counts, including one holding none of the mint, whose vote + /// moves no option's rank. + public var totalVoters: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "flipcash.poll.v1" + +extension Flipcash_Poll_V1_Mode: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}COUNT\0\u{1}BALANCE\0") +} + +extension Flipcash_Poll_V1_PollId: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PollId" + 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.decodeSingularBytesField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.value.isEmpty { + try visitor.visitSingularBytesField(value: self.value, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_PollId, rhs: Flipcash_Poll_V1_PollId) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_IdempotencyKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".IdempotencyKey" + 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.decodeSingularBytesField(value: &self.value) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.value.isEmpty { + try visitor.visitSingularBytesField(value: self.value, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_IdempotencyKey, rhs: Flipcash_Poll_V1_IdempotencyKey) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_PollDefinition: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PollDefinition" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}option_count\0\u{1}mode\0\u{3}allows_multiple_choices\0\u{3}ends_at\0\u{3}balance_parameters\0\u{1}plaintext\0\u{1}encrypted\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.decodeSingularUInt32Field(value: &self.optionCount) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.mode) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.allowsMultipleChoices) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._endsAt) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._balanceParameters) }() + case 6: try { + var v: Flipcash_Poll_V1_PollText? + var hadOneofValue = false + if let current = self.text { + hadOneofValue = true + if case .plaintext(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.text = .plaintext(v) + } + }() + case 7: try { + var v: Flipcash_Poll_V1_EncryptedPollText? + var hadOneofValue = false + if let current = self.text { + hadOneofValue = true + if case .encrypted(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.text = .encrypted(v) + } + }() + 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.optionCount != 0 { + try visitor.visitSingularUInt32Field(value: self.optionCount, fieldNumber: 1) + } + if self.mode != .unknown { + try visitor.visitSingularEnumField(value: self.mode, fieldNumber: 2) + } + if self.allowsMultipleChoices != false { + try visitor.visitSingularBoolField(value: self.allowsMultipleChoices, fieldNumber: 3) + } + try { if let v = self._endsAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if let v = self._balanceParameters { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + switch self.text { + case .plaintext?: try { + guard case .plaintext(let v)? = self.text else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + }() + case .encrypted?: try { + guard case .encrypted(let v)? = self.text else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_PollDefinition, rhs: Flipcash_Poll_V1_PollDefinition) -> Bool { + if lhs.optionCount != rhs.optionCount {return false} + if lhs.mode != rhs.mode {return false} + if lhs.allowsMultipleChoices != rhs.allowsMultipleChoices {return false} + if lhs._endsAt != rhs._endsAt {return false} + if lhs._balanceParameters != rhs._balanceParameters {return false} + if lhs.text != rhs.text {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_PollText: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PollText" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}question\0\u{1}options\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.question) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.options) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.question.isEmpty { + try visitor.visitSingularStringField(value: self.question, fieldNumber: 1) + } + if !self.options.isEmpty { + try visitor.visitRepeatedMessageField(value: self.options, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_PollText, rhs: Flipcash_Poll_V1_PollText) -> Bool { + if lhs.question != rhs.question {return false} + if lhs.options != rhs.options {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_Option: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Option" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}text\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.text) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.text.isEmpty { + try visitor.visitSingularStringField(value: self.text, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_Option, rhs: Flipcash_Poll_V1_Option) -> Bool { + if lhs.text != rhs.text {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_EncryptedPollText: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".EncryptedPollText" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}scheme\0\u{1}nonce\0\u{1}ciphertext\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.scheme) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.nonce) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.ciphertext) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.scheme != .unknown { + try visitor.visitSingularEnumField(value: self.scheme, fieldNumber: 1) + } + if !self.nonce.isEmpty { + try visitor.visitSingularBytesField(value: self.nonce, fieldNumber: 2) + } + if !self.ciphertext.isEmpty { + try visitor.visitSingularBytesField(value: self.ciphertext, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_EncryptedPollText, rhs: Flipcash_Poll_V1_EncryptedPollText) -> Bool { + if lhs.scheme != rhs.scheme {return false} + if lhs.nonce != rhs.nonce {return false} + if lhs.ciphertext != rhs.ciphertext {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_EncryptedPollText.Scheme: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}CHAT_KEY_XCHACHA20POLY1305\0") +} + +extension Flipcash_Poll_V1_BalanceParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BalanceParameters" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}mint\0\u{1}currency\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._mint) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.currency) }() + 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._mint { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.currency.isEmpty { + try visitor.visitSingularStringField(value: self.currency, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_BalanceParameters, rhs: Flipcash_Poll_V1_BalanceParameters) -> Bool { + if lhs._mint != rhs._mint {return false} + if lhs.currency != rhs.currency {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_PollMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PollMetadata" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}poll_id\0\u{3}chat_id\0\u{1}creator\0\u{1}definition\0\u{3}created_at\0\u{1}state\0") + + fileprivate class _StorageClass { + var _pollID: Flipcash_Poll_V1_PollId? = nil + var _chatID: Flipcash_Common_V1_ChatId? = nil + var _creator: Flipcash_Common_V1_UserId? = nil + var _definition: Flipcash_Poll_V1_PollDefinition? = nil + var _createdAt: SwiftProtobuf.Google_Protobuf_Timestamp? = nil + var _state: Flipcash_Poll_V1_PollState? = 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) { + _pollID = source._pollID + _chatID = source._chatID + _creator = source._creator + _definition = source._definition + _createdAt = source._createdAt + _state = source._state + } + } + + 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._pollID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._chatID) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._creator) }() + case 4: try { try decoder.decodeSingularMessageField(value: &_storage._definition) }() + case 5: try { try decoder.decodeSingularMessageField(value: &_storage._createdAt) }() + case 6: try { try decoder.decodeSingularMessageField(value: &_storage._state) }() + 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._pollID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._chatID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._creator { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = _storage._definition { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if let v = _storage._createdAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + try { if let v = _storage._state { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_PollMetadata, rhs: Flipcash_Poll_V1_PollMetadata) -> 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._pollID != rhs_storage._pollID {return false} + if _storage._chatID != rhs_storage._chatID {return false} + if _storage._creator != rhs_storage._creator {return false} + if _storage._definition != rhs_storage._definition {return false} + if _storage._createdAt != rhs_storage._createdAt {return false} + if _storage._state != rhs_storage._state {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_PollState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".PollState" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}status\0\u{3}self_vote\0\u{3}closed_at\0\u{1}results\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.status) }() + case 2: try { try decoder.decodeRepeatedUInt32Field(value: &self.selfVote) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._closedAt) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._results) }() + 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.status != .unknown { + try visitor.visitSingularEnumField(value: self.status, fieldNumber: 1) + } + if !self.selfVote.isEmpty { + try visitor.visitPackedUInt32Field(value: self.selfVote, fieldNumber: 2) + } + try { if let v = self._closedAt { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = self._results { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_PollState, rhs: Flipcash_Poll_V1_PollState) -> Bool { + if lhs.status != rhs.status {return false} + if lhs.selfVote != rhs.selfVote {return false} + if lhs._closedAt != rhs._closedAt {return false} + if lhs._results != rhs._results {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_PollState.Status: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNKNOWN\0\u{1}OPEN\0\u{1}TALLYING\0\u{1}CLOSED\0") +} + +extension Flipcash_Poll_V1_Results: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Results" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}count\0\u{1}balance\0\u{1}withheld\0\u{1}ranked\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 { + var v: Flipcash_Poll_V1_CountResults? + var hadOneofValue = false + if let current = self.type { + hadOneofValue = true + if case .count(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.type = .count(v) + } + }() + case 2: try { + var v: Flipcash_Poll_V1_BalanceResults? + var hadOneofValue = false + if let current = self.type { + hadOneofValue = true + if case .balance(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.type = .balance(v) + } + }() + case 3: try { + var v: Flipcash_Poll_V1_WithheldResults? + var hadOneofValue = false + if let current = self.type { + hadOneofValue = true + if case .withheld(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.type = .withheld(v) + } + }() + case 4: try { + var v: Flipcash_Poll_V1_RankedResults? + var hadOneofValue = false + if let current = self.type { + hadOneofValue = true + if case .ranked(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.type = .ranked(v) + } + }() + 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 + switch self.type { + case .count?: try { + guard case .count(let v)? = self.type else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + }() + case .balance?: try { + guard case .balance(let v)? = self.type else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .withheld?: try { + guard case .withheld(let v)? = self.type else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case .ranked?: try { + guard case .ranked(let v)? = self.type else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_Results, rhs: Flipcash_Poll_V1_Results) -> Bool { + if lhs.type != rhs.type {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_WithheldResults: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".WithheldResults" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_WithheldResults, rhs: Flipcash_Poll_V1_WithheldResults) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_CountResults: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CountResults" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}total_voters\0\u{3}option_voters\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.decodeSingularUInt64Field(value: &self.totalVoters) }() + case 2: try { try decoder.decodeRepeatedUInt64Field(value: &self.optionVoters) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.totalVoters != 0 { + try visitor.visitSingularUInt64Field(value: self.totalVoters, fieldNumber: 1) + } + if !self.optionVoters.isEmpty { + try visitor.visitPackedUInt64Field(value: self.optionVoters, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_CountResults, rhs: Flipcash_Poll_V1_CountResults) -> Bool { + if lhs.totalVoters != rhs.totalVoters {return false} + if lhs.optionVoters != rhs.optionVoters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_BalanceResults: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BalanceResults" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}total\0\u{3}option_balances\0\u{3}total_voters\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._total) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.optionBalances) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &self.totalVoters) }() + 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._total { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.optionBalances.isEmpty { + try visitor.visitRepeatedMessageField(value: self.optionBalances, fieldNumber: 2) + } + if self.totalVoters != 0 { + try visitor.visitSingularUInt64Field(value: self.totalVoters, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_BalanceResults, rhs: Flipcash_Poll_V1_BalanceResults) -> Bool { + if lhs._total != rhs._total {return false} + if lhs.optionBalances != rhs.optionBalances {return false} + if lhs.totalVoters != rhs.totalVoters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_RankedResults: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".RankedResults" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}option_ranks\0\u{3}total_voters\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.decodeRepeatedUInt32Field(value: &self.optionRanks) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.totalVoters) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.optionRanks.isEmpty { + try visitor.visitPackedUInt32Field(value: self.optionRanks, fieldNumber: 1) + } + if self.totalVoters != 0 { + try visitor.visitSingularUInt64Field(value: self.totalVoters, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_RankedResults, rhs: Flipcash_Poll_V1_RankedResults) -> Bool { + if lhs.optionRanks != rhs.optionRanks {return false} + if lhs.totalVoters != rhs.totalVoters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/Flipcash2ClientProtocol/poll_v1_poll_service.grpc.swift b/Sources/Flipcash2ClientProtocol/poll_v1_poll_service.grpc.swift new file mode 100644 index 0000000..fe1ecc3 --- /dev/null +++ b/Sources/Flipcash2ClientProtocol/poll_v1_poll_service.grpc.swift @@ -0,0 +1,717 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. +// Source: poll/v1/poll_service.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/grpc/grpc-swift + +import GRPCCore +import GRPCProtobuf + +// MARK: - flipcash.poll.v1.Poll + +/// Namespace containing generated types for the "flipcash.poll.v1.Poll" service. +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +public enum Flipcash_Poll_V1_Poll { + /// Service descriptor for the "flipcash.poll.v1.Poll" service. + public static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.poll.v1.Poll") + /// Namespace for method metadata. + public enum Method { + /// Namespace for "CreatePoll" metadata. + public enum CreatePoll { + /// Request type for "CreatePoll". + public typealias Input = Flipcash_Poll_V1_CreatePollRequest + /// Response type for "CreatePoll". + public typealias Output = Flipcash_Poll_V1_CreatePollResponse + /// Descriptor for "CreatePoll". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.poll.v1.Poll"), + method: "CreatePoll" + ) + } + /// Namespace for "GetPoll" metadata. + public enum GetPoll { + /// Request type for "GetPoll". + public typealias Input = Flipcash_Poll_V1_GetPollRequest + /// Response type for "GetPoll". + public typealias Output = Flipcash_Poll_V1_GetPollResponse + /// Descriptor for "GetPoll". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.poll.v1.Poll"), + method: "GetPoll" + ) + } + /// Namespace for "GetPolls" metadata. + public enum GetPolls { + /// Request type for "GetPolls". + public typealias Input = Flipcash_Poll_V1_GetPollsRequest + /// Response type for "GetPolls". + public typealias Output = Flipcash_Poll_V1_GetPollsResponse + /// Descriptor for "GetPolls". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.poll.v1.Poll"), + method: "GetPolls" + ) + } + /// Namespace for "Vote" metadata. + public enum Vote { + /// Request type for "Vote". + public typealias Input = Flipcash_Poll_V1_VoteRequest + /// Response type for "Vote". + public typealias Output = Flipcash_Poll_V1_VoteResponse + /// Descriptor for "Vote". + public static let descriptor = GRPCCore.MethodDescriptor( + service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.poll.v1.Poll"), + method: "Vote" + ) + } + /// Descriptors for all methods in the "flipcash.poll.v1.Poll" service. + public static let descriptors: [GRPCCore.MethodDescriptor] = [ + CreatePoll.descriptor, + GetPoll.descriptor, + GetPolls.descriptor, + Vote.descriptor + ] + } +} + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension GRPCCore.ServiceDescriptor { + /// Service descriptor for the "flipcash.poll.v1.Poll" service. + public static let flipcash_poll_v1_Poll = GRPCCore.ServiceDescriptor(fullyQualifiedService: "flipcash.poll.v1.Poll") +} + +// MARK: flipcash.poll.v1.Poll (client) + +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension Flipcash_Poll_V1_Poll { + /// Generated client protocol for the "flipcash.poll.v1.Poll" service. + /// + /// You don't need to implement this protocol directly, use the generated + /// implementation, ``Client``. + /// + /// > Source IDL Documentation: + /// > + /// > Poll manages polls in group chats, private or not. DMs are not supported. + /// > + /// > A poll is shown in a chat as a message. The creator first calls CreatePoll, + /// > then sends the returned PollId with Messaging.SendMessage as a + /// > messaging.v1.PollWidget. The message carries only the PollId; clients get the + /// > question, options, and state from GetPoll or GetPolls. + /// > + /// > A poll belongs to the chat it was created for. Its creator may send it in + /// > that chat any number of times, for example to bring an older poll back to + /// > the bottom of the chat. Every message carrying it shows the same poll. + /// > Deleting a message that carries a poll does not delete the poll. + /// > + /// > In a group that is not private, the widget is sent as plain WidgetContent, + /// > so the server knows when the poll is first sent: the poll goes live then. + /// > + /// > In a private group, the question and option text are encrypted with the + /// > chat key (see EncryptedPollText), and the widget is sent inside + /// > messaging.v1.EncryptedContent like everything else. The server never learns + /// > which messages carry the poll, so the poll is live as soon as it is + /// > created. The server still sees the poll's structure (option count, mode, + /// > ends_at, balance parameters) and every vote, since it has to count them. + public protocol ClientProtocol: Sendable { + /// Call the "CreatePoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreatePoll creates a poll for a group chat. The caller must be able to + /// > send messages in the chat. A plaintext question and its options are + /// > moderated together, as one text; encrypted text is not. + /// > + /// > In a group that is not private, the poll is not visible to anyone but + /// > its creator, and does not accept votes, until it has been sent in a + /// > message, and a poll that is never sent is eventually discarded. In a + /// > private group, the poll is visible to members and accepts votes as soon + /// > as it is created. PollDefinition.ends_at is a fixed time, so the poll's + /// > clock runs from creation, not from when it is sent. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_CreatePollRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_CreatePollRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_CreatePollResponse` 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 createPoll( + 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 "GetPoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPoll gets a poll, including the caller's own vote and, once the poll + /// > is CLOSED, its results. The caller must be able to read the chat in + /// > full (see messaging.v1.ViewMode FULL): in a group that is not private, + /// > its members and anyone who satisfies its listener rules; in a private + /// > group, only its members. Being able to preview the chat is not enough. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_GetPollRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_GetPollRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_GetPollResponse` 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 getPoll( + 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 "GetPolls" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPolls is the batch version of GetPoll, for rendering a page of + /// > messages. Polls the caller cannot see, or that don't exist, are omitted. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_GetPollsRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_GetPollsRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_GetPollsResponse` 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 getPolls( + 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 "Vote" method. + /// + /// > Source IDL Documentation: + /// > + /// > Vote replaces the caller's entire selection in an OPEN poll. Calling it + /// > again with the same selection changes nothing; an empty selection + /// > removes the caller's vote. The caller must be a member of the chat. The + /// > chat's speaker rules don't apply: a member who cannot send messages in + /// > the chat can still vote. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_VoteRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_VoteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_VoteResponse` 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 vote( + 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.poll.v1.Poll" service. + /// + /// The ``Client`` provides an implementation of ``ClientProtocol`` which wraps + /// a `GRPCCore.GRPCCClient`. The underlying `GRPCClient` provides the long-lived + /// means of communication with the remote peer. + /// + /// > Source IDL Documentation: + /// > + /// > Poll manages polls in group chats, private or not. DMs are not supported. + /// > + /// > A poll is shown in a chat as a message. The creator first calls CreatePoll, + /// > then sends the returned PollId with Messaging.SendMessage as a + /// > messaging.v1.PollWidget. The message carries only the PollId; clients get the + /// > question, options, and state from GetPoll or GetPolls. + /// > + /// > A poll belongs to the chat it was created for. Its creator may send it in + /// > that chat any number of times, for example to bring an older poll back to + /// > the bottom of the chat. Every message carrying it shows the same poll. + /// > Deleting a message that carries a poll does not delete the poll. + /// > + /// > In a group that is not private, the widget is sent as plain WidgetContent, + /// > so the server knows when the poll is first sent: the poll goes live then. + /// > + /// > In a private group, the question and option text are encrypted with the + /// > chat key (see EncryptedPollText), and the widget is sent inside + /// > messaging.v1.EncryptedContent like everything else. The server never learns + /// > which messages carry the poll, so the poll is live as soon as it is + /// > created. The server still sees the poll's structure (option count, mode, + /// > ends_at, balance parameters) and every vote, since it has to count them. + public struct Client: ClientProtocol where Transport: GRPCCore.ClientTransport { + private let client: GRPCCore.GRPCClient + + /// Creates a new client wrapping the provided `GRPCCore.GRPCClient`. + /// + /// - Parameters: + /// - client: A `GRPCCore.GRPCClient` providing a communication channel to the service. + public init(wrapping client: GRPCCore.GRPCClient) { + self.client = client + } + + /// Call the "CreatePoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreatePoll creates a poll for a group chat. The caller must be able to + /// > send messages in the chat. A plaintext question and its options are + /// > moderated together, as one text; encrypted text is not. + /// > + /// > In a group that is not private, the poll is not visible to anyone but + /// > its creator, and does not accept votes, until it has been sent in a + /// > message, and a poll that is never sent is eventually discarded. In a + /// > private group, the poll is visible to members and accepts votes as soon + /// > as it is created. PollDefinition.ends_at is a fixed time, so the poll's + /// > clock runs from creation, not from when it is sent. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_CreatePollRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_CreatePollRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_CreatePollResponse` 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 createPoll( + 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_Poll_V1_Poll.Method.CreatePoll.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPoll gets a poll, including the caller's own vote and, once the poll + /// > is CLOSED, its results. The caller must be able to read the chat in + /// > full (see messaging.v1.ViewMode FULL): in a group that is not private, + /// > its members and anyone who satisfies its listener rules; in a private + /// > group, only its members. Being able to preview the chat is not enough. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_GetPollRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_GetPollRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_GetPollResponse` 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 getPoll( + 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_Poll_V1_Poll.Method.GetPoll.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPolls" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPolls is the batch version of GetPoll, for rendering a page of + /// > messages. Polls the caller cannot see, or that don't exist, are omitted. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_GetPollsRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_GetPollsRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_GetPollsResponse` 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 getPolls( + 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_Poll_V1_Poll.Method.GetPolls.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "Vote" method. + /// + /// > Source IDL Documentation: + /// > + /// > Vote replaces the caller's entire selection in an OPEN poll. Calling it + /// > again with the same selection changes nothing; an empty selection + /// > removes the caller's vote. The caller must be a member of the chat. The + /// > chat's speaker rules don't apply: a member who cannot send messages in + /// > the chat can still vote. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_VoteRequest` message. + /// - serializer: A serializer for `Flipcash_Poll_V1_VoteRequest` messages. + /// - deserializer: A deserializer for `Flipcash_Poll_V1_VoteResponse` 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 vote( + 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_Poll_V1_Poll.Method.Vote.descriptor, + serializer: serializer, + deserializer: deserializer, + options: options, + onResponse: handleResponse + ) + } + } +} + +// Helpers providing default arguments to 'ClientProtocol' methods. +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension Flipcash_Poll_V1_Poll.ClientProtocol { + /// Call the "CreatePoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreatePoll creates a poll for a group chat. The caller must be able to + /// > send messages in the chat. A plaintext question and its options are + /// > moderated together, as one text; encrypted text is not. + /// > + /// > In a group that is not private, the poll is not visible to anyone but + /// > its creator, and does not accept votes, until it has been sent in a + /// > message, and a poll that is never sent is eventually discarded. In a + /// > private group, the poll is visible to members and accepts votes as soon + /// > as it is created. PollDefinition.ends_at is a fixed time, so the poll's + /// > clock runs from creation, not from when it is sent. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_CreatePollRequest` 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 createPoll( + 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.createPoll( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPoll gets a poll, including the caller's own vote and, once the poll + /// > is CLOSED, its results. The caller must be able to read the chat in + /// > full (see messaging.v1.ViewMode FULL): in a group that is not private, + /// > its members and anyone who satisfies its listener rules; in a private + /// > group, only its members. Being able to preview the chat is not enough. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_GetPollRequest` 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 getPoll( + 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.getPoll( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPolls" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPolls is the batch version of GetPoll, for rendering a page of + /// > messages. Polls the caller cannot see, or that don't exist, are omitted. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_GetPollsRequest` 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 getPolls( + 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.getPolls( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } + + /// Call the "Vote" method. + /// + /// > Source IDL Documentation: + /// > + /// > Vote replaces the caller's entire selection in an OPEN poll. Calling it + /// > again with the same selection changes nothing; an empty selection + /// > removes the caller's vote. The caller must be a member of the chat. The + /// > chat's speaker rules don't apply: a member who cannot send messages in + /// > the chat can still vote. + /// + /// - Parameters: + /// - request: A request containing a single `Flipcash_Poll_V1_VoteRequest` 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 vote( + 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.vote( + request: request, + serializer: GRPCProtobuf.ProtobufSerializer(), + deserializer: GRPCProtobuf.ProtobufDeserializer(), + options: options, + onResponse: handleResponse + ) + } +} + +// Helpers providing sugared APIs for 'ClientProtocol' methods. +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) +extension Flipcash_Poll_V1_Poll.ClientProtocol { + /// Call the "CreatePoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > CreatePoll creates a poll for a group chat. The caller must be able to + /// > send messages in the chat. A plaintext question and its options are + /// > moderated together, as one text; encrypted text is not. + /// > + /// > In a group that is not private, the poll is not visible to anyone but + /// > its creator, and does not accept votes, until it has been sent in a + /// > message, and a poll that is never sent is eventually discarded. In a + /// > private group, the poll is visible to members and accepts votes as soon + /// > as it is created. PollDefinition.ends_at is a fixed time, so the poll's + /// > clock runs from creation, not from when it is sent. + /// + /// - 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 createPoll( + _ message: Flipcash_Poll_V1_CreatePollRequest, + 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.createPoll( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPoll" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPoll gets a poll, including the caller's own vote and, once the poll + /// > is CLOSED, its results. The caller must be able to read the chat in + /// > full (see messaging.v1.ViewMode FULL): in a group that is not private, + /// > its members and anyone who satisfies its listener rules; in a private + /// > group, only its members. Being able to preview the chat is not enough. + /// + /// - 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 getPoll( + _ message: Flipcash_Poll_V1_GetPollRequest, + 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.getPoll( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "GetPolls" method. + /// + /// > Source IDL Documentation: + /// > + /// > GetPolls is the batch version of GetPoll, for rendering a page of + /// > messages. Polls the caller cannot see, or that don't exist, are omitted. + /// + /// - 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 getPolls( + _ message: Flipcash_Poll_V1_GetPollsRequest, + 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.getPolls( + request: request, + options: options, + onResponse: handleResponse + ) + } + + /// Call the "Vote" method. + /// + /// > Source IDL Documentation: + /// > + /// > Vote replaces the caller's entire selection in an OPEN poll. Calling it + /// > again with the same selection changes nothing; an empty selection + /// > removes the caller's vote. The caller must be a member of the chat. The + /// > chat's speaker rules don't apply: a member who cannot send messages in + /// > the chat can still vote. + /// + /// - 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 vote( + _ message: Flipcash_Poll_V1_VoteRequest, + 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.vote( + request: request, + options: options, + onResponse: handleResponse + ) + } +} \ No newline at end of file diff --git a/Sources/Flipcash2ClientProtocol/poll_v1_poll_service.pb.swift b/Sources/Flipcash2ClientProtocol/poll_v1_poll_service.pb.swift new file mode 100644 index 0000000..6c6e78e --- /dev/null +++ b/Sources/Flipcash2ClientProtocol/poll_v1_poll_service.pb.swift @@ -0,0 +1,826 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: poll/v1/poll_service.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +public struct Flipcash_Poll_V1_CreatePollRequest: 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 group chat to create the poll for. + 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 definition: Flipcash_Poll_V1_PollDefinition { + get {return _definition ?? Flipcash_Poll_V1_PollDefinition()} + set {_definition = newValue} + } + /// Returns true if `definition` has been explicitly set. + public var hasDefinition: Bool {return self._definition != nil} + /// Clears the value of `definition`. Subsequent reads from it will return its default value. + public mutating func clearDefinition() {self._definition = nil} + + /// Key for retrying this request safely. A retry with the same key from the + /// same caller returns the poll the first attempt created, with result OK, + /// even if the parameters differ. + public var idempotencyKey: Flipcash_Poll_V1_IdempotencyKey { + get {return _idempotencyKey ?? Flipcash_Poll_V1_IdempotencyKey()} + set {_idempotencyKey = newValue} + } + /// Returns true if `idempotencyKey` has been explicitly set. + public var hasIdempotencyKey: Bool {return self._idempotencyKey != nil} + /// Clears the value of `idempotencyKey`. Subsequent reads from it will return its default value. + public mutating func clearIdempotencyKey() {self._idempotencyKey = 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 _definition: Flipcash_Poll_V1_PollDefinition? = nil + fileprivate var _idempotencyKey: Flipcash_Poll_V1_IdempotencyKey? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Poll_V1_CreatePollResponse: 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_Poll_V1_CreatePollResponse.Result = .ok + + /// The created poll. Set only when result == OK. + public var poll: Flipcash_Poll_V1_PollMetadata { + get {return _poll ?? Flipcash_Poll_V1_PollMetadata()} + set {_poll = newValue} + } + /// Returns true if `poll` has been explicitly set. + public var hasPoll: Bool {return self._poll != nil} + /// Clears the value of `poll`. Subsequent reads from it will return its default value. + public mutating func clearPoll() {self._poll = nil} + + /// The best-fit category that tripped moderation, mirroring the Moderation + /// service's vocabulary. Set only when result == TEXT_MODERATED; NONE + /// otherwise. + public var flaggedCategory: Flipcash_Moderation_V1_FlaggedCategory = .none + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// The caller is not a member of the chat, or does not satisfy its + /// speaker rules. + case denied // = 1 + + /// The chat is a DM. + case chatNotSupported // = 2 + + /// The text is encrypted and the chat is not a private group. + case encryptionNotAllowed // = 3 + + /// The chat is a private group and the text is not encrypted. + case encryptionRequired // = 4 + + /// The text is plaintext and option_count differs from its number of + /// options. + case optionCountMismatch // = 5 + + /// ends_at is less than 5 minutes or more than 30 days after now. + case invalidEndsAt // = 6 + + /// balance_parameters is missing for a BALANCE poll or set for a + /// COUNT poll, or its mint or currency is one the server cannot value. + case invalidBalanceParameters // = 7 + + /// The plaintext question and options, moderated together as one + /// text, were flagged. + case textModerated // = 8 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + case 2: self = .chatNotSupported + case 3: self = .encryptionNotAllowed + case 4: self = .encryptionRequired + case 5: self = .optionCountMismatch + case 6: self = .invalidEndsAt + case 7: self = .invalidBalanceParameters + case 8: self = .textModerated + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .chatNotSupported: return 2 + case .encryptionNotAllowed: return 3 + case .encryptionRequired: return 4 + case .optionCountMismatch: return 5 + case .invalidEndsAt: return 6 + case .invalidBalanceParameters: return 7 + case .textModerated: return 8 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Poll_V1_CreatePollResponse.Result] = [ + .ok, + .denied, + .chatNotSupported, + .encryptionNotAllowed, + .encryptionRequired, + .optionCountMismatch, + .invalidEndsAt, + .invalidBalanceParameters, + .textModerated, + ] + + } + + public init() {} + + fileprivate var _poll: Flipcash_Poll_V1_PollMetadata? = nil +} + +public struct Flipcash_Poll_V1_GetPollRequest: 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 pollID: Flipcash_Poll_V1_PollId { + get {return _pollID ?? Flipcash_Poll_V1_PollId()} + set {_pollID = newValue} + } + /// Returns true if `pollID` has been explicitly set. + public var hasPollID: Bool {return self._pollID != nil} + /// Clears the value of `pollID`. Subsequent reads from it will return its default value. + public mutating func clearPollID() {self._pollID = 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 _pollID: Flipcash_Poll_V1_PollId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Poll_V1_GetPollResponse: 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_Poll_V1_GetPollResponse.Result = .ok + + /// Set only when result == OK. + public var poll: Flipcash_Poll_V1_PollMetadata { + get {return _poll ?? Flipcash_Poll_V1_PollMetadata()} + set {_poll = newValue} + } + /// Returns true if `poll` has been explicitly set. + public var hasPoll: Bool {return self._poll != nil} + /// Clears the value of `poll`. Subsequent reads from it will return its default value. + public mutating func clearPoll() {self._poll = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// The caller cannot read the poll's chat in full. + case denied // = 1 + + /// The poll doesn't exist, or is in a group that is not private, has + /// not been sent yet, and the caller is not its creator. + 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_Poll_V1_GetPollResponse.Result] = [ + .ok, + .denied, + .notFound, + ] + + } + + public init() {} + + fileprivate var _poll: Flipcash_Poll_V1_PollMetadata? = nil +} + +public struct Flipcash_Poll_V1_GetPollsRequest: 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. + + /// Duplicates are ignored. + public var pollIds: [Flipcash_Poll_V1_PollId] = [] + + 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 _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Poll_V1_GetPollsResponse: 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_Poll_V1_GetPollsResponse.Result = .ok + + /// One entry per requested poll the caller can see, in no particular order. + /// Clients apply each as described on PollState, so a poll whose status is + /// behind the one the client already holds does not regress it. + public var polls: [Flipcash_Poll_V1_PollMetadata] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + case denied // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .ok + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ok + case 1: self = .denied + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Poll_V1_GetPollsResponse.Result] = [ + .ok, + .denied, + ] + + } + + public init() {} +} + +public struct Flipcash_Poll_V1_VoteRequest: 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 pollID: Flipcash_Poll_V1_PollId { + get {return _pollID ?? Flipcash_Poll_V1_PollId()} + set {_pollID = newValue} + } + /// Returns true if `pollID` has been explicitly set. + public var hasPollID: Bool {return self._pollID != nil} + /// Clears the value of `pollID`. Subsequent reads from it will return its default value. + public mutating func clearPollID() {self._pollID = nil} + + /// Positions in PollDefinition.options of every option the caller selects. + /// Replaces any earlier vote. Empty removes the caller's vote. More than + /// one is allowed only if PollDefinition.allows_multiple_choices is set. + public var optionIndices: [UInt32] = [] + + 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 _pollID: Flipcash_Poll_V1_PollId? = nil + fileprivate var _auth: Flipcash_Common_V1_Auth? = nil +} + +public struct Flipcash_Poll_V1_VoteResponse: 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_Poll_V1_VoteResponse.Result = .ok + + /// The poll's state after the vote, with self_vote reflecting it, when + /// result == OK. The poll's current state when result == POLL_CLOSED. + /// Unset otherwise. + public var state: Flipcash_Poll_V1_PollState { + get {return _state ?? Flipcash_Poll_V1_PollState()} + set {_state = newValue} + } + /// Returns true if `state` has been explicitly set. + public var hasState: Bool {return self._state != nil} + /// Clears the value of `state`. Subsequent reads from it will return its default value. + public mutating func clearState() {self._state = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ok // = 0 + + /// The caller is not a member of the chat. + case denied // = 1 + + /// The poll doesn't exist, or is in a group that is not private and + /// has not been sent yet. + case notFound // = 2 + + /// The poll is no longer OPEN. + case pollClosed // = 3 + + /// An option index is out of range, or several options were selected + /// in a poll that allows only one. + case invalidSelection // = 4 + 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 = .pollClosed + case 4: self = .invalidSelection + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ok: return 0 + case .denied: return 1 + case .notFound: return 2 + case .pollClosed: return 3 + case .invalidSelection: return 4 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Flipcash_Poll_V1_VoteResponse.Result] = [ + .ok, + .denied, + .notFound, + .pollClosed, + .invalidSelection, + ] + + } + + public init() {} + + fileprivate var _state: Flipcash_Poll_V1_PollState? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "flipcash.poll.v1" + +extension Flipcash_Poll_V1_CreatePollRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CreatePollRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}definition\0\u{3}idempotency_key\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._definition) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._idempotencyKey) }() + 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._definition { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = self._idempotencyKey { + try visitor.visitSingularMessageField(value: v, 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_Poll_V1_CreatePollRequest, rhs: Flipcash_Poll_V1_CreatePollRequest) -> Bool { + if lhs._chatID != rhs._chatID {return false} + if lhs._definition != rhs._definition {return false} + if lhs._idempotencyKey != rhs._idempotencyKey {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_CreatePollResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CreatePollResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}poll\0\u{3}flagged_category\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._poll) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.flaggedCategory) }() + 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._poll { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if self.flaggedCategory != .none { + try visitor.visitSingularEnumField(value: self.flaggedCategory, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_CreatePollResponse, rhs: Flipcash_Poll_V1_CreatePollResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._poll != rhs._poll {return false} + if lhs.flaggedCategory != rhs.flaggedCategory {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_CreatePollResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0\u{1}CHAT_NOT_SUPPORTED\0\u{1}ENCRYPTION_NOT_ALLOWED\0\u{1}ENCRYPTION_REQUIRED\0\u{1}OPTION_COUNT_MISMATCH\0\u{1}INVALID_ENDS_AT\0\u{1}INVALID_BALANCE_PARAMETERS\0\u{1}TEXT_MODERATED\0") +} + +extension Flipcash_Poll_V1_GetPollRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetPollRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}poll_id\0\u{2}\u{9}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._pollID) }() + 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._pollID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_GetPollRequest, rhs: Flipcash_Poll_V1_GetPollRequest) -> Bool { + if lhs._pollID != rhs._pollID {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_GetPollResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetPollResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}poll\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._poll) }() + 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._poll { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_GetPollResponse, rhs: Flipcash_Poll_V1_GetPollResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._poll != rhs._poll {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_GetPollResponse.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_Poll_V1_GetPollsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetPollsRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}poll_ids\0\u{2}\u{9}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.decodeRepeatedMessageField(value: &self.pollIds) }() + 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 + if !self.pollIds.isEmpty { + try visitor.visitRepeatedMessageField(value: self.pollIds, fieldNumber: 1) + } + try { if let v = self._auth { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_GetPollsRequest, rhs: Flipcash_Poll_V1_GetPollsRequest) -> Bool { + if lhs.pollIds != rhs.pollIds {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_GetPollsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GetPollsResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}polls\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.polls) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.result != .ok { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if !self.polls.isEmpty { + try visitor.visitRepeatedMessageField(value: self.polls, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_GetPollsResponse, rhs: Flipcash_Poll_V1_GetPollsResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.polls != rhs.polls {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_GetPollsResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0OK\0\u{1}DENIED\0") +} + +extension Flipcash_Poll_V1_VoteRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".VoteRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}poll_id\0\u{3}option_indices\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._pollID) }() + case 2: try { try decoder.decodeRepeatedUInt32Field(value: &self.optionIndices) }() + 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._pollID { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.optionIndices.isEmpty { + try visitor.visitPackedUInt32Field(value: self.optionIndices, 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_Poll_V1_VoteRequest, rhs: Flipcash_Poll_V1_VoteRequest) -> Bool { + if lhs._pollID != rhs._pollID {return false} + if lhs.optionIndices != rhs.optionIndices {return false} + if lhs._auth != rhs._auth {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_VoteResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".VoteResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{1}state\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._state) }() + 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._state { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Flipcash_Poll_V1_VoteResponse, rhs: Flipcash_Poll_V1_VoteResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs._state != rhs._state {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Flipcash_Poll_V1_VoteResponse.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}POLL_CLOSED\0\u{1}INVALID_SELECTION\0") +} diff --git a/flipcash2.lock b/flipcash2.lock index 70f88c5..4d81627 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: 9f67566fd0297a27ad2de7a98474634adf4d3a24 -subject: Update StartChat comments to enable minimum speaker balanace requirements for public groups (#139) +commit: 1897108ae75e032fc5f7de13cf6e9cadcec9deda +subject: Update link preview comments (#146) diff --git a/proto/intent/v1/model.proto b/proto/intent/v1/model.proto index 517a894..7a84261 100644 --- a/proto/intent/v1/model.proto +++ b/proto/intent/v1/model.proto @@ -26,6 +26,7 @@ message ChatMetadata { ContactDmPayment contact_dm_payment = 2; DmPayment dm_payment = 3; + GroupPayment group_payment = 4; } // For sending a payment to a contact in a DM @@ -48,11 +49,26 @@ message ChatMetadata { Location location = 1; // The action being performed for the payment - enum Action { - DEFAULT = 0; // Same as SEND: the payment is shown as "sent", not "tipped" - SEND = 1; - TIP = 2; - } - Action action = 2; + PaymentAction action = 2; + } + + // For sending a payment directly to another member of a group chat. The + // chat_id must be a group, and the payment's destination owner account + // names the recipient, who must be a Flipcash user and a member of the + // group. The sender must be able to speak in the group: the payment posts + // a CashContent message into it as the sender, naming the recipient as + // its receiver. + message GroupPayment { + // The action being performed for the payment. Only DEFAULT and SEND + // are supported today, and the payment is shown as SENT; the server + // denies TIP. + PaymentAction action = 1; } } + +// The action being performed for a payment in a chat +enum PaymentAction { + DEFAULT = 0; // Same as SEND: the payment is shown as "sent", not "tipped" + SEND = 1; + TIP = 2; +} diff --git a/proto/messaging/v1/messaging_service.proto b/proto/messaging/v1/messaging_service.proto index 7c03972..8235f53 100644 --- a/proto/messaging/v1/messaging_service.proto +++ b/proto/messaging/v1/messaging_service.proto @@ -237,6 +237,9 @@ message SendMessageRequest { // - TextContent // - ReplyContent // - MediaContent + // - WidgetContent with a PollWidget, in group chats that are not private + // only. A private group sends it inside EncryptedContent. See + // PollWidget for when it is accepted. // - EncryptedContent, in DMs and private groups only. A private group // accepts nothing else. // diff --git a/proto/messaging/v1/model.proto b/proto/messaging/v1/model.proto index 0d86b45..b052a5b 100644 --- a/proto/messaging/v1/model.proto +++ b/proto/messaging/v1/model.proto @@ -9,6 +9,7 @@ option objc_class_prefix = "FPBMessagingV1"; import "blob/v1/model.proto"; import "common/v1/common.proto"; import "google/protobuf/timestamp.proto"; +import "poll/v1/model.proto"; import "validate/validate.proto"; message MessageId { @@ -191,6 +192,98 @@ message TextContent { min_len: 1 max_len: 4096 }]; + + // Previews of links in `text`, generated by the sending device (see + // LinkPreview). One today; raising this cap later allows several, carried + // in the order their links appear in `text`. Each preview's url is a + // distinct link in `text`, as LinkPreview.url defines it, or the send is + // DENIED. A client that finds more previews than it supports renders the + // first and ignores the rest. + // + // Allowed on a message's text and on a reply's text body; a media caption + // carries none. A preview is optional: a text that holds a link is always + // sendable without one, and the sender may drop one it generated. + repeated LinkPreview link_previews = 2 [(validate.rules).repeated = { + max_items: 1 + }]; +} + +// A preview of a link in the text that carries it, as the sending device +// rendered it from the page's Open Graph tags (or their fallbacks: the page +// title and meta description): a title, an optional description and an +// optional image. The sending device fetches the page, not the server and +// never the recipient, so no one but the sender ever touches the link, and +// the server cannot moderate a preview beyond its image or vouch that the +// preview matches the page. +// +// What the server guarantees is only that `url` is a link the recipient can +// read in the text (see url below) and, outside EncryptedContent, that the +// image is one the sender uploaded and the server moderated. The title, +// description and image are the sender's claim about the page. Clients +// therefore show the host of `url` on the card, always and never the title +// alone, and open `url` when the card is tapped, so a forged card sits beside +// a true domain and leads only where the text already leads. They render the +// host apart from the sender's text, in a style the title and description +// never take, and clamp the title and description to a few lines each, so +// neither can pass for the host or push it out of view. +message LinkPreview { + // The link the preview describes. Its scheme is http or https, and it + // carries no userinfo, whitespace or bidirectional control (U+061C, + // U+200E, U+200F, U+202A–U+202E, U+2066–U+2069). Its host is a plain + // domain name: dot-separated labels of ASCII letters, digits and hyphens, + // none empty, with an optional trailing dot, so no IP literal, no + // percent-escape and no non-ASCII (a sender uses the punycode form). The + // host's last label is not a number (all digits, or `0x` and hex + // digits), which a browser reads as an IPv4 address. A port, if given, is + // 1–65535. + // + // It appears in the text that holds the preview exactly as written here, + // as a whole link: byte for byte, with no case folding, percent-decoding + // or other normalization, preceded by + // the start of the text or whitespace with only opening brackets or + // quotes between (any run of `(`, `[`, `{`, `<`, `"`, `'`), and followed + // by the end of the text or whitespace with only trailing punctuation + // between (any run of `.`, `,`, `;`, `:`, `!`, `?`, `)`, `]`, `}`, `>`, + // `"`, `'`). So a whole link is one whitespace-separated word of the + // text, less the brackets, quotes and punctuation around it: "see + // (https://a.example)." carries a link to https://a.example, and + // "https://a.example.evil.example" and + // "https://evil.example/(https://a.example)" do not. A sender that + // linkifies the text differently generates no preview for what it cannot + // send. + string url = 1 [(validate.rules).string = { + min_len: 1 + max_len: 2048 + }]; + + // The page's title. Something visible: at least one character that is + // neither whitespace nor an invisible format character (such as a + // zero-width space). One line: no line break (line feed, U+2028 or + // U+2029), so a title cannot draw a line beneath it that reads as the + // host. No control characters, and no bidirectional embedding, override + // or isolate (U+202A–U+202E, U+2066–U+2069); the marks U+200E, U+200F + // and U+061C are allowed. A sender removes what is refused from the + // title it scraped (replacing a line break with a space), or drops the + // preview. + string title = 2 [(validate.rules).string = { + min_len: 1 + max_len: 256 + }]; + + // The page's description, if it has one. Empty for none; otherwise held + // to the same rules as the title, except that it may hold line feeds. + string description = 3 [(validate.rules).string.max_len = 512]; + + // The page's image, if it has one. Like a MediaContent item: on + // SendMessage the client supplies exactly one ORIGINAL rendition, a blob + // it uploaded like any chat image, which the server moderated and derives + // renditions from; the server fills the rendition's metadata and appends + // the derived renditions on every read. Inside EncryptedContent the + // sender sets the ORIGINAL's metadata itself and there are no derived + // renditions; see EncryptedContent. The sending device downloads the + // page's image, downscales it and strips its metadata before uploading, + // as it would a photo. + blob.v1.Media image = 4; } // Cash content @@ -201,8 +294,10 @@ message CashContent { // The amount of cash that was sent common.v1.CryptoPaymentAmount amount = 2 [(validate.rules).message.required = true]; - // Reserved for receiver, which will is required for group chats - reserved 3; + // The user who received the cash. Always set in a group chat, where it + // names which member was paid. In a DM it is set on new messages but unset + // on older ones, whose receiver is the DM member who is not the sender. + common.v1.UserId receiver = 3; // Verb for how the cash was sent. Clients should always show SENT as a // fallback. @@ -249,17 +344,49 @@ message MediaContent { // System message content message SystemContent { - // Best-effort, server-rendered text in the user's locale setting. Today this - // is the only way to display a system message; once the structured `event` - // oneof exists it becomes a fallback, rendered ONLY when the client does not - // recognize the variant (old client, new server). It is not localized per + // Best-effort, server-rendered text in the user's locale setting. Always + // set. It is a fallback, rendered ONLY when the client does not recognize + // the `type` variant (old client, new server). It is not localized per // viewer — clients that know a variant render their own localized string. + // It names no one: users are carried in the variant, so clients render + // their current profiles, and the text never goes stale on a rename. string fallback_text = 1 [(validate.rules).string = { min_len: 1 max_len: 256 }]; - // todo: Define events once we have them + // What this system message describes, in structured form. Always set. + oneof type { + option (validate.required) = true; + + UserJoined user_joined = 2; + } + + // A user joined the chat. + message UserJoined { + // The user that joined + common.v1.UserId user_id = 1 [(validate.rules).message.required = true]; + + // How they joined. Always set. A client that finds it unset (a way of + // joining it does not know yet) renders a plain join. + oneof method { + option (validate.required) = true; + + Direct direct = 2; + Admitted admitted = 3; + } + + // They joined on their own (e.g. a private group's creator rejoining + // their group). + message Direct {} + + // Someone let them in (e.g. a private group's creator admitting them + // from its lobby). + message Admitted { + // The user that let them in + common.v1.UserId admitted_by = 1 [(validate.rules).message.required = true]; + } + } } // Content that clients render as a native widget rather than as plain text @@ -270,6 +397,7 @@ message WidgetContent { option (validate.required) = true; ShareProfileWidget share_profile = 1; + PollWidget poll = 2; } } @@ -279,6 +407,25 @@ message ShareProfileWidget { common.v1.Username username = 1 [(validate.rules).message.required = true]; } +// A widget that shows a poll. It carries only the poll's ID: clients get the +// question, options, and state from poll.v1.Poll's GetPoll or GetPolls. +// +// The creator sends it after calling poll.v1.Poll.CreatePoll, and may send the +// same poll in its chat any number of times. Deleting a message that carries +// it does not delete the poll. +// +// In a group that is not private, it is sent as plain WidgetContent: +// SendMessage accepts it only from the poll's creator, in the chat the poll +// was created for, while the poll is OPEN; otherwise the send is DENIED. +// +// In a private group, it is sent inside EncryptedContent, so the server cannot +// check any of this. Clients render the widget as unsupported if the poll's +// chat_id is not the chat the message is in, or the poll's creator is not the +// message's sender. +message PollWidget { + poll.v1.PollId poll_id = 1 [(validate.rules).message.required = true]; +} + // Deleted message content message DeletedContent { // Timestamp the message was deleted. Set whenever a message is tombstoned; @@ -306,12 +453,15 @@ message DeletedContent { // // The plaintext is a serialized Content message. The allowed plaintext types // are: -// - TextContent +// - TextContent, with or without link previews; a preview's image is +// described under "Media" below // - MediaContent, as described under "Media" below // - ReplyContent, whose own content is a TextContent or a MediaContent -// The server cannot enforce this. A client that decrypts any other type, or -// cannot decrypt the payload at all, renders the message as unsupported. A -// decrypted Content never itself holds EncryptedContent. +// The server cannot enforce this, nor anything TextContent says of its link +// previews: a recipient applies those rules itself after decrypting, and +// renders a preview that breaks one as unsupported. A client that decrypts +// any other type, or cannot decrypt the payload at all, renders the message +// as unsupported. A decrypted Content never itself holds EncryptedContent. // // DMs // @@ -403,8 +553,10 @@ message DeletedContent { // // Media // -// A MediaContent plaintext references blobs the sender uploaded for this chat -// with blob.v1.InitiateExternalUploadRequest.end_to_end_encrypted_for. The +// A MediaContent plaintext, and a LinkPreview.image in a TextContent +// plaintext, reference blobs the sender uploaded for this chat with +// blob.v1.InitiateExternalUploadRequest.end_to_end_encrypted_for; everything +// below applies to both. The // scheme also determines the format of every blob the plaintext references. // Clients never take the format from the blob's server-side metadata, which // the server could alter. For scheme X25519_XCHACHA20POLY1305, each blob is @@ -478,12 +630,15 @@ message EncryptedContent { }]; // The encrypted, serialized Content with the 16-byte Poly1305 tag - // appended. The upper bound fits a MediaContent with a maximum-length - // caption (4096 characters of up to 4 bytes each) wrapped in a - // ReplyContent, plus the tag. + // appended. The upper bound fits a TextContent of maximum length (4096 + // characters of up to 4 bytes each) carrying one LinkPreview of maximum + // length (a 2048-character url, a 256-character title and a + // 512-character description, each of up to 4 bytes a character, and an + // image rendition with its sender-set metadata) wrapped in a + // ReplyContent, plus the tag. It is raised when the preview cap is. bytes ciphertext = 3 [(validate.rules).bytes = { min_len: 17 - max_len: 17408 + max_len: 28672 }]; } diff --git a/proto/poll/v1/model.proto b/proto/poll/v1/model.proto new file mode 100644 index 0000000..c7575cc --- /dev/null +++ b/proto/poll/v1/model.proto @@ -0,0 +1,349 @@ +syntax = "proto3"; + +package flipcash.poll.v1; + +option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/poll/v1;pollpb"; +option java_package = "com.codeinc.flipcash.gen.poll.v1"; +option objc_class_prefix = "FPBPollV1"; + +import "common/v1/common.proto"; +import "google/protobuf/timestamp.proto"; +import "validate/validate.proto"; + +// PollId is the server-assigned identity of a poll. It is what a +// messaging.v1.PollWidget carries, and what every Poll RPC is keyed by. +message PollId { + bytes value = 1 [(validate.rules).bytes = { + min_len: 16 + max_len: 16 + }]; +} + +// IdempotencyKey lets a client safely retry CreatePoll. The server derives the +// poll's identity from the caller and this key, so a retry with the same key +// returns the original poll instead of creating a duplicate. Which parameters +// were sent is not part of the identity: a retry with different parameters +// still returns the original. +// +// The key is owned by the client and is typically a randomly generated UUID. +// It is never exposed as the poll's identity. +message IdempotencyKey { + bytes value = 1 [(validate.rules).bytes = { + min_len: 16 + max_len: 16 + }]; +} + +// Mode is how votes are weighted, which also determines the shape of the +// results (see Results). +enum Mode { + UNKNOWN = 0; + + // Each voter adds 1 to every option they chose. Results are CountResults, + // or WithheldResults if too few voters were counted. + COUNT = 1; + + // Each voter adds their balance of BalanceParameters.mint to every option + // they chose. Balances are read once, when the poll closes, not when the + // vote is cast: every token sits in exactly one wallet at that moment, so + // moving funds between accounts cannot count the same tokens twice. A + // voter who chose several options adds their full balance to each. + // Results are RankedResults, which order the options by the balance + // behind them and show how many voted but no amounts, so they are never + // withheld. + BALANCE = 2; +} + +// PollDefinition is what the creator chose for the poll. It is set on +// CreatePoll and never changes afterward. +// +// The question and option text are in `text`, which is plaintext in a group +// that is not private and encrypted in a private group. Everything else is +// plaintext everywhere, because the server needs it to accept votes and count +// them. +message PollDefinition { + // The number of options. Votes and results refer to options by their + // position, from 0 to option_count - 1. For plaintext text, it must equal + // the number of options in it. For encrypted text, the server cannot check + // it, so clients render a poll whose decrypted option count differs as + // unsupported. + uint32 option_count = 1 [(validate.rules).uint32 = { + gte: 2 + lte: 10 + }]; + + // How votes are weighted. + Mode mode = 2 [(validate.rules).enum = { + in: [1, 2] // COUNT, BALANCE + }]; + + // Whether a voter may choose more than one option. If false, a vote + // selects exactly one option. + bool allows_multiple_choices = 3; + + // When the poll closes. It must be at least 5 minutes and at most 30 days + // after the poll is created. This is the only way a poll closes: nobody, + // including the creator, can close it earlier, so nobody controls when a + // BALANCE poll's balances are read. + google.protobuf.Timestamp ends_at = 4 [(validate.rules).timestamp.required = true]; + + // Required when mode == BALANCE, and must be unset otherwise. + BalanceParameters balance_parameters = 5; + + // The question and option text. + oneof text { + option (validate.required) = true; + + // Required in a group that is not private, and moderated. + PollText plaintext = 6; + + // Required in a private group. Not moderated, since the server cannot + // read it. + EncryptedPollText encrypted = 7; + } +} + +// PollText is the human-readable part of a poll. +message PollText { + // The question being asked. + string question = 1 [(validate.rules).string = { + min_len: 1 + max_len: 255 + }]; + + // The options voters choose between, in the order votes and results refer + // to them. + repeated Option options = 2 [(validate.rules).repeated = { + min_items: 2 + max_items: 10 + }]; +} + +// Option is one choice in a poll. +message Option { + string text = 1 [(validate.rules).string = { + min_len: 1 + max_len: 100 + }]; +} + +// EncryptedPollText is a private group's PollText, encrypted with the chat +// key every member holds. The server stores it as-is and cannot read it. +// +// For scheme CHAT_KEY_XCHACHA20POLY1305, the creator encrypts with +// XChaCha20-Poly1305, as for messaging.v1.EncryptedContent but with its own +// aad: +// key = the group's chat key +// nonce = 24 fresh random bytes, set in `nonce` below +// plaintext = the serialized PollText +// aad = "flipcash-group-poll-e2ee-v1" || chat_id || creator_id +// chat_id is the raw bytes of common.v1.ChatId.value, and creator_id is the +// raw bytes of common.v1.UserId.value for PollMetadata.creator. Never reuse a +// nonce, and do not substitute a 12-byte-nonce AEAD. +// +// A member decrypts with the chat key and the same aad, so the server cannot +// move the text to another chat or attribute it to another creator, and the +// distinct prefix keeps poll text and message content from being swapped for +// each other. The poll ID is not bound, since the server assigns it after the +// text is encrypted. +message EncryptedPollText { + // The encryption scheme used. Clients render a poll with an unrecognized + // scheme as unsupported. + Scheme scheme = 1 [(validate.rules).enum = { + in: [1] // CHAT_KEY_XCHACHA20POLY1305 + }]; + enum Scheme { + UNKNOWN = 0; + // The private group's chat key. + CHAT_KEY_XCHACHA20POLY1305 = 1; + } + + // The random XChaCha20-Poly1305 nonce, unique per encryption. + bytes nonce = 2 [(validate.rules).bytes = { + min_len: 24 + max_len: 24 + }]; + + // The encrypted, serialized PollText with the 16-byte Poly1305 tag + // appended. The upper bound fits a PollText with a maximum-length question + // and the maximum number of maximum-length options (up to 4 bytes per + // character), plus the tag. + bytes ciphertext = 3 [(validate.rules).bytes = { + min_len: 17 + max_len: 5120 + }]; +} + +// BalanceParameters configures a BALANCE poll. +message BalanceParameters { + // The mint whose balance is a voter's weight. Clients default to the mint + // in the group's MinimumBalanceRequirement. + common.v1.PublicKey mint = 1 [(validate.rules).message.required = true]; + + // ISO 4217 alpha-3 currency code that the results' native amounts are + // valued in, at the exchange rate when the poll closes. + string currency = 2 [(validate.rules).string = { pattern: "^[a-z]{3,4}$" }]; +} + +// PollMetadata is a poll: its identity, definition, and current state. +message PollMetadata { + PollId poll_id = 1 [(validate.rules).message.required = true]; + + // The chat the poll was created for. Anyone who can read this chat in + // full can see the poll: its members and anyone who satisfies its + // listener rules in a group that is not private, and only its members in + // a private group. Someone who can only preview the chat sees the + // PollWidget but not the poll. Only the chat's members can vote. Clients + // render a PollWidget whose poll belongs to a different chat than the + // message carrying it as unsupported. + common.v1.ChatId chat_id = 2 [(validate.rules).message.required = true]; + + // The user that created the poll, who in a group that is not private is + // the only user that can send it. + common.v1.UserId creator = 3 [(validate.rules).message.required = true]; + + // What the creator chose. Never changes. + PollDefinition definition = 4 [(validate.rules).message.required = true]; + + // When the poll was created. + google.protobuf.Timestamp created_at = 5 [(validate.rules).timestamp.required = true]; + + // The poll's current state. + PollState state = 6 [(validate.rules).message.required = true]; +} + +// PollState is the changing part of a poll. +// +// Results are hidden while the poll is open: nobody, including the creator, +// can see any totals or how many people have voted until the poll is CLOSED. +// Individual votes are never revealed to anyone but the voter, before or after +// the poll closes. +// +// PollState deliberately carries no version: a value that changed on every +// vote would let anyone watching an open poll see when votes are cast. Status +// only moves forward (OPEN, then TALLYING, then CLOSED), so clients keep the +// copy with the most advanced status and never move a poll's status +// backward. For self_vote, a client that has called Vote trusts its latest +// VoteResponse over any GetPoll or GetPolls response it requested before +// that Vote completed. +message PollState { + Status status = 1; + enum Status { + UNKNOWN = 0; + // Accepting votes. + OPEN = 1; + // No longer accepting votes, and results are being computed. A + // BALANCE poll reads every counted voter's balance at this point, so + // results may take a moment to appear. + TALLYING = 2; + // Results are available and final. + CLOSED = 3; + } + + // Positions of the options the caller has currently selected. Empty if + // the caller hasn't voted or removed their vote. + // + // Per-viewer, like messaging.v1.EmojiReaction.self_reactor: everything + // else in PollState is the same for every viewer, but this is computed + // for the caller. + repeated uint32 self_vote = 2 [(validate.rules).repeated = { + max_items: 10 + unique: true + }]; + + // When the poll stopped accepting votes. Set once status is TALLYING or + // CLOSED. At or shortly after PollDefinition.ends_at. + google.protobuf.Timestamp closed_at = 3; + + // The final results. Set only when status == CLOSED. + Results results = 4; +} + +// Results are a closed poll's final totals. Every vote is counted: only +// members can vote, and a vote still counts if the voter has since left the +// chat. The variant is CountResults for a COUNT poll, unless its totals are +// withheld, and RankedResults for a BALANCE poll. +message Results { + oneof type { + option (validate.required) = true; + + CountResults count = 1; + // Not sent to clients: the server keeps a BALANCE poll's totals and + // shows only their ranking (see RankedResults). + BalanceResults balance = 2; + WithheldResults withheld = 3; + RankedResults ranked = 4; + } +} + +// WithheldResults replaces a closed COUNT poll's totals when too few voters +// were counted for the totals to be shown without exposing how individual +// members voted. With only one voter, for example, any totals would be that +// voter's own choice. A BALANCE poll's results are never withheld, since its +// RankedResults show no totals. +// +// The minimum number of counted voters is set by the server and is not part +// of the contract. Withheld results reveal only that fewer voters than that +// minimum were counted. They are final: a poll whose results are withheld +// never shows totals. +message WithheldResults {} + +// Results for a COUNT poll. +message CountResults { + // The number of distinct voters counted. This is the denominator for each + // option's percentage. When multiple choices are allowed, option + // percentages can add up to more than 100%. + uint64 total_voters = 1; + + // The number of voters that chose each option, in option order. + repeated uint64 option_voters = 2 [(validate.rules).repeated = { + min_items: 2 + max_items: 10 + }]; +} + +// The totals behind a BALANCE poll's results. The server records these when +// the poll closes but does not send them to clients, who see only +// RankedResults: members' balances can often be looked up, so the totals could +// reveal who voted and how. +message BalanceResults { + // The sum of the counted voters' balances when the poll closed, with each + // voter counted once even if they chose several options. This is the + // denominator for each option's percentage. When multiple choices are + // allowed, option percentages can add up to more than 100%. + common.v1.CryptoPaymentAmount total = 1 [(validate.rules).message.required = true]; + + // The balance behind each option, in option order. + repeated common.v1.CryptoPaymentAmount option_balances = 2 [(validate.rules).repeated = { + min_items: 2 + max_items: 10 + }]; + + // The number of distinct voters counted, including those holding none of + // the mint, whose votes add nothing to any balance. It is what + // RankedResults.total_voters shows. + uint64 total_voters = 3; +} + +// Results for a BALANCE poll: the options ranked by the balance behind each, +// and how many voted, with no amounts and no per-option voter counts, so they +// reveal neither how much was voted nor by whom. +message RankedResults { + // Each option's rank, in option order. 1 is the option with the most + // balance behind it. Options with equal balances share a rank, and the + // next rank skips the tied positions: balances of 30, 20, 20 and 10 rank + // 1, 2, 2 and 4. Every option is ranked, including those with no balance + // behind them, which share the last rank. + repeated uint32 option_ranks = 1 [(validate.rules).repeated = { + min_items: 2 + max_items: 10 + items: { + uint32: { gte: 1, lte: 10 } + } + }]; + + // The number of distinct voters counted, as for CountResults.total_voters. + // Every voter counts, including one holding none of the mint, whose vote + // moves no option's rank. + uint64 total_voters = 2; +} diff --git a/proto/poll/v1/poll_service.proto b/proto/poll/v1/poll_service.proto new file mode 100644 index 0000000..8de2d7f --- /dev/null +++ b/proto/poll/v1/poll_service.proto @@ -0,0 +1,201 @@ +syntax = "proto3"; + +package flipcash.poll.v1; + +option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/poll/v1;pollpb"; +option java_package = "com.codeinc.flipcash.gen.poll.v1"; +option objc_class_prefix = "FPBPollV1"; + +import "common/v1/common.proto"; +import "moderation/v1/model.proto"; +import "poll/v1/model.proto"; +import "validate/validate.proto"; + +// Poll manages polls in group chats, private or not. DMs are not supported. +// +// A poll is shown in a chat as a message. The creator first calls CreatePoll, +// then sends the returned PollId with Messaging.SendMessage as a +// messaging.v1.PollWidget. The message carries only the PollId; clients get the +// question, options, and state from GetPoll or GetPolls. +// +// A poll belongs to the chat it was created for. Its creator may send it in +// that chat any number of times, for example to bring an older poll back to +// the bottom of the chat. Every message carrying it shows the same poll. +// Deleting a message that carries a poll does not delete the poll. +// +// In a group that is not private, the widget is sent as plain WidgetContent, +// so the server knows when the poll is first sent: the poll goes live then. +// +// In a private group, the question and option text are encrypted with the +// chat key (see EncryptedPollText), and the widget is sent inside +// messaging.v1.EncryptedContent like everything else. The server never learns +// which messages carry the poll, so the poll is live as soon as it is +// created. The server still sees the poll's structure (option count, mode, +// ends_at, balance parameters) and every vote, since it has to count them. +service Poll { + // CreatePoll creates a poll for a group chat. The caller must be able to + // send messages in the chat. A plaintext question and its options are + // moderated together, as one text; encrypted text is not. + // + // In a group that is not private, the poll is not visible to anyone but + // its creator, and does not accept votes, until it has been sent in a + // message, and a poll that is never sent is eventually discarded. In a + // private group, the poll is visible to members and accepts votes as soon + // as it is created. PollDefinition.ends_at is a fixed time, so the poll's + // clock runs from creation, not from when it is sent. + rpc CreatePoll(CreatePollRequest) returns (CreatePollResponse); + + // GetPoll gets a poll, including the caller's own vote and, once the poll + // is CLOSED, its results. The caller must be able to read the chat in + // full (see messaging.v1.ViewMode FULL): in a group that is not private, + // its members and anyone who satisfies its listener rules; in a private + // group, only its members. Being able to preview the chat is not enough. + rpc GetPoll(GetPollRequest) returns (GetPollResponse); + + // GetPolls is the batch version of GetPoll, for rendering a page of + // messages. Polls the caller cannot see, or that don't exist, are omitted. + rpc GetPolls(GetPollsRequest) returns (GetPollsResponse); + + // Vote replaces the caller's entire selection in an OPEN poll. Calling it + // again with the same selection changes nothing; an empty selection + // removes the caller's vote. The caller must be a member of the chat. The + // chat's speaker rules don't apply: a member who cannot send messages in + // the chat can still vote. + rpc Vote(VoteRequest) returns (VoteResponse); +} + +message CreatePollRequest { + // The group chat to create the poll for. + common.v1.ChatId chat_id = 1 [(validate.rules).message.required = true]; + + PollDefinition definition = 2 [(validate.rules).message.required = true]; + + // Key for retrying this request safely. A retry with the same key from the + // same caller returns the poll the first attempt created, with result OK, + // even if the parameters differ. + IdempotencyKey idempotency_key = 3 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message CreatePollResponse { + Result result = 1; + enum Result { + OK = 0; + // The caller is not a member of the chat, or does not satisfy its + // speaker rules. + DENIED = 1; + // The chat is a DM. + CHAT_NOT_SUPPORTED = 2; + // The text is encrypted and the chat is not a private group. + ENCRYPTION_NOT_ALLOWED = 3; + // The chat is a private group and the text is not encrypted. + ENCRYPTION_REQUIRED = 4; + // The text is plaintext and option_count differs from its number of + // options. + OPTION_COUNT_MISMATCH = 5; + // ends_at is less than 5 minutes or more than 30 days after now. + INVALID_ENDS_AT = 6; + // balance_parameters is missing for a BALANCE poll or set for a + // COUNT poll, or its mint or currency is one the server cannot value. + INVALID_BALANCE_PARAMETERS = 7; + // The plaintext question and options, moderated together as one + // text, were flagged. + TEXT_MODERATED = 8; + } + + // The created poll. Set only when result == OK. + PollMetadata poll = 2; + + // The best-fit category that tripped moderation, mirroring the Moderation + // service's vocabulary. Set only when result == TEXT_MODERATED; NONE + // otherwise. + moderation.v1.FlaggedCategory flagged_category = 3; +} + +message GetPollRequest { + PollId poll_id = 1 [(validate.rules).message.required = true]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message GetPollResponse { + Result result = 1; + enum Result { + OK = 0; + // The caller cannot read the poll's chat in full. + DENIED = 1; + // The poll doesn't exist, or is in a group that is not private, has + // not been sent yet, and the caller is not its creator. + NOT_FOUND = 2; + } + + // Set only when result == OK. + PollMetadata poll = 2; +} + +message GetPollsRequest { + // Duplicates are ignored. + repeated PollId poll_ids = 1 [(validate.rules).repeated = { + min_items: 1 + max_items: 100 + }]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message GetPollsResponse { + Result result = 1; + enum Result { + OK = 0; + DENIED = 1; + } + + // One entry per requested poll the caller can see, in no particular order. + // Clients apply each as described on PollState, so a poll whose status is + // behind the one the client already holds does not regress it. + repeated PollMetadata polls = 2 [(validate.rules).repeated = { + max_items: 100 + }]; +} + +message VoteRequest { + PollId poll_id = 1 [(validate.rules).message.required = true]; + + // Positions in PollDefinition.options of every option the caller selects. + // Replaces any earlier vote. Empty removes the caller's vote. More than + // one is allowed only if PollDefinition.allows_multiple_choices is set. + repeated uint32 option_indices = 2 [(validate.rules).repeated = { + max_items: 10 + unique: true + items: { + uint32: { + lt: 10 + } + } + }]; + + common.v1.Auth auth = 10 [(validate.rules).message.required = true]; +} + +message VoteResponse { + Result result = 1; + enum Result { + OK = 0; + // The caller is not a member of the chat. + DENIED = 1; + // The poll doesn't exist, or is in a group that is not private and + // has not been sent yet. + NOT_FOUND = 2; + // The poll is no longer OPEN. + POLL_CLOSED = 3; + // An option index is out of range, or several options were selected + // in a poll that allows only one. + INVALID_SELECTION = 4; + } + + // The poll's state after the vote, with self_vote reflecting it, when + // result == OK. The poll's current state when result == POLL_CLOSED. + // Unset otherwise. + PollState state = 2; +}