1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;

use crate::config::Config;
use crate::request::Request;
use crate::response::{Outcome, Response};

use failure::Error;

pub(crate) struct CommandRouter {
    commands: HashMap<&'static str, &'static dyn CommandHandler>,
    default: Option<&'static dyn CommandHandler>,
}

impl CommandRouter {
    pub fn new() -> Self {
        CommandRouter {
            commands: HashMap::new(),
            default: None,
        }
    }

    pub fn add_handlers(
        &mut self,
        handlers: Vec<(Option<&'static str>, &'static dyn CommandHandler)>,
    ) {
        for (label, handler) in handlers {
            if let Some(label) = label {
                self.commands.insert(label, handler);
            } else if self.default.is_none() {
                self.default = Some(handler);
            }
        }
    }

    pub async fn route<'r>(&'r self, request: &'r Request<'r>) -> Outcome {
        if request
            .config
            .bot_settings
            .blacklisted_users
            .contains(&request.command.source_nick.into())
        {
            return Outcome::Success(Response::None);
        }

        let c: &str = request.command.command_str.as_ref();
        if let Some(handler) = self.commands.get(c) {
            match handler.handle(&request) {
                Ok(fut) => await!(fut),
                Err(err) => return Outcome::Failure(err),
            }
        } else if let Some(handler) = &self.default {
            match handler.handle(&request) {
                Ok(fut) => await!(fut),
                Err(err) => return Outcome::Failure(err),
            }
        } else {
            Outcome::Success(Response::None)
        }
    }
}

pub trait CommandHandler {
    fn route_id(&self) -> Option<&'static str>;

    fn handle<'a, 'r>(
        &'a self,
        request: &'a Request<'r>,
    ) -> Result<Pin<Box<Future<Output = Outcome> + 'a>>, Error>;
}

pub struct Command<'a> {
    pub source_nick: &'a str,
    pub command_str: String,
    pub arguments: Vec<String>,
}

impl<'a> Command<'a> {
    pub fn try_parse<'u>(
        our_nick: &'u str,
        source_nick: &'a str,
        message: &'a str,
        config: &Config,
    ) -> Option<Command<'a>> {
        let command_str = config
            .bot_settings
            .command_indicator
            .iter()
            .chain(std::iter::once(&format!("{}:", our_nick)))
            .filter_map(|indicator| {
                if !message.starts_with(indicator) {
                    message.find(&format!("{{{}", indicator)).and_then(|start| {
                        let start = start + indicator.len() + 1;
                        let end = message.split_at(start).1.find('}')?;

                        Some(&message[start..(start + end)])
                    })
                } else {
                    Some(message.split_at(indicator.len()).1)
                }
            })
            .nth(0)?;

        let mut parts = command_str.trim().split(' ').map(String::from);
        let command = parts.next()?;
        let args = parts.collect();
        Some(Command {
            source_nick,
            command_str: command,
            arguments: args,
        })
    }

    pub fn from_command_str(source_nick: &'a str, command_str: &str) -> Option<Command<'a>> {
        let mut parts = command_str.trim().split(' ').map(String::from);
        let command = parts.next()?;
        let args = parts.collect();
        Some(Command {
            source_nick,
            command_str: command,
            arguments: args,
        })
    }
}