use crate::Bot;
#[cfg(feature = "plugin-access-control")]
use crate::bot::AccessControlMode;
use crate::bot::BotInformation;
#[cfg(feature = "plugin-access-control")]
use crate::bot::runtimebot::kovi_api::AccessList;
use crate::event::{Event, InternalEvent, MessageEventTrait};
use crate::plugin::PLUGIN_NAME;
use crate::plugin::plugin_builder::ListenInner;
use crate::types::ApiAndOptOneshot;
use parking_lot::RwLock;
use std::sync::Arc;
use tokio::sync::{mpsc, watch};
#[derive(Clone)]
pub(crate) enum InternalInternalEvent {
Exit(ExitEvent),
OneBotEvent(Box<InternalEvent>),
}
#[derive(Clone)]
pub(crate) enum ExitEvent {
FromDrive,
FromSignal,
}
impl Bot {
pub(crate) async fn handler_event(
bot: Arc<RwLock<Self>>,
event: InternalInternalEvent,
api_tx: mpsc::Sender<ApiAndOptOneshot>,
) {
match event {
InternalInternalEvent::Exit(_) => Self::handle_kovi_exit(bot).await,
InternalInternalEvent::OneBotEvent(msg) => {
Self::handler_internal_event(bot, *msg, api_tx).await
}
}
}
pub(crate) async fn handle_kovi_exit(bot: Arc<RwLock<Self>>) {
let drop_task = {
let mut bot_write = bot.write();
#[cfg(any(feature = "save_plugin_status", feature = "save_bot_admin"))]
bot_write.save_bot_status();
let mut task_vec = Vec::new();
for plugin in bot_write.plugins.values_mut() {
task_vec.push(plugin.shutdown());
}
Some(task_vec)
};
if let Some(drop_task) = drop_task {
for task in drop_task {
let _ = task.await;
}
}
}
async fn handler_internal_event(
bot: Arc<RwLock<Self>>,
msg: InternalEvent,
api_tx: mpsc::Sender<ApiAndOptOneshot>,
) {
let bot_read = bot.read();
let drive = bot_read.drive.clone();
let info = &bot_read.information;
let plugin_iter = bot_read.plugins.iter();
let plugin_cache = plugin_iter
.clone()
.map(|(name, plugin)| {
let name = Arc::new(name.to_owned());
(
name.clone(),
PluginCache {
name,
#[cfg(feature = "plugin-access-control")]
acc: AccCache::new(
plugin.access_control,
plugin.list_mode,
plugin.access_list.clone(),
),
bot_info: info.clone(),
enabled: plugin.enabled.subscribe(),
},
)
})
.collect::<ahash::HashMap<Arc<String>, PluginCache>>();
let type_plugin_map = {
let mut type_plugin_map: PluginMap = Default::default();
for (name, plugin) in plugin_iter {
for listen in &plugin.listen.list {
let plugin_map = type_plugin_map.entry(listen.type_id).or_default();
let plugin_vec = plugin_map
.plugins
.entry(plugin_cache[name].name.clone())
.or_default();
plugin_vec.push(listen.clone());
}
}
type_plugin_map
};
let msg_event =
(drive.message_event_register().type_de)(&msg, &info.read(), &api_tx).map(|e| {
log_msg_event(&*e);
e
});
drop(bot_read);
struct SharedData {
msg: InternalEvent,
api_tx: mpsc::Sender<ApiAndOptOneshot>,
plugin_cache: ahash::HashMap<Arc<String>, PluginCache>,
}
let shared_data = Arc::new(SharedData {
msg,
api_tx,
plugin_cache,
});
for plugin_map in type_plugin_map.into_values() {
tokio::spawn(type_handler(
plugin_map,
msg_event.clone(),
shared_data.clone(),
));
}
async fn type_handler(
plugin_map: EventHandler,
msg_event: Option<Arc<dyn MessageEventTrait>>,
shared_data: Arc<SharedData>,
) {
let mut event_cache: Option<Arc<dyn Event>> = None;
for (name, plugin_vec) in plugin_map.plugins.into_iter() {
let plugin_cache = &shared_data.plugin_cache[&name];
#[cfg(feature = "plugin-access-control")]
if let Some(event) = &msg_event {
if !is_access(&plugin_cache.acc, &**event) {
continue;
}
}
for listen in plugin_vec {
let event = match &event_cache {
Some(v) => v.clone(),
None => {
let event_opt = (listen.type_de)(
&shared_data.msg,
&plugin_cache.bot_info.read(),
&shared_data.api_tx,
);
match event_opt {
Some(event) => {
event_cache = Some(event.clone());
event
}
None => return,
}
}
};
let name = name.clone();
let enabled = plugin_cache.enabled.clone();
tokio::spawn(async move {
tokio::select! {
_ = PLUGIN_NAME.scope(name, handle_listen(listen, event)) => {}
_ = monitor_enabled_state(enabled) => {}
}
});
}
}
}
async fn monitor_enabled_state(mut enabled: watch::Receiver<bool>) {
loop {
enabled
.changed()
.await
.expect("The enabled signal was dropped");
if !*enabled.borrow_and_update() {
break;
}
}
}
fn log_msg_event<T: MessageEventTrait + ?Sized>(event: &T) {
let message_type = event.get_message_type_str().unwrap_or_default();
let group_id = match event.get_group_id() {
Some(id) => id.to_string(),
None => "".to_string(),
};
let nickname = event.get_sender_name().unwrap_or_default();
let id = event.get_sender_id();
let msg = event.get_message();
log::info!(
"[{message_type}{group_id}{nickname} {id}]: {text}",
text = msg.to_human_string()
);
}
async fn handle_listen(listen: Arc<ListenInner>, cache_event: Arc<dyn Event + 'static>) {
(*listen.handler)(cache_event).await;
}
}
}
struct PluginCache {
name: Arc<String>,
#[cfg(feature = "plugin-access-control")]
acc: AccCache,
bot_info: Arc<RwLock<BotInformation>>,
enabled: watch::Receiver<bool>,
}
#[cfg(feature = "plugin-access-control")]
struct AccCache {
pub(crate) access_control: bool,
pub(crate) list_mode: AccessControlMode,
pub(crate) access_list: AccessList,
}
#[cfg(feature = "plugin-access-control")]
impl AccCache {
pub fn new(
access_control: bool,
list_mode: AccessControlMode,
access_list: AccessList,
) -> Self {
Self {
access_control,
list_mode,
access_list,
}
}
}
#[cfg(feature = "plugin-access-control")]
fn is_access<T: MessageEventTrait + ?Sized>(plugin: &AccCache, event: &T) -> bool {
if !plugin.access_control {
return true;
}
let access_list = &plugin.access_list;
let in_group = event.is_group_message();
match (plugin.list_mode, in_group) {
(AccessControlMode::WhiteList, true) => {
let id = event.get_group_id().expect("unreachable");
access_list.groups.iter().any(|v| *v == id)
}
(AccessControlMode::WhiteList, false) => {
let id = event.get_sender_id();
access_list.friends.iter().any(|v| *v == id)
}
(AccessControlMode::BlackList, true) => {
let id = event.get_group_id().expect("unreachable");
!access_list.groups.iter().any(|v| *v == id)
}
(AccessControlMode::BlackList, false) => {
let id = event.get_sender_id();
!access_list.friends.iter().any(|v| *v == id)
}
}
}
#[allow(dead_code)]
#[derive(Default)]
struct EventHandler {
plugins: ahash::HashMap<Arc<String>, Vec<Arc<ListenInner>>>,
}
#[allow(warnings)]
type PluginMap<'a> = std::collections::HashMap<
std::any::TypeId,
EventHandler,
std::hash::BuildHasherDefault<IdHasher>,
>;
#[allow(warnings)]
type TypeEventCacheMap = std::collections::HashMap<
std::any::TypeId,
Option<Arc<dyn Event>>,
std::hash::BuildHasherDefault<IdHasher>,
>;
#[derive(Default, Debug)]
struct IdHasher(u64);
impl std::hash::Hasher for IdHasher {
fn write(&mut self, _: &[u8]) {
unreachable!("TypeId calls write_u64");
}
#[inline]
fn write_u64(&mut self, id: u64) {
self.0 = id;
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
}