Skip to main content

danmuji_sdk/
lib.rs

1//! Rust side SDK for writing B站弹幕姬 (`bililive_dm`) plugins through the .NET bridge.
2
3pub 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}