Skip to content

Commit

Permalink
feat: add ping and push events for github (#20)
Browse files Browse the repository at this point in the history
* 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
mhkafadar committed Jan 21, 2024
1 parent 24cdc41 commit 6187cb2
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 10 deletions.
4 changes: 3 additions & 1 deletion .env.example
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=
182 changes: 182 additions & 0 deletions src/bots/github_bot.rs
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"))
}
1 change: 1 addition & 0 deletions src/bots/mod.rs
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;
2 changes: 2 additions & 0 deletions src/http_server.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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};
Expand All @@ -8,6 +9,7 @@ pub async fn run_http_server() -> std::io::Result<()> {
.wrap(middleware::Logger::default())
.service(health)
.service(handle_gitlab_webhook)
.service(handle_github_webhook)
.service(handle_trello_callback)
})
.bind(("0.0.0.0", 8080))?
Expand Down
16 changes: 7 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
use crate::bots::gitlab_bot::run_gitlab_bot;
use crate::bots::trello_bot::run_trello_bot;
use crate::http_server::run_http_server;

use dotenv::dotenv;
use tokio::task;
use std::env;
use tokio::task;

pub mod bots;
pub mod http_server;
pub mod webhooks;


use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use std::error::Error;
use diesel::prelude::*;
use diesel::PgConnection;
use crate::bots::github_bot::run_github_bot;
use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection};
use diesel::PgConnection;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};

pub type PgPool = Pool<ConnectionManager<PgConnection>>;
pub type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
Expand All @@ -35,8 +32,9 @@ async fn main() {
connection.run_pending_migrations(MIGRATIONS);

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

log::info!("Main 2");
log::info!("Main");
}
76 changes: 76 additions & 0 deletions src/webhooks/github/http_server.rs
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()
}
}
2 changes: 2 additions & 0 deletions src/webhooks/github/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod http_server;
pub mod webhook_handlers;
2 changes: 2 additions & 0 deletions src/webhooks/github/webhook_handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod ping;
pub mod push;
34 changes: 34 additions & 0 deletions src/webhooks/github/webhook_handlers/ping.rs
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
)
}
Loading

0 comments on commit 6187cb2

Please sign in to comment.