96
									
								
								src/args.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/args.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Parser, Debug)]
 | 
			
		||||
pub struct ServerConfig {
 | 
			
		||||
    /// Server's host.
 | 
			
		||||
    #[arg(
 | 
			
		||||
        name = "server-host",
 | 
			
		||||
        long,
 | 
			
		||||
        default_value = "0.0.0.0",
 | 
			
		||||
        env = "BOT_SERVER_HOST"
 | 
			
		||||
    )]
 | 
			
		||||
 | 
			
		||||
    /// Port that server is listening to.
 | 
			
		||||
    pub host: String,
 | 
			
		||||
    #[arg(
 | 
			
		||||
        name = "server-port",
 | 
			
		||||
        long,
 | 
			
		||||
        default_value = "8000",
 | 
			
		||||
        env = "BOT_SERVER_PORT"
 | 
			
		||||
    )]
 | 
			
		||||
    pub port: u16,
 | 
			
		||||
 | 
			
		||||
    /// This password is used to authenticate
 | 
			
		||||
    /// along with telegram code.
 | 
			
		||||
    #[arg(
 | 
			
		||||
        name = "server-pass",
 | 
			
		||||
        long,
 | 
			
		||||
        default_value = "pass",
 | 
			
		||||
        env = "BOT_SERVER_PASS"
 | 
			
		||||
    )]
 | 
			
		||||
    pub server_pass: String,
 | 
			
		||||
 | 
			
		||||
    /// This name is displayed on password page.
 | 
			
		||||
    #[arg(
 | 
			
		||||
        name = "server-username",
 | 
			
		||||
        long,
 | 
			
		||||
        default_value = "s3rius_san",
 | 
			
		||||
        env = "BOT_SERVER_USERNAME"
 | 
			
		||||
    )]
 | 
			
		||||
    pub username: String,
 | 
			
		||||
 | 
			
		||||
    #[arg(
 | 
			
		||||
        name = "server-static-dir",
 | 
			
		||||
        long,
 | 
			
		||||
        default_value = "static",
 | 
			
		||||
        env = "BOT_SERVER_STATIC_DIR"
 | 
			
		||||
    )]
 | 
			
		||||
    pub static_dir: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Parser, Debug)]
 | 
			
		||||
pub struct BotConfig {
 | 
			
		||||
    /// Telegram APP id. How to get it: https://core.telegram.org/api/obtaining_api_id
 | 
			
		||||
    #[arg(name = "bot-app-id", long, env = "BOT_APP_ID")]
 | 
			
		||||
    pub app_id: i32,
 | 
			
		||||
 | 
			
		||||
    /// Telegram API hash.
 | 
			
		||||
    #[arg(name = "bot-api-hash", long, env = "BOT_API_HASH")]
 | 
			
		||||
    pub api_hash: String,
 | 
			
		||||
 | 
			
		||||
    /// Your account's phone.
 | 
			
		||||
    #[arg(name = "bot-phone", long, env = "BOT_ACCOUNT_PHONE")]
 | 
			
		||||
    pub phone: String,
 | 
			
		||||
 | 
			
		||||
    /// Password for two-factor authentication.
 | 
			
		||||
    #[arg(name = "bot-tfa", long, env = "BOT_TFA_PASSWORD")]
 | 
			
		||||
    pub tfa_password: Option<String>,
 | 
			
		||||
 | 
			
		||||
    /// Name of a file to save session to.
 | 
			
		||||
    #[arg(
 | 
			
		||||
        name = "bot-session-file",
 | 
			
		||||
        long,
 | 
			
		||||
        default_value = "saved.session",
 | 
			
		||||
        env = "BOT_SESSION_FILE"
 | 
			
		||||
    )]
 | 
			
		||||
    pub session_file: String,
 | 
			
		||||
 | 
			
		||||
    #[arg(name = "bot-excluded-chats", long, env = "BOT_EXCLUDED_CHATS")]
 | 
			
		||||
    pub excluded_chats: Vec<i64>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Parser, Debug)]
 | 
			
		||||
#[command(author, version, about)]
 | 
			
		||||
pub struct Arguments {
 | 
			
		||||
    /// Application log level.
 | 
			
		||||
    #[arg(long, default_value = "INFO", env = "BOT_LOG_LEVEL")]
 | 
			
		||||
    pub log_level: log::LevelFilter,
 | 
			
		||||
 | 
			
		||||
    #[command(flatten)]
 | 
			
		||||
    pub server: ServerConfig,
 | 
			
		||||
 | 
			
		||||
    #[command(flatten)]
 | 
			
		||||
    pub bot: BotConfig,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								src/bot/filters/base.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/bot/filters/base.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
use dyn_clone::DynClone;
 | 
			
		||||
use grammers_client::Update;
 | 
			
		||||
 | 
			
		||||
pub trait Filter: DynClone + Send + Sync {
 | 
			
		||||
    fn filter(&self, update: &Update) -> anyhow::Result<bool>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dyn_clone::clone_trait_object!(Filter);
 | 
			
		||||
 | 
			
		||||
impl<T> Filter for T
 | 
			
		||||
where
 | 
			
		||||
    T: Fn(&Update) -> anyhow::Result<bool> + Clone + Send + Sync,
 | 
			
		||||
{
 | 
			
		||||
    fn filter(&self, update: &Update) -> anyhow::Result<bool> {
 | 
			
		||||
        self(update)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/bot/filters/chain.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/bot/filters/chain.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
use grammers_client::Update;
 | 
			
		||||
 | 
			
		||||
use crate::bot::handlers::Handler;
 | 
			
		||||
 | 
			
		||||
use super::base::Filter;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct FilteredHandler {
 | 
			
		||||
    filters: Vec<Box<dyn Filter>>,
 | 
			
		||||
    pub handler: Box<dyn Handler>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FilteredHandler {
 | 
			
		||||
    pub fn new<H: Handler + 'static>(handler: H) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            filters: vec![],
 | 
			
		||||
            handler: Box::new(handler),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_filter<F: Filter + 'static>(mut self, filter: F) -> Self {
 | 
			
		||||
        self.filters.push(Box::new(filter));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn check(&self, update: &Update) -> bool {
 | 
			
		||||
        for filter in &self.filters {
 | 
			
		||||
            match filter.filter(update) {
 | 
			
		||||
                Err(error) => {
 | 
			
		||||
                    log::error!("Fitler error: {error}");
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                Ok(false) => return false,
 | 
			
		||||
                _ => {}
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								src/bot/filters/groups.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/bot/filters/groups.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										74
									
								
								src/bot/filters/message_fitlers.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/bot/filters/message_fitlers.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,74 @@
 | 
			
		||||
use grammers_client::Update;
 | 
			
		||||
 | 
			
		||||
use super::base::Filter;
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 | 
			
		||||
pub enum MessageDirection {
 | 
			
		||||
    Outgoing,
 | 
			
		||||
    Incoming,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 | 
			
		||||
pub enum TextMatchMethod {
 | 
			
		||||
    IStartsWith,
 | 
			
		||||
    IMatches,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct ExcludedChatsFilter(pub Vec<i64>);
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct MessageDirectionFilter(pub MessageDirection);
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct TextFilter<'a>(pub &'a [&'a str], pub TextMatchMethod);
 | 
			
		||||
 | 
			
		||||
impl Filter for ExcludedChatsFilter {
 | 
			
		||||
    fn filter(&self, update: &Update) -> anyhow::Result<bool> {
 | 
			
		||||
        let a = match update {
 | 
			
		||||
            Update::NewMessage(message) | Update::MessageEdited(message) => message.chat(),
 | 
			
		||||
            Update::CallbackQuery(query) => query.chat().clone(),
 | 
			
		||||
            _ => return Ok(false),
 | 
			
		||||
        };
 | 
			
		||||
        if self.0.contains(&a.id()) {
 | 
			
		||||
            return Ok(false);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Filter for MessageDirectionFilter {
 | 
			
		||||
    fn filter(&self, update: &Update) -> anyhow::Result<bool> {
 | 
			
		||||
        let Update::NewMessage(message) = update else {return Ok(false)};
 | 
			
		||||
 | 
			
		||||
        let res = matches!(
 | 
			
		||||
            (self.0, message.outgoing()),
 | 
			
		||||
            (MessageDirection::Outgoing, true) | (MessageDirection::Incoming, false)
 | 
			
		||||
        );
 | 
			
		||||
        Ok(res)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Filter for TextFilter<'a> {
 | 
			
		||||
    fn filter(&self, update: &Update) -> anyhow::Result<bool> {
 | 
			
		||||
        let message_text = match update {
 | 
			
		||||
            Update::NewMessage(message) | Update::MessageEdited(message) => message.text(),
 | 
			
		||||
            Update::InlineQuery(query) => query.text(),
 | 
			
		||||
            _ => return Ok(false),
 | 
			
		||||
        };
 | 
			
		||||
        for text in self.0 {
 | 
			
		||||
            let matches = match self.1 {
 | 
			
		||||
                TextMatchMethod::IStartsWith => message_text
 | 
			
		||||
                    .to_lowercase()
 | 
			
		||||
                    .starts_with(text.to_lowercase().as_str()),
 | 
			
		||||
                TextMatchMethod::IMatches => message_text.to_lowercase() == text.to_lowercase(),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if matches {
 | 
			
		||||
                return Ok(true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(false)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								src/bot/filters/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/bot/filters/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
mod base;
 | 
			
		||||
pub mod chain;
 | 
			
		||||
pub mod message_fitlers;
 | 
			
		||||
							
								
								
									
										10
									
								
								src/bot/handlers/base.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/bot/handlers/base.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use dyn_clone::DynClone;
 | 
			
		||||
use grammers_client::{Client, Update};
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
pub trait Handler: DynClone + Sync + Send {
 | 
			
		||||
    async fn react(&self, client: &Client, update: &Update) -> anyhow::Result<()>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dyn_clone::clone_trait_object!(Handler);
 | 
			
		||||
							
								
								
									
										17
									
								
								src/bot/handlers/basic/get_chat_id.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/bot/handlers/basic/get_chat_id.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
use grammers_client::{Client, Update};
 | 
			
		||||
 | 
			
		||||
use crate::{bot::handlers::Handler, utils::messages::get_message};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct GetChatId;
 | 
			
		||||
 | 
			
		||||
#[async_trait::async_trait]
 | 
			
		||||
impl Handler for GetChatId {
 | 
			
		||||
    async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> {
 | 
			
		||||
        let message = get_message(update);
 | 
			
		||||
        if let Some(msg) = message {
 | 
			
		||||
            msg.reply(msg.chat().id().to_string()).await?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								src/bot/handlers/basic/help.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/bot/handlers/basic/help.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
use grammers_client::{Client, Update};
 | 
			
		||||
 | 
			
		||||
use crate::bot::handlers::Handler;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Help;
 | 
			
		||||
 | 
			
		||||
#[async_trait::async_trait]
 | 
			
		||||
impl Handler for Help {
 | 
			
		||||
    async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> {
 | 
			
		||||
        let Update::NewMessage(message) = update else {return  Ok(())};
 | 
			
		||||
 | 
			
		||||
        message.reply("Хелпа").await?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								src/bot/handlers/basic/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/bot/handlers/basic/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
pub mod get_chat_id;
 | 
			
		||||
pub mod help;
 | 
			
		||||
							
								
								
									
										39
									
								
								src/bot/handlers/fun/blyaficator.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/bot/handlers/fun/blyaficator.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    bot::handlers::Handler,
 | 
			
		||||
    utils::{inter_join::RandomIntersperse, messages::get_message},
 | 
			
		||||
};
 | 
			
		||||
use grammers_client::{Client, Update};
 | 
			
		||||
 | 
			
		||||
const BLYA_WORDS: &[&str] = &[", бля,", ", сука,", ", ёбаный рот,", ", охуеть конечно,"];
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Blyaficator;
 | 
			
		||||
 | 
			
		||||
#[async_trait::async_trait]
 | 
			
		||||
impl Handler for Blyaficator {
 | 
			
		||||
    async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> {
 | 
			
		||||
        if let Some(message) = get_message(update) {
 | 
			
		||||
            let maybe_blyaficated = message.text().strip_prefix(".bl").map(|stripped| {
 | 
			
		||||
                stripped
 | 
			
		||||
                    // Trim string after removing prefix, to
 | 
			
		||||
                    // remove leading spaces.
 | 
			
		||||
                    .trim()
 | 
			
		||||
                    // split by commas
 | 
			
		||||
                    .split(',')
 | 
			
		||||
                    // choose random strings from BLYA_WORDS
 | 
			
		||||
                    // and insert them between splitted strings.
 | 
			
		||||
                    .random_itersperse(BLYA_WORDS, &mut rand::thread_rng())
 | 
			
		||||
                    // Collect it back to vec
 | 
			
		||||
                    .collect::<Vec<_>>()
 | 
			
		||||
                    // Creating one string with all words concatenated.
 | 
			
		||||
                    .join("")
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // If the text was blyaficated we send it as a reply.
 | 
			
		||||
            if let Some(blyficated) = maybe_blyaficated {
 | 
			
		||||
                message.reply(blyficated).await?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/bot/handlers/fun/greeter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/bot/handlers/fun/greeter.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use grammers_client::{Client, Update};
 | 
			
		||||
use rand::seq::IteratorRandom;
 | 
			
		||||
 | 
			
		||||
use crate::bot::handlers::base::Handler;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Greeter;
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl Handler for Greeter {
 | 
			
		||||
    async fn react(&self, client: &Client, update: &Update) -> anyhow::Result<()> {
 | 
			
		||||
        let Update::NewMessage(message) = update else {return Ok(())};
 | 
			
		||||
 | 
			
		||||
        // Check if chat has less than 100 participants.
 | 
			
		||||
        let participants = client.iter_participants(message.chat()).total().await?;
 | 
			
		||||
        if participants >= 100 {
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let reply_text = ["Привет!", "Добрый день!", "Здравствуйте.", "Приетствую"]
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .choose(&mut rand::thread_rng());
 | 
			
		||||
 | 
			
		||||
        if let Some(text) = reply_text {
 | 
			
		||||
            message.reply(text).await?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								src/bot/handlers/fun/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/bot/handlers/fun/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
pub mod blyaficator;
 | 
			
		||||
pub mod greeter;
 | 
			
		||||
							
								
								
									
										5
									
								
								src/bot/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/bot/handlers/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
pub mod base;
 | 
			
		||||
pub mod basic;
 | 
			
		||||
pub mod fun;
 | 
			
		||||
 | 
			
		||||
pub use base::Handler;
 | 
			
		||||
							
								
								
									
										131
									
								
								src/bot/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/bot/main.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
			
		||||
use std::{sync::Arc, time::Duration};
 | 
			
		||||
 | 
			
		||||
use crate::args::BotConfig;
 | 
			
		||||
use grammers_client::{Client, Config, SignInError, Update};
 | 
			
		||||
use grammers_session::Session;
 | 
			
		||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
 | 
			
		||||
use tokio::sync::RwLock;
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
    filters::{
 | 
			
		||||
        chain::FilteredHandler,
 | 
			
		||||
        message_fitlers::{
 | 
			
		||||
            ExcludedChatsFilter, MessageDirection, MessageDirectionFilter, TextFilter,
 | 
			
		||||
            TextMatchMethod,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    handlers::{
 | 
			
		||||
        basic::{get_chat_id::GetChatId, help::Help},
 | 
			
		||||
        fun::{blyaficator::Blyaficator, greeter::Greeter},
 | 
			
		||||
        Handler,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
async fn authorize(
 | 
			
		||||
    args: &BotConfig,
 | 
			
		||||
    client: &Client,
 | 
			
		||||
    web_code: Arc<RwLock<Option<String>>>,
 | 
			
		||||
) -> anyhow::Result<()> {
 | 
			
		||||
    log::info!("Requesting login code.");
 | 
			
		||||
    let token = client
 | 
			
		||||
        .request_login_code(&args.phone, args.app_id, &args.api_hash)
 | 
			
		||||
        .await?;
 | 
			
		||||
    let mut code = None;
 | 
			
		||||
 | 
			
		||||
    while code.is_none() {
 | 
			
		||||
        tokio::time::sleep(Duration::from_secs(1)).await;
 | 
			
		||||
        {
 | 
			
		||||
            code = web_code.read().await.clone();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let signed_in = client.sign_in(&token, &code.unwrap()).await;
 | 
			
		||||
    match signed_in {
 | 
			
		||||
        Err(SignInError::PasswordRequired(password_token)) => {
 | 
			
		||||
            // Note: this `prompt` method will echo the password in the console.
 | 
			
		||||
            //       Real code might want to use a better way to handle this.
 | 
			
		||||
            let hint = password_token.hint().unwrap_or("None");
 | 
			
		||||
            let password = args
 | 
			
		||||
                .tfa_password
 | 
			
		||||
                .as_ref()
 | 
			
		||||
                .unwrap_or_else(|| panic!("2FA password is required. Hint for password: `{hint}`"));
 | 
			
		||||
            log::info!("Checking client's password.");
 | 
			
		||||
            client
 | 
			
		||||
                .check_password(password_token, password.trim())
 | 
			
		||||
                .await?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(_) => (),
 | 
			
		||||
        Err(e) => panic!("{}", e),
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn handle_with_log(handler: Box<dyn Handler>, client: Client, update_data: Update) {
 | 
			
		||||
    if let Err(err) = handler.react(&client, &update_data).await {
 | 
			
		||||
        log::error!("{err}");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn run(args: BotConfig, client: Client) {
 | 
			
		||||
    let handlers: Vec<FilteredHandler> = vec![
 | 
			
		||||
        FilteredHandler::new(Greeter)
 | 
			
		||||
            .add_filter(MessageDirectionFilter(MessageDirection::Incoming))
 | 
			
		||||
            .add_filter(TextFilter(&["привет"], TextMatchMethod::IStartsWith))
 | 
			
		||||
            .add_filter(ExcludedChatsFilter(args.excluded_chats)),
 | 
			
		||||
        FilteredHandler::new(Help).add_filter(TextFilter(&[".h"], TextMatchMethod::IMatches)),
 | 
			
		||||
        FilteredHandler::new(GetChatId)
 | 
			
		||||
            .add_filter(TextFilter(&[".cid"], TextMatchMethod::IMatches)),
 | 
			
		||||
        FilteredHandler::new(Blyaficator)
 | 
			
		||||
            .add_filter(TextFilter(&[".bl"], TextMatchMethod::IStartsWith)),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        let update = client.next_update().await;
 | 
			
		||||
        if update.is_err() {
 | 
			
		||||
            log::error!("{}", update.unwrap_err());
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(update_data) = update.unwrap() {
 | 
			
		||||
            let update_ref = &update_data;
 | 
			
		||||
            let matched_handlers = handlers
 | 
			
		||||
                .par_iter()
 | 
			
		||||
                .filter(move |val| val.check(update_ref))
 | 
			
		||||
                .collect::<Vec<_>>();
 | 
			
		||||
            for handler in matched_handlers {
 | 
			
		||||
                tokio::spawn(handle_with_log(
 | 
			
		||||
                    handler.handler.clone(),
 | 
			
		||||
                    client.clone(),
 | 
			
		||||
                    update_data.clone(),
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn start(args: BotConfig, web_code: Arc<RwLock<Option<String>>>) -> anyhow::Result<()> {
 | 
			
		||||
    log::info!("Connecting to Telegram...");
 | 
			
		||||
    let client = Client::connect(Config {
 | 
			
		||||
        session: Session::load_file_or_create(args.session_file.as_str())?,
 | 
			
		||||
        api_id: args.app_id,
 | 
			
		||||
        api_hash: args.api_hash.clone(),
 | 
			
		||||
        params: grammers_client::InitParams {
 | 
			
		||||
            device_model: String::from("MEME_MACHINE"),
 | 
			
		||||
            catch_up: false,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        },
 | 
			
		||||
    })
 | 
			
		||||
    .await?;
 | 
			
		||||
    log::info!("Connected!");
 | 
			
		||||
    if client.is_authorized().await? {
 | 
			
		||||
        // If we already authrized, we write random token, so web won't update it.
 | 
			
		||||
        let mut code_writer = web_code.write().await;
 | 
			
		||||
        *code_writer = Some(String::new());
 | 
			
		||||
    } else {
 | 
			
		||||
        authorize(&args, &client, web_code).await?;
 | 
			
		||||
        client.session().save_to_file(args.session_file.as_str())?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    run(args.clone(), client).await;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/bot/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/bot/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
mod filters;
 | 
			
		||||
mod handlers;
 | 
			
		||||
mod main;
 | 
			
		||||
pub mod utils;
 | 
			
		||||
 | 
			
		||||
pub use main::start;
 | 
			
		||||
							
								
								
									
										1
									
								
								src/bot/utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/bot/utils.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								src/logging.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/logging.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
use fern::colors::{Color, ColoredLevelConfig};
 | 
			
		||||
 | 
			
		||||
pub fn setup_logger(log_level: log::LevelFilter) -> anyhow::Result<()> {
 | 
			
		||||
    let colors = ColoredLevelConfig::new()
 | 
			
		||||
        // use builder methods
 | 
			
		||||
        .info(Color::Green)
 | 
			
		||||
        .warn(Color::Yellow)
 | 
			
		||||
        .debug(Color::BrightCyan)
 | 
			
		||||
        .error(Color::BrightRed)
 | 
			
		||||
        .trace(Color::Blue);
 | 
			
		||||
 | 
			
		||||
    fern::Dispatch::new()
 | 
			
		||||
        .format(move |out, message, record| {
 | 
			
		||||
            out.finish(format_args!(
 | 
			
		||||
                "{}[{}][{}] {}",
 | 
			
		||||
                chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
 | 
			
		||||
                record.target(),
 | 
			
		||||
                colors.color(record.level()),
 | 
			
		||||
                message
 | 
			
		||||
            ));
 | 
			
		||||
        })
 | 
			
		||||
        .level(log_level)
 | 
			
		||||
        .chain(std::io::stdout())
 | 
			
		||||
        .apply()?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
use futures::{stream::FuturesUnordered, StreamExt};
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use tokio::sync::RwLock;
 | 
			
		||||
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use logging::setup_logger;
 | 
			
		||||
 | 
			
		||||
mod args;
 | 
			
		||||
mod bot;
 | 
			
		||||
mod logging;
 | 
			
		||||
mod server;
 | 
			
		||||
pub mod utils;
 | 
			
		||||
 | 
			
		||||
#[tokio::main(flavor = "current_thread")]
 | 
			
		||||
async fn main() -> anyhow::Result<()> {
 | 
			
		||||
    dotenvy::dotenv().ok();
 | 
			
		||||
    let args = args::Arguments::parse();
 | 
			
		||||
    setup_logger(args.log_level)?;
 | 
			
		||||
 | 
			
		||||
    let token_lock = Arc::new(RwLock::new(None));
 | 
			
		||||
 | 
			
		||||
    let bot_token = token_lock.clone();
 | 
			
		||||
    let server_token = token_lock.clone();
 | 
			
		||||
 | 
			
		||||
    let local_set = tokio::task::LocalSet::new();
 | 
			
		||||
    local_set
 | 
			
		||||
        .run_until(async {
 | 
			
		||||
            let tasks = vec![
 | 
			
		||||
                tokio::task::spawn_local(bot::start(args.bot.clone(), bot_token)),
 | 
			
		||||
                tokio::task::spawn_local(server::start(args.server.clone(), server_token)),
 | 
			
		||||
            ];
 | 
			
		||||
            // Here we wait for one async task to complete.
 | 
			
		||||
            let completed = tasks
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .collect::<FuturesUnordered<_>>()
 | 
			
		||||
                .take(1)
 | 
			
		||||
                .collect::<Vec<_>>()
 | 
			
		||||
                .await;
 | 
			
		||||
            // Now we get all completed futures (one future),
 | 
			
		||||
            // and return it's result.
 | 
			
		||||
            if let Some(fut) = completed.into_iter().next() {
 | 
			
		||||
                fut?
 | 
			
		||||
            } else {
 | 
			
		||||
                Ok(())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/server/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/server/main.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use tokio::sync::RwLock;
 | 
			
		||||
 | 
			
		||||
use actix_web::{web::Data, App, HttpServer};
 | 
			
		||||
 | 
			
		||||
use crate::args::ServerConfig;
 | 
			
		||||
 | 
			
		||||
use super::routes::{healthcheck, index, login};
 | 
			
		||||
 | 
			
		||||
pub async fn start(args: ServerConfig, token: Arc<RwLock<Option<String>>>) -> anyhow::Result<()> {
 | 
			
		||||
    let addr = (args.host.clone(), args.port);
 | 
			
		||||
    HttpServer::new(move || {
 | 
			
		||||
        App::new()
 | 
			
		||||
            .wrap(actix_web::middleware::Logger::new(
 | 
			
		||||
                "\"%r\" \"-\" \"%s\" \"%a\" \"%D\"",
 | 
			
		||||
            ))
 | 
			
		||||
            .app_data(Data::new(token.clone()))
 | 
			
		||||
            .app_data(Data::new(args.clone()))
 | 
			
		||||
            .service(login)
 | 
			
		||||
            .service(index)
 | 
			
		||||
            .service(healthcheck)
 | 
			
		||||
            .service(actix_files::Files::new("/static", args.static_dir.clone()))
 | 
			
		||||
    })
 | 
			
		||||
    .bind(addr)?
 | 
			
		||||
    .workers(1)
 | 
			
		||||
    .run()
 | 
			
		||||
    .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/server/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/server/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
mod main;
 | 
			
		||||
mod routes;
 | 
			
		||||
mod schema;
 | 
			
		||||
mod templates;
 | 
			
		||||
 | 
			
		||||
pub use main::start;
 | 
			
		||||
							
								
								
									
										49
									
								
								src/server/routes.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/server/routes.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
use actix_web::{
 | 
			
		||||
    get, post,
 | 
			
		||||
    web::{Data, Json},
 | 
			
		||||
    HttpResponse, Responder,
 | 
			
		||||
};
 | 
			
		||||
use tokio::sync::RwLock;
 | 
			
		||||
 | 
			
		||||
use super::{schema::LoginRequestDTO, templates::Index};
 | 
			
		||||
use crate::args::ServerConfig;
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::unused_async)]
 | 
			
		||||
#[get("/health")]
 | 
			
		||||
async fn healthcheck() -> impl Responder {
 | 
			
		||||
    HttpResponse::Ok().finish()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[post("/login")]
 | 
			
		||||
pub async fn login(
 | 
			
		||||
    code_data: Json<LoginRequestDTO>,
 | 
			
		||||
    token: Data<Arc<RwLock<Option<String>>>>,
 | 
			
		||||
    config: Data<ServerConfig>,
 | 
			
		||||
) -> impl Responder {
 | 
			
		||||
    if code_data.password != config.server_pass {
 | 
			
		||||
        log::warn!("Passwords don't match");
 | 
			
		||||
        return HttpResponse::BadRequest().body("Passwords don't match");
 | 
			
		||||
    }
 | 
			
		||||
    if token.read().await.is_some() {
 | 
			
		||||
        log::warn!("Token is already set.");
 | 
			
		||||
        return HttpResponse::Conflict().body("Token is already set");
 | 
			
		||||
    }
 | 
			
		||||
    let mut token_write = token.write().await;
 | 
			
		||||
    *token_write = Some(code_data.code.clone());
 | 
			
		||||
    log::info!("Token is updated!");
 | 
			
		||||
    HttpResponse::Ok().finish()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/")]
 | 
			
		||||
pub async fn index(
 | 
			
		||||
    token: Data<Arc<RwLock<Option<String>>>>,
 | 
			
		||||
    config: Data<ServerConfig>,
 | 
			
		||||
) -> impl Responder {
 | 
			
		||||
    Index {
 | 
			
		||||
        activated: token.read().await.is_some(),
 | 
			
		||||
        username: config.username.clone(),
 | 
			
		||||
        image_num: rand::random::<u8>() % 3,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/server/schema.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/server/schema.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct LoginRequestDTO {
 | 
			
		||||
    pub code: String,
 | 
			
		||||
    pub password: String,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								src/server/templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/server/templates/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,181 @@
 | 
			
		||||
<html lang="en">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
    <link href="/static/nes.min.css" rel="stylesheet" />
 | 
			
		||||
 | 
			
		||||
    <style>
 | 
			
		||||
        @font-face {
 | 
			
		||||
            font-family: 'Press Start 2P';
 | 
			
		||||
            font-style: normal;
 | 
			
		||||
            font-weight: 400;
 | 
			
		||||
            src: url(/static/fonts/ps2p_latin.woff2) format('woff2');
 | 
			
		||||
            unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        html,
 | 
			
		||||
        body,
 | 
			
		||||
        pre,
 | 
			
		||||
        code,
 | 
			
		||||
        kbd,
 | 
			
		||||
        samp {
 | 
			
		||||
            font-family: "Press Start 2P", serif;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <style>
 | 
			
		||||
        .center {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 50%;
 | 
			
		||||
            left: 50%;
 | 
			
		||||
            margin: 0 -50% 0 0;
 | 
			
		||||
            transform: translate(-50%, -50%);
 | 
			
		||||
            z-index: 10000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .large-center-text {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            font-size: large;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .at_bottom {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            top: 100%;
 | 
			
		||||
            font-size: large;
 | 
			
		||||
            transform: translate(-0%, -100%)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        html {
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            background: #A1FFCE;
 | 
			
		||||
            /* fallback for old browsers */
 | 
			
		||||
            background: -webkit-linear-gradient(to right, #FAFFD1, #A1FFCE);
 | 
			
		||||
            /* Chrome 10-25, Safari 5.1-6 */
 | 
			
		||||
            background: linear-gradient(to right, #FAFFD1, #A1FFCE);
 | 
			
		||||
            /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .mt-15 {
 | 
			
		||||
            margin-top: 15px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .nes-input[type='submit']:hover {
 | 
			
		||||
            background-color: #76c442;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .nes-input {
 | 
			
		||||
            cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABFklEQVRYR9WXURLDIAhE6/0PbSdOtUpcd1Gnpv1KGpTHBpCE1/cXq+vrMph7dGvXZTtpfW10DCA5jrH1H0Jhs5E0hnZdCR+vb5S8Nn8mQCeS9BdSalYJqMBjAGzq59xAESN7VFVUgV8AZB/dZBR7QTFDCqGquvUBVVoEtgIwpQRzmANSFHgWQKExHdIrPeuMvQNDarXe6nC/AutgV3JW+6bgqQLeV8FekRtgV+ToDKEKnACYKsfZjjkam7a0ZpYTytwmgainpC3HvwBocgKOxqRjehoR9DFKNFYtOwCGYCszobeCbl26N6yyQ6g8X/Wex/rBPsNEV6qAMaJPMynIHQCoSqS9JSMmwef51LflTgCRszU7DvAGiV6mHWfsaVUAAAAASUVORK5CYII=), auto;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .mtr-2 {
 | 
			
		||||
            margin-top: 2rem;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    <title>Bot activation</title>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
 | 
			
		||||
    {% if activated %}
 | 
			
		||||
 | 
			
		||||
    <div class="center large-center-text">
 | 
			
		||||
        <div>
 | 
			
		||||
            Hi, there! The bot is running.
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            Just write a ".h" to <a href="tg://resolve?domain={{username}}">@{{username}}</a> in telegram.
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="mtr-2">
 | 
			
		||||
            If you want to raise an issue or request feature,
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            you can ask <a href="tg://resolve?domain={{username}}">me</a> for that
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {%else%}
 | 
			
		||||
 | 
			
		||||
    <div class="center large-center-text">
 | 
			
		||||
        <div>
 | 
			
		||||
            Welcome back, {{ username }}!
 | 
			
		||||
        </div>
 | 
			
		||||
        <dialog class="nes-dialog is-error" id="dialog-error">
 | 
			
		||||
            <form method="dialog">
 | 
			
		||||
                <p class="title">Failed to authorize.</p>
 | 
			
		||||
                <p id="error_description"></p>
 | 
			
		||||
                <menu class="dialog-menu">
 | 
			
		||||
                    <button class="nes-btn is-primary">Confirm</button>
 | 
			
		||||
                </menu>
 | 
			
		||||
            </form>
 | 
			
		||||
        </dialog>
 | 
			
		||||
        <dialog class="nes-dialog" id="dialog-success">
 | 
			
		||||
            <form method="dialog">
 | 
			
		||||
                <p class="title">Successfully authorized</p>
 | 
			
		||||
                <p>You were successfully authorized.</p>
 | 
			
		||||
                <menu class="dialog-menu">
 | 
			
		||||
                    <button class="nes-btn is-primary" onclick="location.reload()">Confirm</button>
 | 
			
		||||
                </menu>
 | 
			
		||||
            </form>
 | 
			
		||||
        </dialog>
 | 
			
		||||
        <form id="code_form" action="/code" method="post" class="mtr-2" autocomplete="off">
 | 
			
		||||
            <div class="nes-field is-inline">
 | 
			
		||||
                <label for="code_field">Verification code</label>
 | 
			
		||||
                <input type="text" id="code_field" class="nes-input is-success" name="code" placeholder="Your code">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="nes-field is-inline mt-15">
 | 
			
		||||
                <label for="pass_field">Password</label>
 | 
			
		||||
                <input type="password" id="pass_field" class="nes-input is-success" name="password"
 | 
			
		||||
                    placeholder="Password">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="nes-field mt-15">
 | 
			
		||||
                <input type="submit" class="nes-input is-success" value="Submit code">
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <img src="/static/images/girl_{{image_num}}.png" alt="" class="at_bottom">
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
{% if !activated %}
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    function authorize(form) {
 | 
			
		||||
        // Bind the FormData object and the form element
 | 
			
		||||
        const form_data = new FormData(form);
 | 
			
		||||
        // Construct request JSON.
 | 
			
		||||
        let request_json = {};
 | 
			
		||||
        form_data.forEach((value, key) => {
 | 
			
		||||
            request_json[key] = value;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        fetch("/login", {
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Content-Type": "application/json",
 | 
			
		||||
            },
 | 
			
		||||
            body: JSON.stringify(request_json),
 | 
			
		||||
        }).then(async response => {
 | 
			
		||||
            if (response.status == 200) {
 | 
			
		||||
                document.getElementById('dialog-success').showModal();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            let desc = document.getElementById('error_description');
 | 
			
		||||
            desc.innerText = await response.text();
 | 
			
		||||
            document.getElementById('dialog-error').showModal();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const form = document.getElementById("code_form");
 | 
			
		||||
 | 
			
		||||
    // ...and take over its submit event.
 | 
			
		||||
    form.addEventListener("submit", function (event) {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        authorize(form);
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										9
									
								
								src/server/templates/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/server/templates/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
use askama::Template;
 | 
			
		||||
 | 
			
		||||
#[derive(Template)]
 | 
			
		||||
#[template(path = "index.html")]
 | 
			
		||||
pub struct Index {
 | 
			
		||||
    pub activated: bool,
 | 
			
		||||
    pub username: String,
 | 
			
		||||
    pub image_num: u8,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								src/utils/inter_join.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/utils/inter_join.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
			
		||||
use std::iter::Peekable;
 | 
			
		||||
 | 
			
		||||
use rand::{seq::SliceRandom, Rng};
 | 
			
		||||
 | 
			
		||||
/// Trait for creating random intersperse.
 | 
			
		||||
///
 | 
			
		||||
/// It's implemented for all iterables.
 | 
			
		||||
///
 | 
			
		||||
/// The usage is the following:
 | 
			
		||||
/// ```
 | 
			
		||||
/// let words = &[4, 5, 6];
 | 
			
		||||
/// let result = [1, 2, 3]
 | 
			
		||||
///     .into_iter()
 | 
			
		||||
///     .random_itersperse(words, &mut rand::thread_rng())
 | 
			
		||||
///     .collect::<Vec<_>>();
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// Now if you print the result, you'll see that
 | 
			
		||||
/// after every word from the source slice,
 | 
			
		||||
/// placed one word from the `words` slice.
 | 
			
		||||
/// Like this: `[1, 6, 2, 4, 3]`.
 | 
			
		||||
pub trait RandomIntersperse<'a, L, R>
 | 
			
		||||
where
 | 
			
		||||
    L: IntoIterator,
 | 
			
		||||
    R: Rng,
 | 
			
		||||
{
 | 
			
		||||
    fn random_itersperse(
 | 
			
		||||
        self,
 | 
			
		||||
        choises: &'a [L::Item],
 | 
			
		||||
        random: &'a mut R,
 | 
			
		||||
    ) -> RandomIntersperseStruct<'a, L, R>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Struct used to create `random_intresperse`.
 | 
			
		||||
/// It has a peekable iterator, a reference to a
 | 
			
		||||
/// random generator, a slice for items to choose from
 | 
			
		||||
/// and boolean to check current state.
 | 
			
		||||
///
 | 
			
		||||
/// The iterator is peekable, because we need to check
 | 
			
		||||
/// if next item exists, to avoid inserting
 | 
			
		||||
/// a generated value at the end of iterator.
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct RandomIntersperseStruct<'a, L, R>
 | 
			
		||||
where
 | 
			
		||||
    L: IntoIterator,
 | 
			
		||||
    R: Rng,
 | 
			
		||||
{
 | 
			
		||||
    iterator: Peekable<L::IntoIter>,
 | 
			
		||||
    choices: &'a [L::Item],
 | 
			
		||||
    random: &'a mut R,
 | 
			
		||||
    use_iter: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Implement a `RandomIntersperse` trait for all
 | 
			
		||||
/// items that can be turned in iterators.
 | 
			
		||||
impl<'a, L, R> RandomIntersperse<'a, L, R> for L
 | 
			
		||||
where
 | 
			
		||||
    L: IntoIterator,
 | 
			
		||||
    R: Rng,
 | 
			
		||||
{
 | 
			
		||||
    fn random_itersperse(
 | 
			
		||||
        self,
 | 
			
		||||
        choices: &'a [L::Item],
 | 
			
		||||
        random: &'a mut R,
 | 
			
		||||
    ) -> RandomIntersperseStruct<'a, L, R> {
 | 
			
		||||
        RandomIntersperseStruct {
 | 
			
		||||
            random,
 | 
			
		||||
            choices,
 | 
			
		||||
            use_iter: true,
 | 
			
		||||
            iterator: self.into_iter().peekable(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Implementation of an interator for a randomitersperse structure,
 | 
			
		||||
/// so it can be used in chain.
 | 
			
		||||
impl<'a, L, R> Iterator for RandomIntersperseStruct<'a, L, R>
 | 
			
		||||
where
 | 
			
		||||
    L: IntoIterator,
 | 
			
		||||
    R: Rng,
 | 
			
		||||
    L::Item: Clone,
 | 
			
		||||
{
 | 
			
		||||
    // The type of item is the same as for
 | 
			
		||||
    // original iterator.
 | 
			
		||||
    type Item = L::Item;
 | 
			
		||||
 | 
			
		||||
    fn next(&mut self) -> Option<Self::Item> {
 | 
			
		||||
        // Peek a value from the iterator to check if we have values.
 | 
			
		||||
        self.iterator.peek()?;
 | 
			
		||||
 | 
			
		||||
        let choise = self.choices.choose(self.random);
 | 
			
		||||
        if choise.is_none() {
 | 
			
		||||
            self.use_iter = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.use_iter {
 | 
			
		||||
            // We change use_iter, so a random
 | 
			
		||||
            // value is chosen on the next step.
 | 
			
		||||
            self.use_iter = false;
 | 
			
		||||
            self.iterator.next()
 | 
			
		||||
        } else {
 | 
			
		||||
            self.use_iter = true;
 | 
			
		||||
            self.choices.choose(self.random).cloned()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::utils::inter_join::RandomIntersperse;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    pub fn success() {
 | 
			
		||||
        let randoms = &[4, 5, 6];
 | 
			
		||||
        let result = [1, 2, 3]
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .random_itersperse(randoms, &mut rand::thread_rng())
 | 
			
		||||
            .collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
        for value in [1, 2, 3] {
 | 
			
		||||
            assert!(result.contains(&value));
 | 
			
		||||
        }
 | 
			
		||||
        assert_eq!(result.len(), 5);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    pub fn empty_array() {
 | 
			
		||||
        let randoms = &[];
 | 
			
		||||
        let result = [1, 2, 3]
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .random_itersperse(randoms, &mut rand::thread_rng())
 | 
			
		||||
            .collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
        for value in [1, 2, 3] {
 | 
			
		||||
            assert!(result.contains(&value));
 | 
			
		||||
        }
 | 
			
		||||
        assert_eq!(result.len(), 3);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/utils/messages.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/utils/messages.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
use grammers_client::{types::Message, Update};
 | 
			
		||||
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub fn get_message(update: &Update) -> Option<&Message> {
 | 
			
		||||
    match update {
 | 
			
		||||
        Update::NewMessage(msg) | Update::MessageEdited(msg) => Some(msg),
 | 
			
		||||
        _ => None,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								src/utils/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/utils/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
pub mod inter_join;
 | 
			
		||||
pub mod messages;
 | 
			
		||||
		Reference in New Issue
	
	Block a user