337 lines
12 KiB
Rust
337 lines
12 KiB
Rust
extern crate toml;
|
|
extern crate crossbeam;
|
|
extern crate discord;
|
|
extern crate rand;
|
|
extern crate pvn;
|
|
extern crate echobox;
|
|
extern crate transformable_channels;
|
|
extern crate stc;
|
|
extern crate regex;
|
|
|
|
#[macro_use]
|
|
extern crate hlua;
|
|
|
|
use std::collections::BTreeMap;
|
|
use toml::Table;
|
|
|
|
mod modules;
|
|
use modules::Module;
|
|
use modules::loader::{ModuleLoader, ModuleLoaderError};
|
|
|
|
mod event;
|
|
use event::Event;
|
|
use event::Envelope;
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::mpsc;
|
|
use std::sync::mpsc::Sender;
|
|
|
|
use transformable_channels::mpsc::TransformableSender;
|
|
|
|
mod helpers;
|
|
|
|
#[macro_use]
|
|
extern crate log;
|
|
|
|
pub struct Tenquestionmarks {
|
|
subscriptions: BTreeMap<String, Subscription>
|
|
}
|
|
|
|
impl Tenquestionmarks {
|
|
pub fn with_modules (modules: BTreeMap<String, Module>) -> Tenquestionmarks {
|
|
let tqm = Tenquestionmarks {
|
|
subscriptions: modules.into_iter().map(|(key, module)| {
|
|
(key.clone(), Subscription::new(key.clone(), module))
|
|
}).collect()
|
|
};
|
|
|
|
tqm
|
|
}
|
|
|
|
pub fn from_configuration (configuration: Table) -> Result<Tenquestionmarks, ModuleLoaderError> {
|
|
let loader = ModuleLoader::new();
|
|
let modules = loader.load_from_configuration(configuration)?;
|
|
Result::Ok(Tenquestionmarks::with_modules(modules))
|
|
}
|
|
|
|
pub fn run (&self) {
|
|
crossbeam::scope(|scope| {
|
|
// Our main event channel.
|
|
// Modules push events to tenquestionmarks using this channel.
|
|
let (ref main_sender, ref main_receiver) = transformable_channels::mpsc::channel();
|
|
|
|
// Module threads.
|
|
// Each Module will produce events which tenquestionmarks will push
|
|
// into all other Modules.
|
|
// tenquestionmarks propagates all events to each Module through these
|
|
// channels.
|
|
let module_senders: BTreeMap<&str, Sender<Arc<Envelope>>> = self.subscriptions.iter().map(|(key, subscription)| {
|
|
let from = key.clone();
|
|
let main_sender_mapped = main_sender.map(move |envelope: Envelope| {
|
|
Envelope {
|
|
from: Some(from.clone()),
|
|
event: envelope.event,
|
|
to: envelope.to
|
|
}
|
|
});
|
|
|
|
let (module_sender, module_receiver) = mpsc::channel();
|
|
info!("Spawning thread for \"{}\"", key);
|
|
scope.spawn(move || {
|
|
subscription.module.run(Box::new(main_sender_mapped), module_receiver);
|
|
info!("Thread for \"{}\" is exiting", key);
|
|
});
|
|
(&key[..], module_sender)
|
|
}).collect();
|
|
|
|
// tenquestionmarks main event loop.
|
|
// tenquestionmarks receives events produced by Modules and pushes them
|
|
// into all other Modules
|
|
loop {
|
|
match main_receiver.recv() {
|
|
Ok(envelope) => {
|
|
/*
|
|
* Check if the target module is a valid destination for the envelope.
|
|
* We want to deliver this envelope if all of the following are true:
|
|
* 1. The target module is not the originator of the envelope
|
|
* 2. The envelope's list of allowed recipients is empty, or specifically
|
|
* names the target module.
|
|
*/
|
|
let arc_envelope = Arc::new(envelope);
|
|
for (key, sender) in &module_senders {
|
|
let from_this = String::from(*key);
|
|
if let Some(subscription) = self.subscriptions.get::<String>(&from_this) {
|
|
if subscription.can_handle_event(&arc_envelope) {
|
|
if let Err(err) = sender.send(arc_envelope.clone()) {
|
|
debug!("Failed to dispatch event to module \"{}\": {:?}", key, err);
|
|
}
|
|
}
|
|
} else {
|
|
debug!("Failed to dispatch event to module \"{}\": No such module found!", key);
|
|
}
|
|
}
|
|
},
|
|
Err(err) => { error!("Failed to receive event in main event loop: {:?}", err); }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Message {
|
|
content: String,
|
|
author: User,
|
|
channel: Option<Channel>
|
|
}
|
|
|
|
impl Message {
|
|
fn reply (&self, message: &str) {
|
|
match self.channel {
|
|
Some(ref channel) => channel.send(message),
|
|
None => self.author.send(message)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Channel {
|
|
name: String,
|
|
description: String,
|
|
topic: String,
|
|
sender: Box<MessageSender>
|
|
}
|
|
|
|
impl Channel {
|
|
pub fn send (&self, message: &str) {
|
|
self.sender.send_message(message);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct User {
|
|
name: String,
|
|
sender: Box<MessageSender>
|
|
}
|
|
|
|
impl User {
|
|
pub fn send (&self, message: &str) {
|
|
self.sender.send_message(message);
|
|
}
|
|
}
|
|
|
|
pub trait MessageSender : Sync + Send + std::fmt::Debug {
|
|
fn send_message (&self, _: &str) {}
|
|
}
|
|
|
|
trait EventFilter: Sync + Send {
|
|
fn accept (&self, envelope: &Envelope) -> bool;
|
|
}
|
|
|
|
struct Subscription {
|
|
pub module: Module,
|
|
pub name: String,
|
|
pub filters: Vec<Box<EventFilter>>
|
|
}
|
|
|
|
impl Subscription {
|
|
pub fn new (name: String, module: Module) -> Subscription {
|
|
let filters: Vec<Box<EventFilter>> = module.config.get("filters")
|
|
.and_then(|value| value.as_slice())
|
|
.map(|value| value.to_vec())
|
|
.unwrap_or(vec![])
|
|
.into_iter()
|
|
.map(|value| {
|
|
match value.as_table() {
|
|
Some(table) => Some(Box::new(AttributeEventFilter::new(table)) as Box<EventFilter>),
|
|
None => None
|
|
}
|
|
})
|
|
.filter(|possible_filter| possible_filter.is_some())
|
|
.map(|possible_filter| possible_filter.unwrap())
|
|
.collect();
|
|
|
|
Subscription {
|
|
module: module,
|
|
name: name,
|
|
filters: filters
|
|
}
|
|
}
|
|
|
|
pub fn can_handle_event (&self, envelope: &Envelope) -> bool {
|
|
if Some(&self.name) == envelope.from.as_ref() {
|
|
debug!("Refusing to transmit event to its originator ({:?})", self.name);
|
|
return false;
|
|
}
|
|
else if !(envelope.to.is_empty() || !envelope.to.contains(&self.name)) {
|
|
debug!(
|
|
"Refusing to transmit envelope from {:?} to {:?} since it is not on the list of allowed recipients ({:?})",
|
|
envelope.from,
|
|
self.name,
|
|
envelope.to
|
|
);
|
|
return false;
|
|
}
|
|
else if !self.filters.is_empty() {
|
|
for filter in &self.filters {
|
|
if filter.accept(envelope) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
debug!(
|
|
"Refusing to transmit envelope from {:?} to {:?} since envelope was filtered out",
|
|
envelope.from,
|
|
self.name
|
|
);
|
|
return false;
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|
|
|
|
struct AttributeEventFilter {
|
|
// Attributes that can be filtered out
|
|
event_type: Option<String>,
|
|
username: Option<String>,
|
|
channel: Option<String>,
|
|
message: Option<String>
|
|
}
|
|
|
|
impl AttributeEventFilter {
|
|
pub fn new (attributes: &Table) -> AttributeEventFilter {
|
|
AttributeEventFilter {
|
|
event_type: attributes.get("type").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
|
message: attributes.get("message").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
|
username: attributes.get("username").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
|
channel: attributes.get("channel").and_then(|value| value.as_str()).map(|value| String::from(value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EventFilter for AttributeEventFilter {
|
|
fn accept (&self, envelope: &Envelope) -> bool {
|
|
let mut result = true;
|
|
match &envelope.event {
|
|
&Event::Message { ref message } => {
|
|
match self.event_type {
|
|
Some(ref event_type) => result = result && event_type == "message",
|
|
None => {}
|
|
}
|
|
|
|
match self.channel {
|
|
Some(ref channel_name) => {
|
|
match message.channel {
|
|
Some(ref channel) => result = result && channel_name == &channel.name,
|
|
None => result = false
|
|
}
|
|
},
|
|
None => {}
|
|
}
|
|
|
|
match self.username {
|
|
Some(ref username) => result = result && &message.author.name == username,
|
|
None => {}
|
|
}
|
|
},
|
|
&Event::SelfJoin { ref channel } => {
|
|
match self.event_type {
|
|
Some(ref event_type) => result = result && event_type == "selfjoin",
|
|
None => {}
|
|
}
|
|
|
|
match self.channel {
|
|
Some(ref channel_name) => result = result && channel_name == &channel.name,
|
|
None => {}
|
|
}
|
|
},
|
|
&Event::SelfQuit { ref channel } => {
|
|
match self.event_type {
|
|
Some(ref event_type) => result = result && event_type == "selfquit",
|
|
None => {}
|
|
}
|
|
|
|
match self.channel {
|
|
Some(ref channel_name) => result = result && channel_name == &channel.name,
|
|
None => {}
|
|
}
|
|
},
|
|
&Event::UserJoin { ref channel, ref user } => {
|
|
match self.event_type {
|
|
Some(ref event_type) => result = result && event_type == "userjoin",
|
|
None => {}
|
|
}
|
|
|
|
match self.channel {
|
|
Some(ref channel_name) => result = result && channel_name == &channel.name,
|
|
None => {}
|
|
}
|
|
|
|
match self.username {
|
|
Some(ref username) => result = result && &user.name == username,
|
|
None => {}
|
|
}
|
|
},
|
|
&Event::UserQuit { ref channel, ref user } => {
|
|
match self.event_type {
|
|
Some(ref event_type) => result = result && event_type == "userquit",
|
|
None => {}
|
|
}
|
|
|
|
match self.channel {
|
|
Some(ref channel_name) => result = result && channel_name == &channel.name,
|
|
None => {}
|
|
}
|
|
|
|
match self.username {
|
|
Some(ref username) => result = result && &user.name == username,
|
|
None => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
}
|