teloxide_plugins_macros/
lib.rs

1#![allow(non_snake_case)]
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, ItemFn};
6
7#[proc_macro_attribute]
8pub fn TeloxidePlugin(args: TokenStream, input: TokenStream) -> TokenStream {
9    let input_fn = parse_macro_input!(input as ItemFn);
10    
11    let args_str = args.to_string();
12
13    let fn_name = &input_fn.sig.ident;
14    let fn_name_str = fn_name.to_string();
15    let ctor_fn_name = syn::Ident::new(&format!("{}_ctor", fn_name_str), fn_name.span());
16    let static_name = syn::Ident::new(&format!("{}_meta", fn_name_str), fn_name.span());
17    let vis = &input_fn.vis;
18    let sig = &input_fn.sig;
19    let block = &input_fn.block;
20
21    let mut commands = Vec::new();
22    let mut prefixes = Vec::new();
23    let mut regex = None;
24    let mut callback_filter = None;
25
26    if args_str.contains("commands = [") {
27        if let Some(start) = args_str.find("commands = [") {
28            if let Some(end) = args_str[start..].find("]") {
29                let commands_str = &args_str[start + 12..start + end];
30                commands = commands_str
31                    .split(',')
32                    .map(|s| s.trim().trim_matches('"'))
33                    .filter(|s| !s.is_empty())
34                    .collect();
35            }
36        }
37    }
38    
39    if args_str.contains("prefixes = [") {
40        if let Some(start) = args_str.find("prefixes = [") {
41            if let Some(end) = args_str[start..].find("]") {
42                let prefixes_str = &args_str[start + 12..start + end];
43                prefixes = prefixes_str
44                    .split(',')
45                    .map(|s| s.trim().trim_matches('"'))
46                    .filter(|s| !s.is_empty())
47                    .collect();
48            }
49        }
50    }
51    
52    if args_str.contains("regex = [") {
53        if let Some(start) = args_str.find("regex = [") {
54            if let Some(end) = args_str[start..].find("]") {
55                let regex_str = &args_str[start + 9..start + end];
56                let regex_patterns: Vec<&str> = regex_str
57                    .split(',')
58                    .map(|s| s.trim().trim_matches('"'))
59                    .filter(|s| !s.is_empty())
60                    .collect();
61                regex = regex_patterns.first().copied();
62            }
63        }
64    }
65    
66    if args_str.contains("callback = [") {
67        if let Some(start) = args_str.find("callback = [") {
68            if let Some(end) = args_str[start..].find("]") {
69                let callback_str = &args_str[start + 12..start + end];
70                let callback_patterns: Vec<&str> = callback_str
71                    .split(',')
72                    .map(|s| s.trim().trim_matches('"'))
73                    .filter(|s| !s.is_empty())
74                    .collect();
75                callback_filter = callback_patterns.first().copied();
76            }
77        }
78    }
79
80    let commands_lit: Vec<proc_macro2::TokenStream> =
81        commands.iter().map(|c| quote! { #c }).collect();
82    let prefixes_lit: Vec<proc_macro2::TokenStream> =
83        prefixes.iter().map(|p| quote! { #p }).collect();
84    let regex_lit = match regex {
85        Some(r) => quote! { Some(#r) },
86        None => quote! { None },
87    };
88    let callback_filter_lit = match callback_filter {
89        Some(c) => quote! { Some(#c) },
90        None => quote! { None },
91    };
92    
93    let callback_handler = if callback_filter.is_some() {
94        quote! {
95            |ctx| Box::pin(async move {
96                if let Some(cq) = ctx.callback_query.clone() {
97                    #fn_name(ctx.bot.clone(), cq).await;
98                }
99            })
100        }
101    } else {
102        quote! {
103            |ctx| Box::pin(async move {
104                if let Some(msg) = ctx.message.clone() {
105                    #fn_name(ctx.bot.clone(), msg).await;
106                }
107            })
108        }
109    };
110
111    let expanded = quote! {
112        #vis #sig #block
113
114        #[allow(non_upper_case_globals)]
115        #[doc(hidden)]
116        static #static_name: &teloxide_plugins::registry::PluginMeta = &teloxide_plugins::registry::PluginMeta {
117            name: #fn_name_str,
118            commands: &[#(#commands_lit),*],
119            prefixes: &[#(#prefixes_lit),*],
120            regex: #regex_lit,
121            callback_filter: #callback_filter_lit,
122            callback: #callback_handler,
123        };
124
125        #[ctor::ctor]
126        fn #ctor_fn_name() {
127            if let Ok(handle) = tokio::runtime::Handle::try_current() {
128                handle.spawn(async move {
129                    let mut reg = teloxide_plugins::registry::PluginRegistry.lock().await;
130                    reg.push(#static_name);
131                });
132            } else {
133                tokio::runtime::Runtime::new()
134                    .unwrap()
135                    .block_on(async {
136                        let mut reg = teloxide_plugins::registry::PluginRegistry.lock().await;
137                        reg.push(#static_name);
138                    });
139            }
140        }
141    };
142
143    TokenStream::from(expanded)
144}