Skip to main content

phoenixcord_sdk/
plugin.rs

1/*
2 * PhoenixCord, a Discord client mod
3 * Copyright (c) 2026 Leclowndu93150 and contributors
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7use crate::types::{
8    Badge, ChatBarButton, ChatBarContext, Element, Event, MemberContext, MenuItem, MenuTarget,
9    Message, OutgoingEdit, OutgoingMessage, PluginInfo, PopoverButton, ProfileContext,
10    SendDecision, Style,
11};
12
13/// A PhoenixCord plugin.
14///
15/// The host creates the plugin with [`Default`] when it is enabled and drops it when it is
16/// disabled. Every hook has an empty default, so implement only the hooks listed in
17/// [`PluginInfo::hooks`]. The meaning of each hook is documented on the matching function of the
18/// `plugin` interface in `phoenixcord.wit`.
19pub trait Plugin: Default + 'static {
20    /// Static metadata, read once when the plugin is loaded.
21    fn info() -> PluginInfo;
22
23    fn start(&mut self) {}
24
25    fn stop(&mut self) {}
26
27    fn on_event(&mut self, _event: Event) {}
28
29    fn pre_send(&mut self, _message: OutgoingMessage) -> SendDecision {
30        SendDecision::Keep
31    }
32
33    fn pre_edit(&mut self, _edit: OutgoingEdit) -> SendDecision {
34        SendDecision::Keep
35    }
36
37    fn message_accessories(&mut self, _message: Message) -> Vec<Element> {
38        Vec::new()
39    }
40
41    fn message_decorations(&mut self, _message: Message) -> Vec<Element> {
42        Vec::new()
43    }
44
45    fn message_popover_buttons(&mut self, _message: Message) -> Vec<PopoverButton> {
46        Vec::new()
47    }
48
49    fn chat_bar_buttons(&mut self, _context: ChatBarContext) -> Vec<ChatBarButton> {
50        Vec::new()
51    }
52
53    fn context_menu(&mut self, _target: MenuTarget) -> Vec<MenuItem> {
54        Vec::new()
55    }
56
57    fn profile_sections(&mut self, _context: ProfileContext) -> Vec<Element> {
58        Vec::new()
59    }
60
61    fn user_badges(&mut self, _context: ProfileContext) -> Vec<Badge> {
62        Vec::new()
63    }
64
65    fn member_decorations(&mut self, _context: MemberContext) -> Vec<Element> {
66        Vec::new()
67    }
68
69    fn server_list_elements(&mut self) -> Vec<Element> {
70        Vec::new()
71    }
72
73    fn settings_panel(&mut self) -> Option<Element> {
74        None
75    }
76}
77
78impl PluginInfo {
79    /// Metadata with every optional field empty and the plugin disabled by default. Fill in the
80    /// rest with struct update syntax.
81    pub fn new(name: &str, description: &str, authors: &[&str]) -> Self {
82        Self {
83            name: name.to_owned(),
84            description: description.to_owned(),
85            authors: authors.iter().map(|&author| author.to_owned()).collect(),
86            tags: Vec::new(),
87            dependencies: Vec::new(),
88            enabled_by_default: false,
89            required: false,
90            settings: Vec::new(),
91            commands: Vec::new(),
92            hooks: Vec::new(),
93            events: Vec::new(),
94        }
95    }
96}
97
98impl Default for Style {
99    fn default() -> Self {
100        Self {
101            direction: None,
102            align: None,
103            justify: None,
104            gap: None,
105            padding: None,
106            width: None,
107            height: None,
108            grow: false,
109            radius: None,
110            background: None,
111            foreground: None,
112            text_size: None,
113            text_weight: None,
114        }
115    }
116}
117
118/// Exports a [`Plugin`] implementation as the component's `plugin` interface. Use it once per
119/// plugin crate.
120#[macro_export]
121macro_rules! plugin {
122    ($plugin:ty) => {
123        struct PhoenixcordGuest;
124
125        ::std::thread_local! {
126            static PHOENIXCORD_INSTANCE: ::std::cell::RefCell<::std::option::Option<$plugin>> =
127                const { ::std::cell::RefCell::new(::std::option::Option::None) };
128        }
129
130        fn phoenixcord_with<R: ::std::default::Default>(f: impl FnOnce(&mut $plugin) -> R) -> R {
131            PHOENIXCORD_INSTANCE.with_borrow_mut(|instance| instance.as_mut().map(f).unwrap_or_default())
132        }
133
134        impl $crate::Guest for PhoenixcordGuest {
135            fn info() -> $crate::types::PluginInfo {
136                <$plugin as $crate::Plugin>::info()
137            }
138
139            fn start() {
140                PHOENIXCORD_INSTANCE.with_borrow_mut(|instance| {
141                    $crate::Plugin::start(instance.insert(<$plugin>::default()))
142                });
143            }
144
145            fn stop() {
146                if let ::std::option::Option::Some(mut plugin) =
147                    PHOENIXCORD_INSTANCE.with_borrow_mut(::std::option::Option::take)
148                {
149                    $crate::Plugin::stop(&mut plugin);
150                }
151            }
152
153            fn on_event(event: $crate::types::Event) {
154                phoenixcord_with(|plugin| $crate::Plugin::on_event(plugin, event))
155            }
156
157            fn pre_send(message: $crate::types::OutgoingMessage) -> $crate::types::SendDecision {
158                PHOENIXCORD_INSTANCE.with_borrow_mut(|instance| match instance.as_mut() {
159                    ::std::option::Option::Some(plugin) => $crate::Plugin::pre_send(plugin, message),
160                    ::std::option::Option::None => $crate::types::SendDecision::Keep,
161                })
162            }
163
164            fn pre_edit(edit: $crate::types::OutgoingEdit) -> $crate::types::SendDecision {
165                PHOENIXCORD_INSTANCE.with_borrow_mut(|instance| match instance.as_mut() {
166                    ::std::option::Option::Some(plugin) => $crate::Plugin::pre_edit(plugin, edit),
167                    ::std::option::Option::None => $crate::types::SendDecision::Keep,
168                })
169            }
170
171            fn message_accessories(message: $crate::types::Message) -> ::std::vec::Vec<$crate::types::Element> {
172                phoenixcord_with(|plugin| $crate::Plugin::message_accessories(plugin, message))
173            }
174
175            fn message_decorations(message: $crate::types::Message) -> ::std::vec::Vec<$crate::types::Element> {
176                phoenixcord_with(|plugin| $crate::Plugin::message_decorations(plugin, message))
177            }
178
179            fn message_popover_buttons(message: $crate::types::Message) -> ::std::vec::Vec<$crate::types::PopoverButton> {
180                phoenixcord_with(|plugin| $crate::Plugin::message_popover_buttons(plugin, message))
181            }
182
183            fn chat_bar_buttons(context: $crate::types::ChatBarContext) -> ::std::vec::Vec<$crate::types::ChatBarButton> {
184                phoenixcord_with(|plugin| $crate::Plugin::chat_bar_buttons(plugin, context))
185            }
186
187            fn context_menu(target: $crate::types::MenuTarget) -> ::std::vec::Vec<$crate::types::MenuItem> {
188                phoenixcord_with(|plugin| $crate::Plugin::context_menu(plugin, target))
189            }
190
191            fn profile_sections(context: $crate::types::ProfileContext) -> ::std::vec::Vec<$crate::types::Element> {
192                phoenixcord_with(|plugin| $crate::Plugin::profile_sections(plugin, context))
193            }
194
195            fn user_badges(context: $crate::types::ProfileContext) -> ::std::vec::Vec<$crate::types::Badge> {
196                phoenixcord_with(|plugin| $crate::Plugin::user_badges(plugin, context))
197            }
198
199            fn member_decorations(context: $crate::types::MemberContext) -> ::std::vec::Vec<$crate::types::Element> {
200                phoenixcord_with(|plugin| $crate::Plugin::member_decorations(plugin, context))
201            }
202
203            fn server_list_elements() -> ::std::vec::Vec<$crate::types::Element> {
204                phoenixcord_with($crate::Plugin::server_list_elements)
205            }
206
207            fn settings_panel() -> ::std::option::Option<$crate::types::Element> {
208                phoenixcord_with($crate::Plugin::settings_panel)
209            }
210        }
211
212        $crate::export!(PhoenixcordGuest with_types_in $crate);
213    };
214}