Skip to content

Commit

Permalink
feat: add beep beep bot for custom webhooks (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhkafadar committed Jan 21, 2024
1 parent ae53d44 commit 012b092
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 2 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ DATABASE_URL= #POSTGRES DATABASE URL
WEBHOOK_BASE_URL=https://webhook.notifine.com
GITLAB_TELOXIDE_TOKEN=
GITHUB_TELOXIDE_TOKEN=
BEEP_TELOXIDE_TOKEN=
TRELLO_TELOXIDE_TOKEN=
TELEGRAM_ADMIN_CHAT_ID=
183 changes: 183 additions & 0 deletions src/bots/beep_bot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use notifine::get_webhook_url_or_create;
use std::env;
use teloxide::dispatching::dialogue;
use teloxide::dispatching::dialogue::InMemStorage;
use teloxide::dptree::case;
use teloxide::filter_command;
use teloxide::prelude::*;
use teloxide::types::{ChatMemberKind, ParseMode};
use teloxide::utils::command::BotCommands;

type MyDialogue = Dialogue<State, InMemStorage<State>>;

pub async fn run_beep_bot() {
log::info!("Starting bot...");

let bot = create_new_bot();

let command_handler =
filter_command::<Command, _>().branch(case![Command::Start].endpoint(handle_start_command));

let message_handler = dptree::entry()
.branch(
Update::filter_message()
.branch(command_handler)
.branch(case![State::ReceiveBotReview].endpoint(handle_bot_review))
.branch(dptree::endpoint(handle_new_message)),
)
.branch(Update::filter_my_chat_member().endpoint(handle_my_chat_member_update));

let handler =
dialogue::enter::<Update, InMemStorage<State>, State, _>().branch(message_handler);

Dispatcher::builder(bot, handler)
.dependencies(dptree::deps![InMemStorage::<State>::new()])
.enable_ctrlc_handler()
.build()
.dispatch()
.await;

log::info!("Closing bot... Goodbye!");
}

#[derive(Clone, Default)]
pub enum State {
#[default]
Start,
ReceiveBotReview,
}

#[derive(BotCommands, Clone)]
#[command(
rename_rule = "lowercase",
description = "These commands are supported:"
)]
enum Command {
#[command(description = "starts!")]
Start,
}

// async fn handle_start_command(bot: Bot, dialogue: MyDialogue, msg: Message) -> ResponseResult<()> {
// log::info!("Start command received");
// bot.send_message(msg.chat.id, "What do you think about our bot?")
// .await?;
// dialogue.update(State::ReceiveBotReview).await.unwrap();
// Ok(())
// }

async fn handle_start_command(msg: Message) -> ResponseResult<()> {
log::info!("Start command received");
handle_new_chat_and_start_command(
msg.chat
.id
.to_string()
.parse::<i64>()
.expect("Error parsing chat id"),
)
.await?;

Ok(())
}

async fn handle_bot_review(bot: Bot, dialogue: MyDialogue, msg: Message) -> ResponseResult<()> {
log::info!("Bot review received");
let chat_id = msg.chat.id;
// let message = msg.text().unwrap();
bot.send_message(chat_id, "Thanks.").await?;
dialogue.exit().await.unwrap();

Ok(())
}

async fn handle_new_message(message: Message) -> ResponseResult<()> {
let chat_id = message.chat.id.0;

if let Some(text) = message.text() {
log::info!("Received message from {}: {}", chat_id, text);
}

log::warn!("{:#?}", message.via_bot);
Ok(())
}

async fn handle_my_chat_member_update(update: ChatMemberUpdated) -> ResponseResult<()> {
let chat_id = update.chat.id.0;

log::info!(
"Received chat member update from {}: {:#?} {:#?}",
chat_id,
update.old_chat_member,
update.new_chat_member
);

// bot joining a group or a new private chat
if update.old_chat_member.kind == ChatMemberKind::Left
&& update.new_chat_member.kind == ChatMemberKind::Member
{
handle_new_chat_and_start_command(chat_id).await?
}

log::info!(
"Received a chat member update from {}: {:?}",
chat_id,
update.new_chat_member
);
Ok(())
}

pub async fn send_message_beep(chat_id: i64, message: String) -> ResponseResult<()> {
log::info!("Sending message to {}: {}", chat_id, message);
let bot = create_new_bot();

let chat_id = ChatId(chat_id);

bot.send_message(chat_id, message)
.disable_web_page_preview(true)
.parse_mode(ParseMode::Html)
.send()
.await?;
Ok(())
}

async fn handle_new_chat_and_start_command(telegram_chat_id: i64) -> ResponseResult<()> {
let webhook_url = get_webhook_url_or_create(telegram_chat_id);

let message = if webhook_url.0.is_empty() {
log::error!("Error creating or getting webhook: {:?}", webhook_url);
"Hi there!\
Our bot is curently has some problems \
Please create a github issue here: \
https://github.com/mhkafadar/notifine/issues/new"
.to_string()
} else {
format!(
"Hi there! \
To setup notifications for \
this chat your custom app, \
add this \
URL: {}/beep/{}",
env::var("WEBHOOK_BASE_URL").expect("WEBHOOK_BASE_URL must be set"),
webhook_url.0
)
};

send_message_beep(telegram_chat_id, message).await?;

if webhook_url.1 {
// send message to admin on telegram and inform new install
send_message_beep(
env::var("TELEGRAM_ADMIN_CHAT_ID")
.expect("TELEGRAM_ADMIN_CHAT_ID must be set")
.parse::<i64>()
.expect("Error parsing TELEGRAM_ADMIN_CHAT_ID"),
format!("New beep webhook added: {telegram_chat_id}"),
)
.await?;
}

Ok(())
}

fn create_new_bot() -> Bot {
Bot::new(env::var("BEEP_TELOXIDE_TOKEN").expect("BEEP_TELOXIDE_TOKEN must be set"))
}
1 change: 1 addition & 0 deletions src/bots/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod beep_bot;
pub mod github_bot;
pub mod gitlab_bot;
pub mod trello_bot;
4 changes: 3 additions & 1 deletion src/http_server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::webhooks::beep::http_server::handle_beep_webhook;
use crate::webhooks::github::http_server::handle_github_webhook;
use crate::webhooks::gitlab::http_server::handle_gitlab_webhook;
use crate::webhooks::trello::http_server::handle_trello_callback;
use actix_web::{get, middleware, App, HttpResponse, HttpServer, Responder};
use actix_web::{get, middleware, App, HttpServer, Responder};

pub async fn run_http_server() -> std::io::Result<()> {
HttpServer::new(|| {
Expand All @@ -10,6 +11,7 @@ pub async fn run_http_server() -> std::io::Result<()> {
.service(health)
.service(handle_gitlab_webhook)
.service(handle_github_webhook)
.service(handle_beep_webhook)
.service(handle_trello_callback)
})
.bind(("0.0.0.0", 8080))?
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod bots;
pub mod http_server;
pub mod webhooks;

use crate::bots::beep_bot::run_beep_bot;
use crate::bots::github_bot::run_github_bot;
use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection};
use diesel::PgConnection;
Expand All @@ -33,6 +34,7 @@ async fn main() {

task::spawn(run_gitlab_bot());
task::spawn(run_github_bot());
task::spawn(run_beep_bot());
// task::spawn(run_trello_bot());
run_http_server().await.expect("Http server error");

Expand Down
67 changes: 67 additions & 0 deletions src/webhooks/beep/http_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::bots::beep_bot::send_message_beep;
use actix_web::{post, web, HttpRequest, HttpResponse, Responder};
use notifine::{find_chat_by_id, find_webhook_by_webhook_url};
use std::env;

#[post("/beep/{webhook_url}")]
pub async fn handle_beep_webhook(
webhook_url: web::Path<String>,
req: HttpRequest,
body: web::Bytes,
) -> impl Responder {
let event_name = "beep";
log::info!("Event name: {:?}", event_name);
// create a message from request body. json stringified
let message = String::from_utf8(body.to_vec()).unwrap();
log::info!("Message: {}", message);
// if message is empty, then we don't need to send it to telegram
if message.is_empty() {
return HttpResponse::Ok();
}

let webhook_url = &webhook_url;
log::info!("webhook_url: {}", webhook_url);
let webhook = find_webhook_by_webhook_url(webhook_url);

if webhook.is_none() {
log::error!("Webhook not found");
return HttpResponse::NotFound();
}
let webhook = webhook.unwrap();

// log chat_id
log::info!("Webhook: {}", webhook.webhook_url);
let chat_id = webhook.chat_id.expect("Chat id must be set");
log::info!("Chat id: {}", chat_id);

let chat = find_chat_by_id(webhook.chat_id.expect("Chat id must be set"));

if chat.is_none() {
log::error!("Chat not found");
return HttpResponse::NotFound();
}
let chat = chat.unwrap();

send_message_beep(
chat.telegram_id
.parse::<i64>()
.expect("CHAT_ID must be an integer"),
message,
)
.await
.unwrap();

// send message to telegram admin
send_message_beep(
env::var("TELEGRAM_ADMIN_CHAT_ID")
.expect("TELEGRAM_ADMIN_CHAT_ID must be set")
.parse::<i64>()
.expect("Error parsing TELEGRAM_ADMIN_CHAT_ID"),
format!("Event: {event_name:?}, Chat id: {chat_id}"),
)
.await
.unwrap();

log::info!("bot sent message");
HttpResponse::Ok() //
}
1 change: 1 addition & 0 deletions src/webhooks/beep/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod http_server;
3 changes: 2 additions & 1 deletion src/webhooks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod gitlab;
pub mod beep;
pub mod github;
pub mod gitlab;
pub mod trello;

0 comments on commit 012b092

Please sign in to comment.