1pub mod ffi;
4pub mod host;
5pub mod model;
6
7pub use host::Host;
8pub use model::{
9 Danmaku, DisconnectEvent, GiftRank, InteractType, MsgType, PluginContext, PluginMetadata,
10};
11
12pub trait DanmujiPlugin: Send + 'static {
13 fn metadata(&self) -> PluginMetadata;
14
15 fn inited(&mut self, _host: Host, _ctx: PluginContext) {}
16
17 fn start(&mut self, _host: Host, _ctx: PluginContext) {}
18
19 fn stop(&mut self, _host: Host, _ctx: PluginContext) {}
20
21 fn admin(&mut self, _host: Host, _ctx: PluginContext) {}
22
23 fn deinit(&mut self, _host: Host, _ctx: PluginContext) {}
24
25 fn connected(&mut self, _host: Host, _room_id: i32) {}
26
27 fn disconnected(&mut self, _host: Host, _event: DisconnectEvent) {}
28
29 fn room_count(&mut self, _host: Host, _user_count: u32) {}
30
31 fn danmaku(&mut self, _host: Host, _danmaku: Danmaku) {}
32}
33
34#[macro_export]
35macro_rules! export_plugin {
36 ($plugin:expr) => {
37 static DANMUJI_RS_PLUGIN: ::std::sync::OnceLock<
38 ::std::sync::Mutex<Box<dyn $crate::DanmujiPlugin>>,
39 > = ::std::sync::OnceLock::new();
40
41 fn danmuji_rs_plugin_instance(
42 ) -> &'static ::std::sync::Mutex<Box<dyn $crate::DanmujiPlugin>> {
43 DANMUJI_RS_PLUGIN.get_or_init(|| ::std::sync::Mutex::new(Box::new($plugin)))
44 }
45
46 fn danmuji_rs_with_plugin<F>(f: F)
47 where
48 F: FnOnce(&mut dyn $crate::DanmujiPlugin),
49 {
50 let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
51 let instance = danmuji_rs_plugin_instance();
52 match instance.lock() {
53 Ok(mut plugin) => f(plugin.as_mut()),
54 Err(poisoned) => {
55 let mut plugin = poisoned.into_inner();
56 f(plugin.as_mut());
57 }
58 }
59 }));
60
61 if result.is_err() {
62 $crate::Host::new().log("Rust plugin panic was caught by danmuji-sdk");
63 }
64 }
65
66 #[doc = "# Safety\n\n`out` must be non-null and point to writable memory for one `FfiPluginMetadata`."]
67 #[no_mangle]
68 pub unsafe extern "C" fn danmuji_rs_plugin_metadata(
69 out: *mut $crate::ffi::FfiPluginMetadata,
70 ) {
71 if out.is_null() {
72 return;
73 }
74
75 let metadata = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
76 let instance = danmuji_rs_plugin_instance();
77 match instance.lock() {
78 Ok(plugin) => plugin.metadata(),
79 Err(poisoned) => poisoned.into_inner().metadata(),
80 }
81 }))
82 .unwrap_or($crate::PluginMetadata {
83 name: "Rust Plugin",
84 author: "",
85 contact: "",
86 version: "",
87 description: "Rust plugin metadata failed",
88 });
89
90 unsafe {
91 *out = metadata.into_ffi();
92 }
93 }
94
95 #[no_mangle]
96 pub extern "C" fn danmuji_rs_plugin_set_host(api: $crate::ffi::FfiHostApi) {
97 $crate::ffi::set_host_api(api);
98 }
99
100 #[no_mangle]
101 pub extern "C" fn danmuji_rs_plugin_inited(ctx: $crate::ffi::FfiPluginContext) {
102 danmuji_rs_with_plugin(|plugin| plugin.inited($crate::Host::new(), ctx.into()));
103 }
104
105 #[no_mangle]
106 pub extern "C" fn danmuji_rs_plugin_start(ctx: $crate::ffi::FfiPluginContext) {
107 danmuji_rs_with_plugin(|plugin| plugin.start($crate::Host::new(), ctx.into()));
108 }
109
110 #[no_mangle]
111 pub extern "C" fn danmuji_rs_plugin_stop(ctx: $crate::ffi::FfiPluginContext) {
112 danmuji_rs_with_plugin(|plugin| plugin.stop($crate::Host::new(), ctx.into()));
113 }
114
115 #[no_mangle]
116 pub extern "C" fn danmuji_rs_plugin_admin(ctx: $crate::ffi::FfiPluginContext) {
117 danmuji_rs_with_plugin(|plugin| plugin.admin($crate::Host::new(), ctx.into()));
118 }
119
120 #[no_mangle]
121 pub extern "C" fn danmuji_rs_plugin_deinit(ctx: $crate::ffi::FfiPluginContext) {
122 danmuji_rs_with_plugin(|plugin| plugin.deinit($crate::Host::new(), ctx.into()));
123 }
124
125 #[no_mangle]
126 pub extern "C" fn danmuji_rs_plugin_on_connected(room_id: i32) {
127 danmuji_rs_with_plugin(|plugin| plugin.connected($crate::Host::new(), room_id));
128 }
129
130 #[no_mangle]
131 pub extern "C" fn danmuji_rs_plugin_on_disconnected(error: $crate::ffi::FfiStr) {
132 let event = $crate::DisconnectEvent {
133 error: unsafe { error.to_string_lossy() },
134 };
135 danmuji_rs_with_plugin(|plugin| plugin.disconnected($crate::Host::new(), event));
136 }
137
138 #[no_mangle]
139 pub extern "C" fn danmuji_rs_plugin_on_room_count(user_count: u32) {
140 danmuji_rs_with_plugin(|plugin| plugin.room_count($crate::Host::new(), user_count));
141 }
142
143 #[doc = "# Safety\n\n`danmaku` must be non-null and point to a readable `FfiDanmaku` value."]
144 #[no_mangle]
145 pub unsafe extern "C" fn danmuji_rs_plugin_on_danmaku(
146 danmaku: *const $crate::ffi::FfiDanmaku,
147 ) {
148 if danmaku.is_null() {
149 return;
150 }
151
152 let danmaku = unsafe { (*danmaku).to_model() };
153 danmuji_rs_with_plugin(|plugin| plugin.danmaku($crate::Host::new(), danmaku));
154 }
155 };
156}