Skip to content

Commit 581d265

Browse files
committed
Added support for macro and updated the codora-framework-bot crate
1 parent 75aa3a3 commit 581d265

File tree

18 files changed

+363
-185
lines changed

18 files changed

+363
-185
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ codora-framework-web = { path = "./crates/codora-framework-web" }
1515
codora-framework-orm = { path = "./crates/codora-framework-orm" }
1616
codora-framework-conf = { path = "./crates/codora-framework-conf" }
1717
codora-framework-axum = { path = "./crates/codora-framework-axum" }
18+
codora-framework-macro = { path = "./crates/codora-framework-macro" }
1819
codora-framework-worker = { path = "./crates/codora-framework-worker" }
1920
codora-framework-bucket = { path = "./crates/codora-framework-bucket" }
2021
codora-framework-adapter = { path = "./crates/codora-framework-adapter" }

crates/codora-framework-bot/Cargo.toml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ categories = ["web-programming"]
55
edition = { workspace = true }
66

77

8+
[features]
9+
default = ["full"]
10+
full = ["codora-framework-bot-telegram", "codora-framework-bot-discord"]
11+
codora-framework-bot-telegram = []
12+
codora-framework-bot-discord = []
13+
814
[lints]
915
workspace = true
1016

@@ -15,12 +21,17 @@ hyper = { version = "1.7.0", features = ["full"] }
1521
thiserror = { workspace = true }
1622
tracing = { workspace = true }
1723
codora-framework = { workspace = true }
24+
codora-framework-macro = { workspace = true, features = [
25+
"codora-framework-bot-discord",
26+
"codora-framework-bot-telegram",
27+
] }
1828
tokio = { workspace = true }
19-
anyhow = { workspace = true }
20-
tracing-subscriber = { workspace = true, features = ["env-filter"] }
21-
tower = { workspace = true }
2229
http-body-util = "0.1"
2330
hyper-util = { version = "0.1", features = ["full"] }
31+
serde = { workspace = true }
32+
serde_json = { workspace = true }
2433

2534
[dev-dependencies]
35+
anyhow = { workspace = true }
36+
tracing-subscriber = { workspace = true, features = ["env-filter"] }
2637
tower = { workspace = true, features = ["util"] }
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
use crate::telegram_types::GetMe;
2+
use std::{
3+
pin::Pin,
4+
task::{Context, Poll},
5+
};
6+
use tower::Service;
7+
8+
/*
9+
async fn get_me(ctx: Content) -> GetMe {
10+
ctx.get_me().await
11+
}
12+
*/
13+
#[derive(Debug, thiserror::Error)]
14+
#[error("{0}")]
15+
pub enum Error {}
16+
17+
pub type Result<T, E = Error> = core::result::Result<T, E>;
18+
19+
trait BotApi {
20+
type Option: Default;
21+
type Connected;
22+
23+
fn connected(&self) -> Result<Self::Connected>;
24+
25+
// Provide other function
26+
}
27+
28+
/// Bot is generic over the intended api just like sqlx
29+
/// --- discord
30+
/// ---- --- telegram ---> unified api to talk to outside world
31+
/// --- x
32+
///
33+
/// Bot is service and expected to run forever
34+
///
35+
/// ```no_run
36+
/// let bot_instance = Bot::<Telegram>::new(|option| {
37+
/// option.url = "https://web.api.telegram";
38+
/// // other configs
39+
/// option
40+
/// });
41+
///
42+
/// bot_instance
43+
/// .setup_listener(3000)
44+
/// .with_graceful_shutdown()
45+
/// .await?;
46+
/// ```
47+
/// or be plugges as service example assuming we using axum
48+
///
49+
/// ```no_run
50+
/// use axum::Router;
51+
///
52+
/// let bot_instance = Bot::<Telegram>::new(|option| {
53+
/// option.url = "https://web.api.telegram";
54+
/// // other configs
55+
/// option
56+
/// });
57+
///
58+
/// let app = Router::new().route("/webhook", post(bot_instance));
59+
///
60+
/// axum::serve(listener, app).await?;
61+
/// ```
62+
#[derive(Debug, new)]
63+
pub struct Bot<A> {
64+
bot_api: A,
65+
}
66+
67+
#[derive(Debug, Clone, new)]
68+
pub struct Telegram {
69+
inner: TelegramOption,
70+
}
71+
72+
#[derive(Debug, Clone, Default)]
73+
pub struct TelegramOption {}
74+
75+
mod telegram_types {
76+
#[derive(Debug, Clone)]
77+
pub struct Updates {}
78+
79+
#[derive(Debug, Clone)]
80+
pub struct GetMe {}
81+
}
82+
83+
impl BotApi for Telegram {
84+
type Option = TelegramOption;
85+
86+
// assuming this is what telegram gives back when connected let hope
87+
type Connected = telegram_types::GetMe;
88+
89+
fn connected(&self) -> Result<Self::Connected> {
90+
Ok(GetMe {})
91+
}
92+
}
93+
94+
// This pattern don't work well if Self needs more than option look into it
95+
impl From<TelegramOption> for Telegram {
96+
fn from(value: TelegramOption) -> Self {
97+
Self::new(value)
98+
}
99+
}
100+
101+
impl Bot<Telegram> {
102+
fn on<H>(self, arg: &str, handler: H) -> Result<Self> {
103+
// the idea we wanna register arg here with the handler to look it upm later
104+
105+
Ok(self)
106+
}
107+
}
108+
109+
/// This is expected to be generic all bot api to work with this let proceed
110+
pub trait Handler {}
111+
112+
pub fn handler<H>(handler: H)
113+
where
114+
H: Handler,
115+
{
116+
// we gonna drop it for now but you get the idea we wanna work with handler here
117+
drop(handler)
118+
}
119+
120+
impl<F> Handler for F where F: FnOnce() {}
121+
122+
impl<A> Service<telegram_types::Updates> for Bot<A> {
123+
type Response = ();
124+
type Error = Error;
125+
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
126+
127+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
128+
Poll::Ready(Ok(()))
129+
}
130+
131+
fn call(&mut self, req: telegram_types::Updates) -> Self::Future {
132+
Box::pin(async move {
133+
// when update is recieved we wanna call self
134+
trace!("Recieved update: {:?}", req);
135+
Ok(())
136+
})
137+
}
138+
}
139+
140+
#[cfg(test)]
141+
mod tests {
142+
use crate::{Bot, Telegram, TelegramOption, handler, telegram_types::Updates};
143+
use anyhow::Result;
144+
use tower::ServiceExt as _;
145+
use tracing_subscriber::EnvFilter;
146+
147+
#[tokio::test]
148+
async fn test_bot_with_long_polling_or_webhook() -> anyhow::Result<()> {
149+
tracing_subscriber::fmt()
150+
.with_env_filter(EnvFilter::new("trace"))
151+
.init();
152+
153+
trace!("Subscriber installed");
154+
155+
// assuming this is a async function with extractor in it you get the idea
156+
fn start_handler() {
157+
// let me = ctx.request::<GetMe>().await?;
158+
// let message = text!(chat_id, format!("Hello, I am {}", me.username));
159+
160+
// the reply function would definently be provided via extension
161+
// let result = ctx.reply(message).await?:
162+
// inside send function
163+
// send {
164+
// convert it into
165+
// let request = message.into();
166+
167+
// we didn't block here
168+
// let res = tokio::task::spawn(request).await;
169+
// return the res
170+
// res
171+
}
172+
let bot: Bot<Telegram> = Bot::new(Telegram::new(TelegramOption {})).on("/start", handler(start_handler))?;
173+
174+
// This is how we wanna test our bot
175+
let res = bot.oneshot(Updates {}).await?;
176+
assert_eq!(res, ());
177+
178+
// What if we wanna serve it we are thinking Bot could be injected as service like in axum as a service
179+
// or
180+
181+
// as teloxide dispatching method or long poll we will explore both
182+
Ok(())
183+
}
184+
}

crates/codora-framework-bot/examples/dev-test-setup.rs

Lines changed: 0 additions & 10 deletions
This file was deleted.

crates/codora-framework-bot/src/adapter/discord/mod.rs

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[cfg(feature = "codora-framework-bot-discord")]
2+
pub mod discord;
3+
4+
#[cfg(feature = "codora-framework-bot-telegram")]
5+
pub mod telegram;

crates/codora-framework-bot/src/adapter/telegram/data/mod.rs

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use crate::listener::Listener;
2+
3+
pub struct TGWebhook {}
4+
5+
impl Listener for TGWebhook {}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
mod listener;
2+
3+
use crate::bot::Bot;
4+
pub use listener::TGWebhook;
5+
6+
#[derive(Debug, thiserror::Error)]
7+
#[error("{0}")]
8+
pub enum Error {}
9+
10+
#[derive(Debug)]
11+
pub struct TGBotOption {}
12+
13+
#[derive(Debug)]
14+
pub struct TGBot {
15+
option: TGBotOption,
16+
}
17+
18+
impl TGBot {
19+
pub fn with(option: TGBotOption) -> Self {
20+
TGBot { option }
21+
}
22+
23+
pub fn command<T>(self, handler: T) -> Self {
24+
// Register command handler
25+
self
26+
}
27+
}
28+
29+
impl Bot<()> for TGBot {
30+
type Error = Error;
31+
32+
async fn handle(&self, request: ()) -> Result<(), Self::Error> {
33+
todo!()
34+
}
35+
}

crates/codora-framework-bot/src/api/mod.rs

Whitespace-only changes.

0 commit comments

Comments
 (0)