This project embodies an attempt to create a Discord/IRC (and possibly more extensible) bridge. Not finished.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
6.2 KiB

use std::collections::hash_map::HashMap;
use std::sync::Arc;
use std::mem;
use std::thread;
use serenity::client::Client;
use serenity::prelude::*;
use serenity::model;
use serenity::client::bridge::gateway::{ShardManager, event::ShardStageUpdateEvent};
use serenity::gateway::ConnectionStage;
use serenity::cache::Cache;
use serde::Deserialize;
use crate::endpoint::{Endpoint, Bridge, BridgeResult, Message, ReceiveCallback};
use crate::refset::RefSet;
#[derive(Deserialize, Debug)]
pub struct DiscordConfig
{
instances: Vec<DiscordInstanceConfig>,
}
#[derive(Deserialize, Debug)]
pub struct DiscordInstanceConfig
{
token: String,
channels: HashMap<String, ChannelSer>,
}
struct DiscordEndpoint(Arc<RwLock<DiscordEndpointInner>>);
struct DiscordEndpointInner
{
bridges: RefSet<RwLock<Bridge>>,
instance: Mutex<Option<DiscordInstance>>,
manager: DiscordManager,
}
impl DiscordEndpoint
{
pub fn originate_message(&self, msg: Message)
{
for br in self.0.read().bridges.iter()
{
}
}
}
impl Endpoint<Arc<dyn ReceiveCallback>> for DiscordEndpoint
{
fn send(&self, msg: Message) -> BridgeResult
{
Ok(())
}
fn add_bridge(&mut self, br: Bridge)
{
}
fn remove_bridge(&mut self, br: Bridge)
{
}
fn add_receive_callback(&mut self, cb: Arc<dyn ReceiveCallback>)
{
}
fn remove_receive_callback(&mut self, cb: Arc<dyn ReceiveCallback>)
{
}
}
impl AsRef<RwLock<DiscordEndpointInner>> for DiscordEndpoint
{
fn as_ref(&self) -> &RwLock<DiscordEndpointInner>
{
self.0.as_ref()
}
}
#[derive(Debug)]
struct ChannelSer(model::id::ChannelId);
impl<'a> Deserialize<'a> for ChannelSer
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
D: serde::Deserializer<'a>
{
struct ChannelVisitor {};
impl<'a> serde::de::Visitor<'a> for ChannelVisitor
{
type Value = ChannelSer;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result
{
formatter.write_str("a Discord channel ID")
}
fn visit_str<E>(self, val: &str) -> Result<Self::Value, E> where
E: serde::de::Error
{
match str::parse::<model::id::ChannelId>(val)
{
Ok(id) => Ok(ChannelSer(id)),
Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(val), &self)),
}
}
}
let cv = ChannelVisitor {};
deserializer.deserialize_str(cv)
}
}
struct DiscordManager(Arc<Mutex<DiscordManagerInner>>);
struct DiscordManagerInner
{
pub instances: Vec<DiscordInstance>,
pub endpoints: RefSet<DiscordEndpoint>,
}
impl DiscordManager
{
pub fn new() -> DiscordManager
{
DiscordManager(Arc::new(Mutex::new(DiscordManagerInner
{
instances: Vec::new(),
endpoints: RefSet::new(),
})))
}
}
#[derive(Clone)]
pub struct DiscordInstance(Arc<Mutex<DiscordInstanceInner>>);
pub enum DiscordState
{
GoingUp(),
Up(),
GoingDown(),
Down(),
Failed(serenity::Error),
}
impl TypeMapKey for DiscordInstance { type Value = DiscordInstance; }
struct DiscordInstanceInner
{
client: Option<Arc<Mutex<Client>>>,
shard_manager: Option<Arc<Mutex<ShardManager>>>,
state: DiscordState,
runner: Option<thread::JoinHandle<()>>,
on_ready_callbacks: Arc<RwLock<RefSet<dyn Fn() + Sync + Send>>>,
}
impl DiscordInstance
{
pub fn new(token: impl AsRef<str>) -> Result<DiscordInstance, serenity::Error>
{
let instance =
DiscordInstance(Arc::new(Mutex::new(DiscordInstanceInner {
client: None,
shard_manager: None,
state: DiscordState::Down(),
runner: None,
on_ready_callbacks: Arc::new(RwLock::new(RefSet::new())),
})));
let mut client = Client::new(token, instance.clone())?;
let mut guard = instance.0.lock();
guard.shard_manager = Some(client.shard_manager.clone());
client.data.write().insert::<DiscordInstance>(instance.clone());
guard.client = Some(Arc::new(Mutex::new(client)));
mem::drop(guard);
Ok(instance)
}
pub fn start(&self)
{
let inner = self.0.clone();
let jh = thread::spawn(move || {
let client;
{
let mut topguard = inner.lock();
client = match &topguard.client
{
Some(c) => c.clone(),
None => return,
};
topguard.state = DiscordState::GoingUp();
}
match client.try_lock()
{
Some(mut lock) => match lock.start()
{
Ok(()) => inner.lock().state = DiscordState::Down(),
Err(e) => inner.lock().state = DiscordState::Failed(e),
},
None => (),
};
});
self.0.lock().runner = Some(jh);
}
pub fn stop_sync(&self)
{
match self.stop()
{
Some(jh) =>
{
jh.join();
let mut inner = None;
mem::swap(&mut inner, &mut self.0.lock().runner);
match inner
{
Some(jh) =>
{
jh.join();
},
None => (),
};
},
None => (),
};
}
pub fn stop(&self) -> Option<thread::JoinHandle<()>>
{
let mut guard = self.0.lock();
match guard.shard_manager.clone()
{
Some(sm) =>
{
guard.state = DiscordState::GoingDown();
let inner = self.0.clone();
Some(thread::spawn(move ||
{
sm.lock().shutdown_all();
inner.lock().state = DiscordState::Down();
}))
},
None => None,
}
}
}
impl EventHandler for DiscordInstance
{
fn ready(&self, _ctx: Context, _data: model::gateway::Ready)
{
let arc;
{
let mut guard = self.0.lock();
guard.state = DiscordState::Up();
arc = guard.on_ready_callbacks.clone();
}
println!("handler ready");
for cb in arc.read().iter()
{
cb();
}
}
fn shard_stage_update(&self, _ctx: Context, ev: ShardStageUpdateEvent)
{
match ev.new
{
ConnectionStage::Connected => println!("shard connected"),
_ => match ev.old
{
ConnectionStage::Connected => println!("shard disconnected"),
_ => (),
},
};
}
}