-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add ping and push events for github (#20)
* feat: add ping and push events for github * chore: update .env.example * refactor: delete unused function parameters * feat: update push event message
- Loading branch information
Showing
11 changed files
with
428 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
DATABASE_URL= #POSTGRES DATABASE URL | ||
WEBHOOK_BASE_URL=https://webhook.notifine.com | ||
GITLAB_TELOXIDE_TOKEN= | ||
TRELLO_TELOXIDE_TOKEN= | ||
GITHUB_TELOXIDE_TOKEN= | ||
TRELLO_TELOXIDE_TOKEN= | ||
TELEGRAM_ADMIN_CHAT_ID= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
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_github_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_github(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) | ||
.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 Github project(repo), \ | ||
open Settings -> Webhooks and add this \ | ||
URL: {}/github/{}", | ||
env::var("WEBHOOK_BASE_URL").expect("WEBHOOK_BASE_URL must be set"), | ||
webhook_url.0 | ||
) | ||
}; | ||
|
||
send_message_github(telegram_chat_id, message).await?; | ||
|
||
if webhook_url.1 { | ||
// send message to admin on telegram and inform new install | ||
send_message_github( | ||
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 github webhook added: {telegram_chat_id}"), | ||
) | ||
.await?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn create_new_bot() -> Bot { | ||
Bot::new(env::var("GITHUB_TELOXIDE_TOKEN").expect("GITHUB_TELOXIDE_TOKEN must be set")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub mod github_bot; | ||
pub mod gitlab_bot; | ||
pub mod trello_bot; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
use crate::bots::github_bot::send_message_github; | ||
use crate::webhooks::github::webhook_handlers::{ping::handle_ping_event, push::handle_push_event}; | ||
use actix_web::{post, web, HttpRequest, HttpResponse, Responder}; | ||
use notifine::{find_chat_by_id, find_webhook_by_webhook_url}; | ||
use std::env; | ||
|
||
#[post("/github/{webhook_url}")] | ||
pub async fn handle_github_webhook( | ||
webhook_url: web::Path<String>, | ||
req: HttpRequest, | ||
body: web::Bytes, | ||
) -> impl Responder { | ||
if let Some(event_name) = req.headers().get("x-github-event") { | ||
log::info!("Event name: {:?}", event_name); | ||
let message = match event_name.to_str() { | ||
Ok("ping") => handle_ping_event(&body), | ||
Ok("push") => handle_push_event(&body), | ||
// _ => handle_unknown_event(&gitlab_event), | ||
_ => "".to_string(), | ||
}; | ||
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_github( | ||
chat.telegram_id | ||
.parse::<i64>() | ||
.expect("CHAT_ID must be an integer"), | ||
message, | ||
) | ||
.await | ||
.unwrap(); | ||
|
||
// send message to telegram admin | ||
send_message_github( | ||
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() // | ||
} else { | ||
HttpResponse::BadRequest() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub mod http_server; | ||
pub mod webhook_handlers; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub mod ping; | ||
pub mod push; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
use actix_web::web; | ||
use serde::Deserialize; | ||
use ureq::serde_json; | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct PingEvent { | ||
zen: String, | ||
repository: Repository, | ||
sender: Sender, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
struct Repository { | ||
html_url: String, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
struct Sender { | ||
login: String, | ||
} | ||
|
||
pub fn handle_ping_event(body: &web::Bytes) -> String { | ||
let ping_event: PingEvent = serde_json::from_slice(body).unwrap(); | ||
log::info!("Ping event"); | ||
log::info!("Zen: {}", ping_event.zen); | ||
|
||
format!( | ||
"Congratulations! A new webhook has been successfully configured for the \ | ||
repository: {repository_url}. This webhook was set up by \ | ||
<a href=\"https://github.com/{sender}\">{sender}</a>. Enjoy using your webhook!", | ||
sender = ping_event.sender.login, | ||
repository_url = ping_event.repository.html_url | ||
) | ||
} |
Oops, something went wrong.