okbot_core/message.rs
1//! Messages flowing through the pipeline.
2
3use jiff::Timestamp;
4use serde::{Deserialize, Serialize};
5
6use crate::id::{ConversationKey, MessageId, UserRef};
7
8/// Media attached to a message. Platforms that deliver media do so by URL.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(tag = "kind", rename_all = "kebab-case")]
11#[non_exhaustive]
12pub enum Attachment {
13 /// An image.
14 Image {
15 /// Where to fetch it.
16 url: String,
17 /// Text sent with the image.
18 caption: Option<String>,
19 },
20 /// A voice or audio message.
21 Audio {
22 /// Where to fetch it.
23 url: String,
24 },
25 /// A sticker.
26 Sticker {
27 /// Platform sticker id or URL.
28 id: String,
29 },
30 /// Any other file.
31 File {
32 /// Where to fetch it.
33 url: String,
34 /// File name, when known.
35 name: Option<String>,
36 },
37}
38
39/// A message received from a platform, normalized by its channel adapter.
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct InboundEvent {
42 /// Platform event or message id, used to drop duplicate deliveries.
43 pub event_id: String,
44 /// The conversation it belongs to.
45 pub conversation: ConversationKey,
46 /// Who wrote it.
47 pub author: UserRef,
48 /// The text, with mentions of the bot removed and other mentions written as `@name`.
49 pub text: String,
50 /// Media sent with the message.
51 pub attachments: Vec<Attachment>,
52 /// The bot was mentioned (or replied to, on platforms that only deliver such group messages).
53 pub mentions_bot: bool,
54 /// A one-to-one conversation with the bot.
55 pub is_direct: bool,
56 /// The platform withheld the content (for example Zalo's `message.unsupported.received` for
57 /// protected users). The runtime answers with a fixed message and never calls an agent.
58 pub restricted: bool,
59 /// Platform message id, used to reply to this message where supported.
60 pub message_id: Option<MessageId>,
61 /// When the platform says it was sent (or when it was received, if unknown).
62 pub sent_at: Timestamp,
63}
64
65/// Where a reply goes.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct ReplyTarget {
68 /// The conversation to post into.
69 pub conversation: ConversationKey,
70 /// The message being answered, on platforms that can quote or thread under it.
71 pub in_reply_to: Option<MessageId>,
72}
73
74impl ReplyTarget {
75 /// The target that answers `event`.
76 pub fn to_event(event: &InboundEvent) -> Self {
77 Self {
78 conversation: event.conversation.clone(),
79 in_reply_to: event.message_id.clone(),
80 }
81 }
82}
83
84/// A reply in canonical Markdown, before rendering for a platform.
85#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
86pub struct OutboundMessage {
87 /// CommonMark text.
88 pub markdown: String,
89 /// Media to send.
90 pub attachments: Vec<Attachment>,
91}
92
93impl OutboundMessage {
94 /// A text-only message.
95 pub fn text(markdown: impl Into<String>) -> Self {
96 Self {
97 markdown: markdown.into(),
98 attachments: Vec::new(),
99 }
100 }
101}
102
103/// One platform message, already in the platform's dialect and within its length limit.
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
105pub struct RenderedMessage {
106 /// Text in the channel's [`MarkdownDialect`](crate::MarkdownDialect).
107 pub text: String,
108}
109
110/// A message the bot sent, for later edits.
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct SentRef {
113 /// Where it was sent.
114 pub target: ReplyTarget,
115 /// The platform's id for it.
116 pub message_id: MessageId,
117}