use futures_util::stream;
use std::error::Error;
use tonic::{
codegen::InterceptedService,
metadata::{Ascii, MetadataValue},
service::Interceptor,
transport::Channel,
Request, Status,
};
mod plugin {
tonic::include_proto!("plugin");
}
use plugin::{
listener_client_data::Data, plugin_client::PluginClient, CmdDef, CmdInvocation, Event,
ListenerClientData, Message, MiddlewareResponse,
};
pub use plugin::Listener;
pub type PluginResult = Result<(), Box<dyn Error>>;
#[derive(Clone)]
pub struct Client {
client: PluginClient<InterceptedService<Channel, AuthInterceptor>>,
}
#[derive(Clone)]
struct AuthInterceptor {
token: MetadataValue<Ascii>,
}
impl AuthInterceptor {
pub fn new(token: String) -> Self {
let token = format!("Bearer {}", token).parse().unwrap();
Self { token }
}
}
impl Interceptor for AuthInterceptor {
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, Status> {
request
.metadata_mut()
.insert("authorization", self.token.clone());
Ok(request)
}
}
impl Client {
pub async fn new<S: Into<String>>(host: S, token: S) -> Result<Self, Box<dyn Error>> {
let channel = Channel::from_shared(host.into())?.connect().await?;
let auth = AuthInterceptor::new(token.into());
let client = PluginClient::with_interceptor(channel, auth);
Ok(Self { client })
}
pub async fn send_message(
&self,
room: String,
from: Option<String>,
msg: String,
ephemeral_to: Option<String>,
) -> PluginResult {
let msg = Message {
room,
from,
msg,
ephemeral_to,
};
let mut mut_self = self.clone();
mut_self.client.send_message(Request::new(msg)).await?;
Ok(())
}
pub async fn register_listener<F, Fut>(&self, listener: Listener, callback: F) -> PluginResult
where
F: FnOnce(Event) -> Fut + Copy,
Fut: std::future::Future<Output = Option<String>>,
{
let mut mut_self = self.clone();
let listener_data = stream::iter(vec![ListenerClientData {
data: Some(Data::Listener(listener.clone())),
}]);
let mut event = mut_self
.client
.register_listener(listener_data)
.await?
.into_inner();
while let Some(event) = event.message().await? {
let result = callback(event).await;
if !listener.middleware() && result.is_some() {
panic!("Function returned a value although it's not marked as a middleware.");
}
Data::Response(MiddlewareResponse { msg: result });
}
Ok(())
}
pub async fn register_cmd<S, F, Fut>(
&self,
name: S,
info: S,
args_info: S,
callback: F,
) -> PluginResult
where
S: Into<String>,
F: FnOnce(CmdInvocation) -> Fut + Copy,
Fut: std::future::Future<Output = String>,
{
let mut mut_self = self.clone();
let cmd = CmdDef {
name: name.into(),
info: info.into(),
args_info: args_info.into(),
};
let mut event = mut_self.client.register_cmd(cmd).await?.into_inner();
while let Some(event) = event.message().await? {
let room = event.room.clone();
let result = callback(event).await;
mut_self.send_message(room, None, result, None).await?;
}
Ok(())
}
}