Skip to content

Commit db0be14

Browse files
committed
Add extend_dialog_data and new examples. Add docs for public API. Refactor builders old style.
1 parent ace36ca commit db0be14

49 files changed

Lines changed: 1817 additions & 987 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/dialogs_button_actions/src/main.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,23 +131,27 @@ fn registry() -> DialogRegistry {
131131
"pickup",
132132
"Pickup from cafe",
133133
ButtonAction::chain([
134-
ButtonAction::set_dialog_value("delivery_method", "pickup"),
135-
ButtonAction::set_dialog_value(
136-
"cart_notice",
137-
"Pickup selected. The order is ready to place.",
138-
),
134+
ButtonAction::extend_dialog_data([
135+
("delivery_method", "pickup"),
136+
(
137+
"cart_notice",
138+
"Pickup selected. The order is ready to place.",
139+
),
140+
]),
139141
ButtonAction::switch_to("cart"),
140142
]),
141143
)])
142144
.row([Button::action(
143145
"courier",
144146
"Courier delivery",
145147
ButtonAction::chain([
146-
ButtonAction::set_dialog_value("delivery_method", "courier"),
147-
ButtonAction::set_dialog_value(
148-
"cart_notice",
149-
"Courier delivery selected. The order is ready to place.",
150-
),
148+
ButtonAction::extend_dialog_data([
149+
("delivery_method", "courier"),
150+
(
151+
"cart_notice",
152+
"Courier delivery selected. The order is ready to place.",
153+
),
154+
]),
151155
ButtonAction::switch_to("cart"),
152156
]),
153157
)])
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "dialogs_force_reply"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
serde_json = "1.0"
9+
telers = { path = "../../telers", features = ["default_signal", "memory-storage"] }
10+
telers-dialog = { path = "../../telers-dialog" }
11+
tokio = { version = "1.36", features = ["macros"] }
12+
tracing = "0.1"
13+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//! `ForceReply` prompt-flow example for `telers-dialog`.
2+
//!
3+
//! Shows how a `ForceReply` reply-markup widget combines with a `MessageInput`
4+
//! to drive a chat that feels like an inline form: the client auto-opens the
5+
//! reply UI, the message handler captures the text, and the dialog moves on.
6+
//!
7+
//! Run with:
8+
//! ```bash
9+
//! BOT_TOKEN={your_bot_token} cargo run --package dialogs_force_reply
10+
//! ```
11+
12+
use serde_json::Value;
13+
use telers::{
14+
enums::UpdateType,
15+
errors::HandlerError,
16+
event::telegram::{Handler, HandlerResult},
17+
filters::Command,
18+
fsm::{MemoryStorage, Strategy::UserInChat},
19+
middlewares::outer::FSMContext as FSMContextMiddleware,
20+
types::MessageText,
21+
Bot, Dispatcher, Router,
22+
};
23+
use telers_dialog::{
24+
dialog,
25+
widgets::{
26+
format_text, input, keyboard, text, Button, ButtonAction, ForceReply, InlineKeyboard,
27+
MessageInput,
28+
},
29+
window, DialogManager, DialogObserverExt, DialogRegistry, StartMode,
30+
};
31+
use tracing_subscriber::{fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter};
32+
33+
const START_STATE: &str = "ask_name";
34+
35+
type Manager = DialogManager<MemoryStorage>;
36+
37+
async fn handle_start(bot: Bot, manager: Manager) -> HandlerResult<()> {
38+
let _ = manager
39+
.start(
40+
&bot,
41+
START_STATE.to_owned(),
42+
Value::Null,
43+
StartMode::ResetStack,
44+
)
45+
.await
46+
.map_err(HandlerError::new)?;
47+
Ok(())
48+
}
49+
50+
fn registry() -> DialogRegistry {
51+
let dialog = dialog([
52+
window(
53+
START_STATE,
54+
[
55+
text(
56+
"Reservation Setup\n\nStep 1 of 2. Telegram will auto-open the reply UI with \
57+
the placeholder visible. Send the guest's full name.\n\n[Reply] \
58+
`ForceReply::builder().input_field_placeholder(..)`",
59+
),
60+
keyboard(
61+
ForceReply::builder()
62+
.input_field_placeholder("Full name")
63+
.build(),
64+
),
65+
input(MessageInput::new(|_ctx, message: MessageText| async move {
66+
ButtonAction::chain([
67+
ButtonAction::set_dialog_value("guest_name", message.text.to_string()),
68+
ButtonAction::next(),
69+
])
70+
})),
71+
],
72+
),
73+
window(
74+
"ask_party_size",
75+
[
76+
format_text(
77+
"Reservation Setup\n\nStep 2 of 2. Guest: {guest_name}\n\nSend how many \
78+
people the table is for. The reply UI is forced again with a different \
79+
placeholder.",
80+
),
81+
keyboard(
82+
ForceReply::builder()
83+
.input_field_placeholder("Number of guests")
84+
.selective(true)
85+
.build(),
86+
),
87+
input(MessageInput::new(|_ctx, message: MessageText| async move {
88+
ButtonAction::chain([
89+
ButtonAction::set_dialog_value("party_size", message.text.to_string()),
90+
ButtonAction::next(),
91+
])
92+
})),
93+
],
94+
),
95+
window(
96+
"done",
97+
[
98+
format_text(
99+
"Reservation Recorded\n\nGuest: {guest_name}\nParty size: \
100+
{party_size}\n\n[Reply] `ForceReply` only owns the reply markup. The prompt \
101+
text and persistence still come from the window's text and the \
102+
`MessageInput` that consumes the response.",
103+
),
104+
keyboard(
105+
InlineKeyboard::builder()
106+
.row([Button::back("back", "Back")])
107+
.row([Button::done("close", "Close")])
108+
.build(),
109+
),
110+
],
111+
),
112+
]);
113+
114+
DialogRegistry::new().register(dialog).unwrap()
115+
}
116+
117+
#[tokio::main(flavor = "current_thread")]
118+
async fn main() {
119+
tracing_subscriber::registry()
120+
.with(fmt::layer())
121+
.with(EnvFilter::new("info,telers_dialog=trace"))
122+
.init();
123+
124+
let bot = Bot::from_env();
125+
let storage = MemoryStorage::new();
126+
let registry = registry();
127+
128+
let router = Router::new("dialogs_force_reply")
129+
.on_update(|observer| {
130+
observer
131+
.register_outer_middleware(FSMContextMiddleware::new(storage).strategy(UserInChat))
132+
})
133+
.on_message(|observer| {
134+
observer
135+
.register(Handler::new(handle_start).filter(Command::one("start")))
136+
.setup_dialogs::<MemoryStorage>()
137+
})
138+
.on_callback_query(DialogObserverExt::setup_dialogs::<MemoryStorage>);
139+
140+
let dispatcher = Dispatcher::builder()
141+
.main_router(router.configure_default())
142+
.bot(bot)
143+
.extension(registry)
144+
.allowed_updates([UpdateType::Message, UpdateType::CallbackQuery])
145+
.build();
146+
147+
match dispatcher.run_polling().await {
148+
Ok(()) => tracing::info!("Bot stopped"),
149+
Err(err) => tracing::error!(error = %err, "Bot stopped"),
150+
}
151+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "dialogs_inline_button_styles"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
serde_json = "1.0"
9+
telers = { path = "../../telers", features = ["default_signal", "memory-storage"] }
10+
telers-dialog = { path = "../../telers-dialog" }
11+
tokio = { version = "1.36", features = ["macros"] }
12+
tracing = "0.1"
13+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

0 commit comments

Comments
 (0)