teloxide_plugins/
registry.rs1#![allow(non_upper_case_globals)]
2
3use crate::context::PluginContext;
4use once_cell::sync::Lazy;
5use regex::Regex;
6use std::collections::HashMap;
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::{Mutex, RwLock as StdRwLock};
10use tokio::sync::RwLock as AsyncRwLock;
11
12pub struct PluginMeta {
13 pub name: &'static str,
14 pub commands: &'static [&'static str],
15 pub prefixes: &'static [&'static str],
16 pub regex: Option<&'static str>,
17 pub callback_filter: Option<&'static str>,
18 pub callback: fn(PluginContext) -> Pin<Box<dyn Future<Output = ()> + Send>>,
19}
20
21pub static PLUGIN_REGISTRY: Lazy<Mutex<Vec<&'static PluginMeta>>> =
22 Lazy::new(|| Mutex::new(Vec::new()));
23
24static REGEX_CACHE: Lazy<AsyncRwLock<HashMap<&'static str, Regex>>> =
25 Lazy::new(|| AsyncRwLock::new(HashMap::new()));
26
27static COMMAND_MAP: Lazy<StdRwLock<HashMap<String, &'static PluginMeta>>> =
28 Lazy::new(|| StdRwLock::new(HashMap::new()));
29
30fn find_command_plugin(text: &str) -> Option<&'static PluginMeta> {
31 let map = COMMAND_MAP.read().unwrap();
32 map.get(text).copied()
33}
34
35pub async fn dispatch(ctx: PluginContext) -> Result<(), teloxide::RequestError> {
36 let text = ctx.message.as_ref().and_then(|m| m.text());
37 let cb_data = ctx.callback_query.as_ref().and_then(|c| c.data.as_deref());
38
39 if let Some(text) = text {
40 if let Some(plugin) = find_command_plugin(text) {
41 (plugin.callback)(ctx.clone()).await;
42 return Ok(());
43 }
44 }
45
46 let plugins = {
47 let registry = PLUGIN_REGISTRY.lock().unwrap();
48 registry.clone()
49 };
50
51 for plugin in plugins {
52 if let Some(text) = text {
53 if let Some(re) = plugin.regex {
54 let regex = get_or_compile_regex(re).await;
55 if regex.is_match(text) {
56 (plugin.callback)(ctx.clone()).await;
57 return Ok(());
58 }
59 }
60 }
61
62 if let Some(cb) = cb_data {
63 if let Some(filter) = plugin.callback_filter {
64 if cb == filter {
65 (plugin.callback)(ctx.clone()).await;
66 return Ok(());
67 }
68 }
69 }
70 }
71
72 Ok(())
73}
74
75pub fn register_plugin(plugin: &'static PluginMeta) {
76 let mut registry = PLUGIN_REGISTRY.lock().unwrap();
77 registry.push(plugin);
78
79 if !plugin.prefixes.is_empty() && !plugin.commands.is_empty() {
80 let mut map = COMMAND_MAP.write().unwrap();
81 for prefix in plugin.prefixes {
82 for cmd in plugin.commands {
83 let mut key = String::with_capacity(prefix.len() + cmd.len());
84 key.push_str(prefix);
85 key.push_str(cmd);
86 map.entry(key).or_insert(plugin);
87 }
88 }
89 }
90}
91
92async fn get_or_compile_regex(pattern: &'static str) -> Regex {
93 {
94 let cache = REGEX_CACHE.read().await;
95 if let Some(r) = cache.get(pattern) {
96 return r.clone();
97 }
98 }
99
100 let regex = Regex::new(pattern).unwrap_or_else(|_| Regex::new("(?!)").unwrap());
101
102 let mut cache = REGEX_CACHE.write().await;
103 cache.insert(pattern, regex.clone());
104
105 regex
106}