├── .gitignore ├── clippy.toml ├── .travis.yml ├── .rustfmt.toml ├── Cargo.toml ├── README.tpl ├── LICENSE-MIT ├── README.md ├── LICENSE-APACHE └── src ├── plugin.rs ├── lib.rs └── ts3interface.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | /target/ 3 | /Cargo.lock 4 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["MiB", "GiB", "TiB", "PiB", "EiB", "TeamSpeak", "TeamSpeak3"] 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | cache: cargo 4 | rust: 5 | - stable 6 | - nightly 7 | 8 | branches: 9 | only: 10 | - master 11 | - develop 12 | notifications: 13 | email: false 14 | git: 15 | depth: 1 16 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | version = "Two" 2 | edition = "2018" 3 | newline_style = "Unix" 4 | 5 | hard_tabs = true 6 | use_small_heuristics = "Max" 7 | use_try_shorthand = true 8 | 9 | # Unstable 10 | 11 | where_single_line = true 12 | comment_width = 100 13 | wrap_comments = true 14 | fn_params_layout = "Compressed" 15 | fn_single_line = true 16 | format_strings = true 17 | overflow_delimited_expr = true 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ts3plugin" 3 | version = "0.3.1" 4 | authors = ["Flakebi "] 5 | description = """ 6 | An abstraction layer that simplifies creating TeamSpeak3 plugins 7 | and stores received data to provide a more convenient API. 8 | """ 9 | repository = "https://github.com/ReSpeak/rust-ts3plugin" 10 | readme = "README.md" 11 | keywords = ["plugin", "TeamSpeak3", "TS3"] 12 | categories = ["api-bindings"] 13 | license = "MIT/Apache-2.0" 14 | edition = "2024" 15 | build = "build/build.rs" 16 | 17 | [lib] 18 | name = "ts3plugin" 19 | # Needed for cargo-readme 20 | path = "src/lib.rs" 21 | 22 | [features] 23 | default = [] 24 | 25 | [dependencies] 26 | chrono = "0.4" 27 | lazy_static = "1" 28 | ts3plugin-sys = "0.5" 29 | 30 | [build-dependencies] 31 | anyhow = "1" 32 | serde = "1" 33 | tera = "1.20.1" 34 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | TeamSpeak3 Plugin API   [![Latest version](https://img.shields.io/crates/v/ts3plugin.svg)](https://crates.io/crates/ts3plugin) 2 | ===================== 3 | The documentation can be found here: [![At docs.rs](https://docs.rs/ts3plugin/badge.svg)](https://docs.rs/ts3plugin) 4 | 5 | {{readme}} 6 | 7 | Projects using this library 8 | --------------------------- 9 | - [TeamSpeak3 Text to Speech](https://github.com/ReSpeak/ts3tts) 10 | - [TsPressor](https://github.com/ReSpeak/TsPressor) 11 | 12 | License 13 | ------- 14 | Licensed under either of 15 | 16 | * [Apache License, Version 2.0](LICENSE-APACHE) 17 | * [MIT license](LICENSE-MIT) 18 | 19 | at your option. 20 | 21 | ### Contribution 22 | 23 | Unless you explicitly state otherwise, any contribution intentionally submitted 24 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 25 | dual licensed as above, without any additional terms or conditions. 26 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sebastian Neubauer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TeamSpeak3 Plugin API   [![Latest version](https://img.shields.io/crates/v/ts3plugin.svg)](https://crates.io/crates/ts3plugin) 2 | ===================== 3 | The documentation can be found here: [![At docs.rs](https://docs.rs/ts3plugin/badge.svg)](https://docs.rs/ts3plugin) 4 | 5 | TeamSpeak 3.6 updates the plugin api version to 26. 6 | Version 0.3 is compatible with this version. 7 | 8 | At the moment, not all methods that are exposed by the TeamSpeak API are 9 | available for plugins. If a method that you need is missing, please file an 10 | issue or open a pull request. 11 | 12 | ## Usage 13 | 14 | Add the following to your `Cargo.toml`: 15 | 16 | ```toml 17 | [package] 18 | name = "" 19 | version = "" 20 | authors = [""] 21 | description = "" 22 | 23 | [lib] 24 | name = "" 25 | crate-type = ["cdylib"] 26 | 27 | [dependencies] 28 | ts3plugin = "0.3" 29 | ``` 30 | 31 | ## Example 32 | 33 | A fully working example, which creates a plugin that does nothing: 34 | 35 | ```rust 36 | #[macro_use] 37 | extern crate ts3plugin; 38 | 39 | use ts3plugin::*; 40 | 41 | struct MyTsPlugin; 42 | 43 | impl Plugin for MyTsPlugin { 44 | // The default name is the crate name, but we can overwrite it here. 45 | fn name() -> String { String::from("My Ts Plugin") } 46 | fn command() -> Option { Some(String::from("myplugin")) } 47 | fn autoload() -> bool { false } 48 | fn configurable() -> ConfigureOffer { ConfigureOffer::No } 49 | 50 | // The only required method 51 | fn new(api: &TsApi) -> Result, InitError> { 52 | api.log_or_print("Inited", "MyTsPlugin", LogLevel::Info); 53 | Ok(Box::new(MyTsPlugin)) 54 | // Or return Err(InitError::Failure) on failure 55 | } 56 | 57 | // Implement callbacks here 58 | 59 | fn shutdown(&mut self, api: &TsApi) { 60 | api.log_or_print("Shutdown", "MyTsPlugin", LogLevel::Info); 61 | } 62 | } 63 | 64 | create_plugin!(MyTsPlugin); 65 | 66 | ``` 67 | 68 | Projects using this library 69 | --------------------------- 70 | - [TeamSpeak3 Text to Speech](https://github.com/ReSpeak/ts3tts) 71 | - [TsPressor](https://github.com/ReSpeak/TsPressor) 72 | 73 | License 74 | ------- 75 | Licensed under either of 76 | 77 | * [Apache License, Version 2.0](LICENSE-APACHE) 78 | * [MIT license](LICENSE-MIT) 79 | 80 | at your option. 81 | 82 | ### Contribution 83 | 84 | Unless you explicitly state otherwise, any contribution intentionally submitted 85 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 86 | dual licensed as above, without any additional terms or conditions. 87 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/plugin.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | #[derive(Debug)] 4 | pub enum InitError { 5 | /// Initialisation failed, the plugin will be unloaded again 6 | Failure, 7 | /// Like `Failure`, but the client will not show a "failed to load" warning. 8 | /// This is a very special case and should only be used if a plugin displays 9 | /// a dialog (e.g. overlay) asking the user to disable the plugin again, 10 | /// avoiding the show another dialog by the client telling the user the 11 | /// plugin failed to load. 12 | /// For normal case, if a plugin really failed to load because of an error, 13 | /// the correct return value is `Failure`. 14 | FailureNoMessage, 15 | } 16 | 17 | /// This trait that has to be implemented by a plugin. To enhance a library to a 18 | /// working TeamSpeak plugin you have to call the macro [`create_plugin!`] 19 | /// afterwards. 20 | /// 21 | /// [`create_plugin!`]: ../macro.create_plugin.html 22 | #[allow(unused_variables, unknown_lints, too_many_arguments)] 23 | pub trait Plugin: 'static + Send { 24 | // ************************* Configuration methods ************************* 25 | /// The name of the plugin as displayed in TeamSpeak. 26 | /// 27 | /// The default value is the crate name. 28 | fn name() -> String 29 | where Self: Sized { 30 | String::from("MAGIC\0") 31 | } 32 | /// The version of the plugin as displayed in TeamSpeak. 33 | /// 34 | /// The default value is the crate version. 35 | fn version() -> String 36 | where Self: Sized { 37 | String::from("MAGIC\0") 38 | } 39 | /// The author of the plugin as displayed in TeamSpeak. 40 | /// 41 | /// The default value is the crate author. 42 | fn author() -> String 43 | where Self: Sized { 44 | String::from("MAGIC\0") 45 | } 46 | /// The description of the plugin as displayed in TeamSpeak. 47 | /// 48 | /// The default value is the crate description. 49 | fn description() -> String 50 | where Self: Sized { 51 | String::from("MAGIC\0") 52 | } 53 | /// The command prefix that can be used by users in the chat, defaults to `None`. 54 | fn command() -> Option 55 | where Self: Sized { 56 | None 57 | } 58 | /// If the plugin offers the possibility to be configured, defaults to 59 | /// [`ConfigureOffer::No`]. 60 | /// 61 | /// [`ConfigureOffer::No`]: ../ts3plugin_sys/plugin_definitions/enum.ConfigureOffer.html 62 | fn configurable() -> crate::ConfigureOffer 63 | where Self: Sized { 64 | crate::ConfigureOffer::No 65 | } 66 | /// If the plugin should be loaded by default or only if activated manually, 67 | /// defaults to `false`. 68 | fn autoload() -> bool 69 | where Self: Sized { 70 | false 71 | } 72 | 73 | // *************************** Required methods **************************** 74 | /// Called when the plugin is loaded by TeamSpeak. 75 | fn new(api: &crate::TsApi) -> Result, InitError> 76 | where Self: Sized; 77 | 78 | // *************************** Optional methods **************************** 79 | /// If the connection status changes. 80 | /// If `status = ConnectStatus::Connecting`, the connection is not yet 81 | /// registered in the [`TsApi`]. 82 | /// 83 | /// [`TsApi`]: ../struct.TsApi.html 84 | fn connect_status_change( 85 | &mut self, api: &crate::TsApi, server: &crate::Server, status: crate::ConnectStatus, error: crate::Error, 86 | ) { 87 | } 88 | 89 | /// Callback for when the settings menu in TS3 is pressed 90 | fn configure(&mut self, api: &crate::TsApi) {} 91 | 92 | /// Called if a server is stopped. The server sends also a stop message. 93 | fn server_stop(&mut self, api: &crate::TsApi, server: &crate::Server, message: String) {} 94 | 95 | /// Called if a server error occurs. 96 | /// Return `false` if the TeamSpeak client should handle the error normally or 97 | /// `true` if the client should ignore the error. 98 | fn server_error( 99 | &mut self, api: &crate::TsApi, server: &crate::Server, error: crate::Error, message: String, 100 | return_code: String, extra_message: String, 101 | ) -> bool { 102 | false 103 | } 104 | 105 | /// Called if someone edited the server. 106 | fn server_edited(&mut self, api: &crate::TsApi, server: &crate::Server, invoker: Option<&crate::Invoker>) {} 107 | 108 | /// Called when the user requests the server info by middle-clicking on the server. 109 | fn server_connection_info(&mut self, api: &crate::TsApi, server: &crate::Server) {} 110 | 111 | fn connection_info(&mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection) {} 112 | 113 | fn connection_properties_changed( 114 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 115 | old_connection: &crate::Connection, changes: crate::ConnectionChanges, invoker: &crate::Invoker, 116 | ) { 117 | } 118 | 119 | /// If the plugin was informed about a new connection. If appeared is true, the connection 120 | /// was previously not known to the plugin, if appeared is false, the connection left 121 | /// the view of connection. 122 | fn connection_announced( 123 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, appeared: bool, 124 | ) { 125 | } 126 | 127 | /// Called, if a connection connects to the server. This is also called for our own 128 | /// connection. 129 | fn connection_changed( 130 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, connected: bool, 131 | message: String, 132 | ) { 133 | } 134 | 135 | /// Called if a connection switched the channel. 136 | fn connection_move( 137 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 138 | old_channel: &crate::Channel, new_channel: &crate::Channel, visibility: crate::Visibility, 139 | ) { 140 | } 141 | 142 | /// Called if a connection was moved by another connection. 143 | fn connection_moved( 144 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 145 | old_channel: &crate::Channel, new_channel: &crate::Channel, visibility: crate::Visibility, 146 | invoker: &crate::Invoker, 147 | ) { 148 | } 149 | 150 | /// Called when a connection times out. 151 | fn connection_timeout(&mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection) {} 152 | 153 | /// Called if a channel is announced to the client. 154 | /// This will be called for each channel when connecting to a server. 155 | fn channel_announced(&mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel) {} 156 | 157 | /// Called if the channel description was changed. 158 | fn channel_description_updated( 159 | &mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel, 160 | ) { 161 | } 162 | 163 | /// Called if the channel data are updated and available. 164 | /// This happens e.g. when the user clicked on the channel for the first time. 165 | fn channel_updated( 166 | &mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel, old_channel: &crate::Channel, 167 | ) { 168 | } 169 | 170 | /// Called if a channel was created. 171 | /// The invoker is `None` if the server created the channel. 172 | fn channel_created( 173 | &mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel, 174 | invoker: Option<&crate::Invoker>, 175 | ) { 176 | } 177 | 178 | /// Called if a channel was deleted. 179 | /// The invoker is `None` if the server deleted the channel. 180 | fn channel_deleted( 181 | &mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel, 182 | invoker: Option<&crate::Invoker>, 183 | ) { 184 | } 185 | 186 | /// Called if a channel was edited. 187 | fn channel_edited( 188 | &mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel, old_channel: &crate::Channel, 189 | invoker: &crate::Invoker, 190 | ) { 191 | } 192 | 193 | /// Called if the channel password was updated. 194 | fn channel_password_updated(&mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel) {} 195 | 196 | /// The current parent id of the channel is the old one, the new 197 | /// parent id is given as a parameter. 198 | fn channel_moved( 199 | &mut self, api: &crate::TsApi, server: &crate::Server, channel: &crate::Channel, 200 | new_parent_channel: &crate::Channel, invoker: Option<&crate::Invoker>, 201 | ) { 202 | } 203 | 204 | /// A message was received. `ignored` describes, if the friend and fool system 205 | /// of TeamSpeak ignored the message. 206 | /// Return `false` if the TeamSpeak client should handle the message normally or 207 | /// `true` if the client should ignore the message. 208 | fn message( 209 | &mut self, api: &crate::TsApi, server: &crate::Server, invoker: &crate::Invoker, 210 | target: crate::MessageReceiver, message: String, ignored: bool, 211 | ) -> bool { 212 | false 213 | } 214 | 215 | /// A user poked us. `ignored` describes, if the friend and fool system 216 | /// of TeamSpeak ignored the message. 217 | /// Return `false` if the TeamSpeak client should handle the poke normally or 218 | /// `true` if the client should ignore the poke. 219 | fn poke( 220 | &mut self, api: &crate::TsApi, server: &crate::Server, invoker: &crate::Invoker, message: String, 221 | ignored: bool, 222 | ) -> bool { 223 | false 224 | } 225 | 226 | fn channel_kick( 227 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 228 | old_channel: &crate::Channel, new_channel: &crate::Channel, visibility: crate::Visibility, 229 | invoker: &crate::Invoker, message: String, 230 | ) { 231 | } 232 | 233 | fn server_kick( 234 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 235 | invoker: &crate::Invoker, message: String, 236 | ) { 237 | } 238 | 239 | fn server_ban( 240 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 241 | invoker: &crate::Invoker, message: String, time: u64, 242 | ) { 243 | } 244 | 245 | /// The old values of `talking` and `whispering` are available from the connection. 246 | /// They will be updated after this functions returned. 247 | fn talking_changed( 248 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 249 | talking: crate::TalkStatus, whispering: bool, 250 | ) { 251 | } 252 | 253 | /// Called if the avatar of a client is updated. 254 | /// This also happens when the avatar is discovered for the first time. 255 | /// The avatar information are only fetched if requested, e.g. if the 256 | /// user clicks on a connection. 257 | fn avatar_changed( 258 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 259 | path: Option, 260 | ) { 261 | } 262 | 263 | /// Called if a channel group is assigned to a connection. 264 | fn connection_channel_group_changed( 265 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 266 | channel_group: &crate::ChannelGroup, channel: &crate::Channel, invoker: &crate::Invoker, 267 | ) { 268 | } 269 | 270 | /// Called if a server group is added to a connection. 271 | fn connection_server_group_added( 272 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Invoker, 273 | server_group: &crate::ServerGroup, invoker: &crate::Invoker, 274 | ) { 275 | } 276 | 277 | /// Called if a server group is removed from a connection. 278 | fn connection_server_group_removed( 279 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Invoker, 280 | server_group: &crate::ServerGroup, invoker: &crate::Invoker, 281 | ) { 282 | } 283 | 284 | /// Called when a voice packet from a client was received. 285 | /// 286 | /// From the TeamSpeak documentation: 287 | /// The following event is called when a voice packet from a client (not own 288 | /// client) is decoded and about to be played over your sound device, but 289 | /// before it is 3D positioned and mixed with other sounds. You can use this 290 | /// function to alter the voice data (for example when you want to do 291 | /// effects on it) or to simply get voice data. The TeamSpeak client uses 292 | /// this function to record sessions. 293 | /// 294 | /// The voice data is available as 16 bit with 48 KHz. The channels are packed 295 | /// (interleaved). 296 | /// The callbacks with audio data are called from another thread than the 297 | /// other functions. 298 | fn playback_voice_data( 299 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 300 | samples: &mut [i16], channels: i32, 301 | ) { 302 | } 303 | 304 | /// Called when a voice packet from a client was positioned. 305 | /// 306 | /// From the TeamSpeak documentation: 307 | /// The following event is called when a voice packet from a client (not own 308 | /// client) is decoded and 3D positioned and about to be played over your 309 | /// sound device, but before it is mixed with other sounds. You can use this 310 | /// function to alter/get the voice data after 3D positioning. 311 | /// 312 | /// The voice data is available as 16 bit with 48 KHz. The channels are packed 313 | /// (interleaved). 314 | /// The callbacks with audio data are called from another thread than the 315 | /// other functions. 316 | fn post_process_voice_data( 317 | &mut self, api: &crate::TsApi, server: &crate::Server, connection: &crate::Connection, 318 | samples: &mut [i16], channels: i32, channel_speaker_array: &[crate::Speaker], 319 | channel_fill_mask: &mut u32, 320 | ) { 321 | } 322 | 323 | /// Called when all voice data were mixed. 324 | /// 325 | /// From the TeamSpeak documentation: 326 | /// The following event is called when all sounds that are about to be 327 | /// played back for this server connection are mixed. This is the last 328 | /// chance to alter/get sound. 329 | /// 330 | /// The voice data is available as 16 bit with 48 KHz. The channels are packed 331 | /// (interleaved). 332 | /// The callbacks with audio data are called from another thread than the 333 | /// other functions. 334 | fn mixed_playback_voice_data( 335 | &mut self, api: &crate::TsApi, server: &crate::Server, samples: &mut [i16], channels: i32, 336 | channel_speaker_array: &[crate::Speaker], channel_fill_mask: &mut u32, 337 | ) { 338 | } 339 | 340 | /// The recorded sound from the current capture device. 341 | /// 342 | /// `send` is set if the audio data will be send to the server. This attribute 343 | /// can be changed in this callback. 344 | /// The return value of this function describes if the sound data was altered. 345 | /// Return `true` if the sound was changed and `false` otherwise. 346 | /// The callbacks with audio data are called from another thread than the 347 | /// other functions. 348 | fn captured_voice_data( 349 | &mut self, api: &crate::TsApi, server: &crate::Server, samples: &mut [i16], channels: i32, 350 | send: &mut bool, 351 | ) -> bool { 352 | false 353 | } 354 | 355 | /// Return `false` if the TeamSpeak client should handle the error normally or 356 | /// `true` if the client should ignore the error. 357 | fn permission_error( 358 | &mut self, api: &crate::TsApi, server: &crate::Server, permission: &crate::Permission, error: crate::Error, 359 | message: String, return_code: String, 360 | ) -> bool { 361 | false 362 | } 363 | 364 | /// Called when a message from another plugin is received. 365 | /// 366 | /// Messages can be sent with [`Server::send_plugin_message`]. 367 | /// The message is called `PluginCommand` by TeamSpeak. 368 | /// 369 | /// [`Server::send_plugin_message`]: ../struct.Server.html#method.send_plugin_message 370 | fn plugin_message( 371 | &mut self, api: &crate::TsApi, server: &crate::Server, plugin: String, message: String, 372 | invoker: Option<&crate::Invoker>, 373 | ) { 374 | } 375 | 376 | /// Called when the user enters a command in the chat box. 377 | /// 378 | /// Commands that are prefixed with the string, which is specified in 379 | /// [`Plugin::command`], are redirected to this function. 380 | /// The command prefix is not contained in the given argument. 381 | /// 382 | /// Return `true` if this function handled the command or `false` if not. 383 | /// 384 | /// [`Plugin::command`]: #method.command 385 | fn process_command(&mut self, api: &crate::TsApi, server: &crate::Server, command: String) -> bool { 386 | false 387 | } 388 | 389 | /// Called if the plugin is getting disabled (either by the user or if 390 | /// TeamSpeak is exiting). 391 | fn shutdown(&mut self, api: &crate::TsApi) {} 392 | } 393 | 394 | /// Save the `CString`s that are returned from the TeamSpeak API. 395 | /// We don't want to return invalid pointers. 396 | #[doc(hidden)] 397 | pub struct CreatePluginData { 398 | pub name: Option<::std::ffi::CString>, 399 | pub version: Option<::std::ffi::CString>, 400 | pub author: Option<::std::ffi::CString>, 401 | pub description: Option<::std::ffi::CString>, 402 | pub command: Option>, 403 | } 404 | 405 | lazy_static! { 406 | #[doc(hidden)] 407 | pub static ref CREATE_PLUGIN_DATA: Mutex = 408 | Mutex::new(CreatePluginData { 409 | name: None, 410 | version: None, 411 | author: None, 412 | description: None, 413 | command: None, 414 | }); 415 | } 416 | 417 | /// Create a plugin. 418 | /// 419 | /// This macro has to be called once per library to create the 420 | /// function interface that is used by TeamSpeak. The argument is the struct 421 | /// which implements the [`Plugin`] trait. 422 | /// 423 | /// # Examples 424 | /// 425 | /// ```ignore 426 | /// create_plugin!(MyTsPlugin); 427 | /// ``` 428 | /// 429 | /// [`Plugin`]: plugin/trait.Plugin.html 430 | #[macro_export] 431 | macro_rules! create_plugin { 432 | ($typename: ident) => { 433 | /// Initialise the plugin and return the error status. 434 | #[unsafe(no_mangle)] 435 | #[doc(hidden)] 436 | pub unsafe extern "C" fn ts3plugin_init() -> std::os::raw::c_int { 437 | match $crate::ts3interface::private_init::<$typename>() { 438 | Ok(_) => 0, 439 | Err($crate::InitError::Failure) => 1, 440 | Err($crate::InitError::FailureNoMessage) => -2, 441 | } 442 | } 443 | 444 | /// Unique name identifying this plugin. 445 | /// The result of this function has to be a null-terminated static string. 446 | /// Can be called before init. 447 | #[unsafe(no_mangle)] 448 | #[doc(hidden)] 449 | pub extern "C" fn ts3plugin_name() -> *const std::os::raw::c_char { 450 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 451 | if data.name.is_none() { 452 | let s = $typename::name(); 453 | if s == "MAGIC\0" { 454 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_NAME")) 455 | .expect("Crate name contains nul character"); 456 | data.name = Some(s); 457 | } else { 458 | let s = 459 | ::std::ffi::CString::new(s).expect("Plugin name contains nul character"); 460 | data.name = Some(s); 461 | } 462 | } 463 | data.name.as_ref().unwrap().as_ptr() 464 | } 465 | 466 | /// The version of the plugin. 467 | /// Can be called before init. 468 | #[unsafe(no_mangle)] 469 | #[doc(hidden)] 470 | pub extern "C" fn ts3plugin_version() -> *const std::os::raw::c_char { 471 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 472 | if data.version.is_none() { 473 | let s = $typename::version(); 474 | if s == "MAGIC\0" { 475 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_VERSION")) 476 | .expect("Crate version contains nul character"); 477 | data.version = Some(s); 478 | } else { 479 | let s = 480 | ::std::ffi::CString::new(s).expect("Plugin version contains nul character"); 481 | data.version = Some(s); 482 | } 483 | } 484 | data.version.as_ref().unwrap().as_ptr() 485 | } 486 | 487 | /// The author of the plugin. 488 | /// Can be called before init. 489 | #[unsafe(no_mangle)] 490 | #[doc(hidden)] 491 | pub extern "C" fn ts3plugin_author() -> *const std::os::raw::c_char { 492 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 493 | if data.author.is_none() { 494 | let s = $typename::author(); 495 | if s == "MAGIC\0" { 496 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_AUTHORS")) 497 | .expect("Crate author contains nul character"); 498 | data.author = Some(s); 499 | } else { 500 | let s = 501 | ::std::ffi::CString::new(s).expect("Plugin author contains nul character"); 502 | data.author = Some(s); 503 | } 504 | } 505 | data.author.as_ref().unwrap().as_ptr() 506 | } 507 | 508 | /// The desription of the plugin. 509 | /// Can be called before init. 510 | #[unsafe(no_mangle)] 511 | #[doc(hidden)] 512 | pub extern "C" fn ts3plugin_description() -> *const std::os::raw::c_char { 513 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 514 | if data.description.is_none() { 515 | let s = $typename::description(); 516 | if s == "MAGIC\0" { 517 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_DESCRIPTION")) 518 | .expect("Crate description contains nul character"); 519 | data.description = Some(s); 520 | } else { 521 | let s = ::std::ffi::CString::new(s) 522 | .expect("Plugin description contains nul character"); 523 | data.description = Some(s); 524 | } 525 | } 526 | data.description.as_ref().unwrap().as_ptr() 527 | } 528 | 529 | /// If the plugin offers the possibility to be configured by the user. 530 | /// Can be called before init. 531 | #[allow(non_snake_case)] 532 | #[unsafe(no_mangle)] 533 | #[doc(hidden)] 534 | pub extern "C" fn ts3plugin_commandKeyword() -> *const std::os::raw::c_char { 535 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 536 | if data.command.is_none() { 537 | data.command = Some(if let Some(s) = $typename::command() { 538 | let s = ::std::ffi::CString::new(s).expect("String contains nul character"); 539 | Some(s) 540 | } else { 541 | None 542 | }) 543 | } 544 | if let &Some(ref s) = data.command.as_ref().unwrap() { 545 | s.as_ptr() 546 | } else { 547 | std::ptr::null() 548 | } 549 | } 550 | 551 | /// If the plugin offers the possibility to be configured by the user. 552 | /// Can be called before init. 553 | #[allow(non_snake_case)] 554 | #[unsafe(no_mangle)] 555 | #[doc(hidden)] 556 | pub extern "C" fn ts3plugin_offersConfigure() -> std::os::raw::c_int { 557 | $typename::configurable() as std::os::raw::c_int 558 | } 559 | 560 | /// If the plugin should be loaded automatically. 561 | /// Can be called before init. 562 | #[allow(non_snake_case)] 563 | #[unsafe(no_mangle)] 564 | #[doc(hidden)] 565 | pub extern "C" fn ts3plugin_requestAutoload() -> std::os::raw::c_int { 566 | if $typename::autoload() { 1 } else { 0 } 567 | } 568 | }; 569 | } 570 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! TeamSpeak 3.6 updates the plugin api version to 26. 2 | //! Version 0.3 is compatible with this version. 3 | //! 4 | //! At the moment, not all methods that are exposed by the TeamSpeak API are 5 | //! available for plugins. If a method that you need is missing, please file an 6 | //! issue or open a pull request. 7 | //! 8 | //! # Usage 9 | //! 10 | //! Add the following to your `Cargo.toml`: 11 | //! 12 | //! ```toml 13 | //! [package] 14 | //! name = "" 15 | //! version = "" 16 | //! authors = [""] 17 | //! description = "" 18 | //! 19 | //! [lib] 20 | //! name = "" 21 | //! crate-type = ["cdylib"] 22 | //! 23 | //! [dependencies] 24 | //! ts3plugin = "0.3" 25 | //! ``` 26 | //! 27 | //! # Example 28 | //! 29 | //! A fully working example, which creates a plugin that does nothing: 30 | //! 31 | //! ``` 32 | //! #[macro_use] 33 | //! extern crate ts3plugin; 34 | //! 35 | //! use ts3plugin::*; 36 | //! 37 | //! struct MyTsPlugin; 38 | //! 39 | //! impl Plugin for MyTsPlugin { 40 | //! // The default name is the crate name, but we can overwrite it here. 41 | //! fn name() -> String { String::from("My Ts Plugin") } 42 | //! fn command() -> Option { Some(String::from("myplugin")) } 43 | //! fn autoload() -> bool { false } 44 | //! fn configurable() -> ConfigureOffer { ConfigureOffer::No } 45 | //! 46 | //! // The only required method 47 | //! fn new(api: &TsApi) -> Result, InitError> { 48 | //! api.log_or_print("Inited", "MyTsPlugin", LogLevel::Info); 49 | //! Ok(Box::new(MyTsPlugin)) 50 | //! // Or return Err(InitError::Failure) on failure 51 | //! } 52 | //! 53 | //! // Implement callbacks here 54 | //! 55 | //! fn shutdown(&mut self, api: &TsApi) { 56 | //! api.log_or_print("Shutdown", "MyTsPlugin", LogLevel::Info); 57 | //! } 58 | //! } 59 | //! 60 | //! create_plugin!(MyTsPlugin); 61 | //! 62 | //! # fn main() { } 63 | //! ``` 64 | 65 | // TODO This should be removed at some time, when more code is ready 66 | #![allow(dead_code)] 67 | 68 | extern crate chrono; 69 | #[macro_use] 70 | extern crate lazy_static; 71 | extern crate ts3plugin_sys; 72 | 73 | pub use ts3plugin_sys::plugin_definitions::*; 74 | pub use ts3plugin_sys::public_definitions::*; 75 | pub use ts3plugin_sys::public_errors::Error; 76 | pub use ts3plugin_sys::ts3functions::Ts3Functions; 77 | 78 | pub use crate::plugin::*; 79 | 80 | use chrono::*; 81 | use std::collections::HashMap as Map; 82 | use std::ffi::{CStr, CString}; 83 | use std::fmt; 84 | use std::mem::transmute; 85 | use std::ops::{Deref, DerefMut}; 86 | use std::os::raw::{c_char, c_int}; 87 | use std::sync::{MutexGuard, RwLock}; 88 | 89 | /// Converts a normal `String` to a `CString`. 90 | macro_rules! to_cstring { 91 | ($string: expr_2021) => { 92 | CString::new($string).unwrap_or(CString::new("String contains null character").unwrap()) 93 | }; 94 | } 95 | 96 | /// Converts a `CString` to a normal `String`. 97 | macro_rules! to_string { 98 | ($string: expr_2021) => {{ String::from_utf8_lossy(CStr::from_ptr($string).to_bytes()).into_owned() }}; 99 | } 100 | 101 | // Declare modules here so the macros are visible in the modules 102 | pub mod plugin; 103 | pub mod ts3interface; 104 | 105 | // Import automatically generated structs 106 | include!(concat!(env!("OUT_DIR"), "/channel.rs")); 107 | include!(concat!(env!("OUT_DIR"), "/connection.rs")); 108 | include!(concat!(env!("OUT_DIR"), "/server.rs")); 109 | 110 | /// The api functions provided by TeamSpeak 111 | /// 112 | /// This is not part of the official api and is only public to permit dirty 113 | /// hacks! 114 | #[doc(hidden)] 115 | pub static TS3_FUNCTIONS: RwLock> = RwLock::new(None); 116 | 117 | // ******************** Structs ******************** 118 | /// The possible receivers of a message. A message can be sent to a specific 119 | /// connection, to the current channel chat or to the server chat. 120 | #[derive(Clone)] 121 | pub enum MessageReceiver { 122 | Connection(ConnectionId), 123 | Channel, 124 | Server, 125 | } 126 | 127 | /// Permissions - TODO not yet implemented 128 | #[derive(Debug, PartialEq, Eq, Clone)] 129 | pub struct Permissions; 130 | 131 | /// A wrapper for a server id. 132 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 133 | pub struct ServerId(u64); 134 | 135 | /// A wrapper for a channel id. 136 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 137 | pub struct ChannelId(u64); 138 | 139 | /// A wrapper for a connection id. 140 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 141 | pub struct ConnectionId(u16); 142 | 143 | #[derive(Debug, Clone)] 144 | pub struct Permission {} 145 | 146 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 147 | pub struct PermissionId(u32); 148 | 149 | #[derive(Debug, Clone)] 150 | pub struct ServerGroup {} 151 | 152 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 153 | pub struct ServerGroupId(u64); 154 | 155 | #[derive(Debug, Clone)] 156 | pub struct ChannelGroup {} 157 | 158 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 159 | pub struct ChannelGroupId(u64); 160 | 161 | // ******************** Implementation ******************** 162 | 163 | // ********** Invoker ********** 164 | #[derive(Debug, Eq)] 165 | pub struct InvokerData { 166 | id: ConnectionId, 167 | uid: String, 168 | name: String, 169 | } 170 | 171 | impl PartialEq for InvokerData { 172 | fn eq(&self, other: &InvokerData) -> bool { 173 | self.id == other.id 174 | } 175 | } 176 | 177 | impl InvokerData { 178 | fn new(id: ConnectionId, uid: String, name: String) -> InvokerData { 179 | InvokerData { id, uid, name } 180 | } 181 | 182 | /// Get the connection id of this invoker. 183 | pub fn get_id(&self) -> ConnectionId { 184 | self.id 185 | } 186 | 187 | /// Get the unique id of this invoker. 188 | pub fn get_uid(&self) -> &String { 189 | &self.uid 190 | } 191 | 192 | /// Get the name of this invoker. 193 | pub fn get_name(&self) -> &String { 194 | &self.name 195 | } 196 | } 197 | 198 | /// The invoker is maybe not visible to the user, but we can get events caused 199 | /// by him, so some information about him are passed along with his id. 200 | #[derive(Debug, Eq)] 201 | pub struct Invoker<'a> { 202 | server: Server<'a>, 203 | data: InvokerData, 204 | } 205 | 206 | impl<'a, 'b> PartialEq> for Invoker<'a> { 207 | fn eq(&self, other: &Invoker) -> bool { 208 | self.server == other.server && self.data == other.data 209 | } 210 | } 211 | impl<'a> Deref for Invoker<'a> { 212 | type Target = InvokerData; 213 | fn deref(&self) -> &Self::Target { 214 | &self.data 215 | } 216 | } 217 | 218 | impl<'a> Invoker<'a> { 219 | fn new(server: Server<'a>, data: InvokerData) -> Invoker<'a> { 220 | Invoker { server, data } 221 | } 222 | 223 | pub fn get_connection(&'_ self) -> Option> { 224 | self.server.get_connection(self.id) 225 | } 226 | } 227 | 228 | // ********** Server ********** 229 | #[derive(Clone)] 230 | pub struct Server<'a> { 231 | api: &'a TsApi, 232 | data: Result<&'a ServerData, ServerId>, 233 | } 234 | 235 | impl<'a, 'b> PartialEq> for Server<'a> { 236 | fn eq(&self, other: &Server<'b>) -> bool { 237 | self.get_id() == other.get_id() 238 | } 239 | } 240 | impl<'a> Eq for Server<'a> {} 241 | impl<'a> fmt::Debug for Server<'a> { 242 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 243 | write!(f, "Server({})", self.get_id().0) 244 | } 245 | } 246 | 247 | impl PartialEq for ServerData { 248 | fn eq(&self, other: &ServerData) -> bool { 249 | self.id == other.id 250 | } 251 | } 252 | impl Eq for ServerData {} 253 | 254 | impl ServerData { 255 | /// Get a server property that is stored as a string. 256 | fn get_property_as_string( 257 | id: ServerId, property: VirtualServerProperties, 258 | ) -> Result { 259 | unsafe { 260 | let mut name: *mut c_char = std::ptr::null_mut(); 261 | let res: Error = 262 | transmute((TS3_FUNCTIONS 263 | .read() 264 | .unwrap() 265 | .as_ref() 266 | .expect("Functions should be loaded") 267 | .get_server_variable_as_string)(id.0, property as usize, &mut name)); 268 | match res { 269 | Error::Ok => Ok(to_string!(name)), 270 | _ => Err(res), 271 | } 272 | } 273 | } 274 | 275 | /// Get a server property that is stored as an int. 276 | fn get_property_as_int(id: ServerId, property: VirtualServerProperties) -> Result { 277 | unsafe { 278 | let mut number: c_int = 0; 279 | let res: Error = 280 | transmute((TS3_FUNCTIONS 281 | .read() 282 | .unwrap() 283 | .as_ref() 284 | .expect("Functions should be loaded") 285 | .get_server_variable_as_int)(id.0, property as usize, &mut number)); 286 | match res { 287 | Error::Ok => Ok(number as i32), 288 | _ => Err(res), 289 | } 290 | } 291 | } 292 | 293 | /// Get a server property that is stored as an int. 294 | fn get_property_as_uint64( 295 | id: ServerId, property: VirtualServerProperties, 296 | ) -> Result { 297 | unsafe { 298 | let mut number: u64 = 0; 299 | let res: Error = transmute((TS3_FUNCTIONS 300 | .read() 301 | .unwrap() 302 | .as_ref() 303 | .expect("Functions should be loaded") 304 | .get_server_variable_as_uint64)( 305 | id.0, property as usize, &mut number 306 | )); 307 | match res { 308 | Error::Ok => Ok(number), 309 | _ => Err(res), 310 | } 311 | } 312 | } 313 | 314 | /// Get the connection id of our own client. 315 | /// Called when a new Server is created. 316 | fn query_own_connection_id(id: ServerId) -> Result { 317 | unsafe { 318 | let mut number: u16 = 0; 319 | let res: Error = transmute((TS3_FUNCTIONS 320 | .read() 321 | .unwrap() 322 | .as_ref() 323 | .expect("Functions should be loaded") 324 | .get_client_id)(id.0, &mut number)); 325 | match res { 326 | Error::Ok => Ok(ConnectionId(number)), 327 | _ => Err(res), 328 | } 329 | } 330 | } 331 | 332 | /// Get all currently active connections on this server. 333 | /// Called when a new Server is created. 334 | /// When an error occurs, users are not inserted into the map. 335 | fn query_connections(id: ServerId) -> Map { 336 | let mut map = Map::new(); 337 | // Query connected connections 338 | let mut result: *mut u16 = std::ptr::null_mut(); 339 | let res: Error = unsafe { 340 | transmute((TS3_FUNCTIONS 341 | .read() 342 | .unwrap() 343 | .as_ref() 344 | .expect("Functions should be loaded") 345 | .get_client_list)(id.0, &mut result)) 346 | }; 347 | if res == Error::Ok { 348 | unsafe { 349 | let mut counter = 0; 350 | while *result.offset(counter) != 0 { 351 | let connection_id = ConnectionId(*result.offset(counter)); 352 | let mut connection = ConnectionData::new(id, connection_id); 353 | connection.update(); 354 | map.insert(connection_id, connection); 355 | counter += 1; 356 | } 357 | } 358 | } 359 | map 360 | } 361 | 362 | /// Get all channels on this server. 363 | /// Called when a new Server is created. 364 | /// When an error occurs, channels are not inserted into the map. 365 | fn query_channels(id: ServerId) -> Result, Error> { 366 | let mut map = Map::new(); 367 | // Query connected connections 368 | let mut result: *mut u64 = std::ptr::null_mut(); 369 | let res: Error = unsafe { 370 | transmute((TS3_FUNCTIONS 371 | .read() 372 | .unwrap() 373 | .as_ref() 374 | .expect("Functions should be loaded") 375 | .get_channel_list)(id.0, &mut result)) 376 | }; 377 | if res == Error::Ok { 378 | unsafe { 379 | let mut counter = 0; 380 | while *result.offset(counter) != 0 { 381 | let channel_id = ChannelId(*result.offset(counter)); 382 | let mut channel = ChannelData::new(id, channel_id); 383 | channel.update(); 384 | map.insert(channel_id, channel); 385 | counter += 1; 386 | } 387 | } 388 | Ok(map) 389 | } else { 390 | Err(res) 391 | } 392 | } 393 | 394 | // ********** Private Interface ********** 395 | 396 | fn add_connection(&mut self, connection_id: ConnectionId) -> &mut ConnectionData { 397 | let mut connection = ConnectionData::new(self.id, connection_id); 398 | connection.update(); 399 | self.visible_connections.insert(connection_id, connection); 400 | self.visible_connections.get_mut(&connection_id).unwrap() 401 | } 402 | 403 | fn remove_connection(&mut self, connection_id: ConnectionId) -> Option { 404 | self.visible_connections.remove(&connection_id) 405 | } 406 | 407 | fn add_channel(&mut self, channel_id: ChannelId) -> Result<&mut ChannelData, Error> { 408 | match self.channels { 409 | Ok(ref mut cs) => { 410 | let mut channel = ChannelData::new(self.id, channel_id); 411 | channel.update(); 412 | cs.insert(channel_id, channel); 413 | Ok(cs.get_mut(&channel_id).unwrap()) 414 | } 415 | Err(error) => Err(error), 416 | } 417 | } 418 | 419 | fn remove_channel(&mut self, channel_id: ChannelId) -> Option { 420 | self.channels.as_mut().ok().and_then(|cs| cs.remove(&channel_id)) 421 | } 422 | 423 | /// Get the mutable connection on this server that has the specified id, returns 424 | /// `None` if there is no such connection. 425 | fn get_mut_connection(&mut self, connection_id: ConnectionId) -> Option<&mut ConnectionData> { 426 | self.visible_connections.get_mut(&connection_id) 427 | } 428 | 429 | /// Get the mutable channel on this server that has the specified id, returns 430 | /// `None` if there is no such channel. 431 | fn get_mut_channel(&mut self, channel_id: ChannelId) -> Option<&mut ChannelData> { 432 | self.channels.as_mut().ok().and_then(|cs| cs.get_mut(&channel_id)) 433 | } 434 | } 435 | 436 | impl<'a> Server<'a> { 437 | fn new(api: &'a TsApi, data: &'a ServerData) -> Server<'a> { 438 | Server { api, data: Ok(data) } 439 | } 440 | 441 | fn new_err(api: &'a TsApi, server_id: ServerId) -> Server<'a> { 442 | Server { api, data: Err(server_id) } 443 | } 444 | 445 | pub fn get_id(&self) -> ServerId { 446 | match self.data { 447 | Ok(data) => data.get_id(), 448 | Err(id) => id, 449 | } 450 | } 451 | 452 | /// Get the connection on this server that has the specified id, returns 453 | /// `None` if there is no such connection. 454 | fn get_connection_unwrap(&self, connection_id: ConnectionId) -> Connection<'a> { 455 | self.get_connection(connection_id).unwrap_or_else(|| { 456 | self.api.log_or_print( 457 | format!("Can't find connection {:?}", connection_id), 458 | "rust-ts3plugin", 459 | crate::LogLevel::Warning, 460 | ); 461 | Connection::new_err(&self.api, self.get_id(), connection_id) 462 | }) 463 | } 464 | 465 | /// Get the channel on this server that has the specified id, returns 466 | /// `None` if there is no such channel. 467 | fn get_channel_unwrap(&self, channel_id: ChannelId) -> Channel<'a> { 468 | self.get_channel(channel_id).unwrap_or_else(|| { 469 | self.api.log_or_print( 470 | format!("Can't find channel {:?}", channel_id), 471 | "rust-ts3plugin", 472 | crate::LogLevel::Warning, 473 | ); 474 | Channel::new_owned(&self.api, self.get_id(), channel_id) 475 | }) 476 | } 477 | 478 | fn get_server_group_unwrap(&self, server_group_id: ServerGroupId) -> ServerGroup { 479 | self.get_server_group(server_group_id).unwrap_or_else(|| { 480 | /*self.api.log_or_print( 481 | format!("Can't find server group {:?}", server_group_id), 482 | "rust-ts3plugin", ::LogLevel::Warning);*/ 483 | ServerGroup {} 484 | }) 485 | } 486 | 487 | fn get_channel_group_unwrap(&self, channel_group_id: ChannelGroupId) -> ChannelGroup { 488 | self.get_channel_group(channel_group_id).unwrap_or_else(|| { 489 | //self.api.log_or_print(format!("Can't find channel group {:?}", channel_group_id), 490 | // "rust-ts3plugin", ::LogLevel::Warning); 491 | ChannelGroup {} 492 | }) 493 | } 494 | 495 | // ********** Public Interface ********** 496 | 497 | /*/// The server properties that are only available on request. 498 | pub fn get_optional_data(&self) -> Option<&OptionalServerData> { 499 | self.data.ok().map(|data| &data.optional_data) 500 | }*/ 501 | 502 | /// Get the own connection to the server. 503 | pub fn get_own_connection(&self) -> Result, Error> { 504 | match self.data { 505 | Ok(data) => data.get_own_connection_id().map(|id| self.get_connection_unwrap(id)), 506 | Err(_) => Err(Error::Ok), 507 | } 508 | } 509 | 510 | /// Get the ids of all visible connections on this server. 511 | pub fn get_connections(&self) -> Vec> { 512 | match self.data { 513 | Ok(data) => { 514 | data.visible_connections.values().map(|c| Connection::new(self.api, &c)).collect() 515 | } 516 | Err(_) => Vec::new(), 517 | } 518 | } 519 | 520 | /// Get the ids of all channels on this server. 521 | pub fn get_channels(&self) -> Vec> { 522 | match self.data { 523 | Ok(data) => match data.channels { 524 | Ok(ref cs) => cs.values().map(|c| Channel::new(self.api, &c)).collect(), 525 | Err(_) => Vec::new(), 526 | }, 527 | Err(_) => Vec::new(), 528 | } 529 | } 530 | 531 | /// Get the connection on this server that has the specified id, returns 532 | /// `None` if there is no such connection. 533 | pub fn get_connection(&self, connection_id: ConnectionId) -> Option> { 534 | self.data.ok().and_then(|data| { 535 | data.visible_connections.get(&connection_id).map(|c| Connection::new(&self.api, c)) 536 | }) 537 | } 538 | 539 | /// Get the channel on this server that has the specified id, returns 540 | /// `None` if there is no such channel. 541 | pub fn get_channel(&self, channel_id: ChannelId) -> Option> { 542 | self.data.ok().and_then(|data| { 543 | data.channels 544 | .as_ref() 545 | .ok() 546 | .and_then(|cs| cs.get(&channel_id)) 547 | .map(|c| Channel::new(&self.api, c)) 548 | }) 549 | } 550 | 551 | pub fn get_server_group(&self, _server_group_id: ServerGroupId) -> Option { 552 | // TODO 553 | Some(ServerGroup {}) 554 | } 555 | 556 | pub fn get_channel_group(&self, _channel_group_id: ChannelGroupId) -> Option { 557 | // TODO 558 | Some(ChannelGroup {}) 559 | } 560 | 561 | /// Send a message to the server chat. 562 | pub fn send_message>(&self, message: S) -> Result<(), Error> { 563 | unsafe { 564 | let text = to_cstring!(message.as_ref()); 565 | let res: Error = transmute((TS3_FUNCTIONS 566 | .read() 567 | .unwrap() 568 | .as_ref() 569 | .expect("Functions should be loaded") 570 | .request_send_server_text_msg)( 571 | self.get_id().0, text.as_ptr(), std::ptr::null() 572 | )); 573 | match res { 574 | Error::Ok => Ok(()), 575 | _ => Err(res), 576 | } 577 | } 578 | } 579 | 580 | /// Sends a plugin message to all connections on the server. 581 | /// 582 | /// Messages can be received in [`Plugin::plugin_message`]. 583 | /// This is refered to as `PluginCommand` in TeamSpeak. 584 | /// 585 | /// [`Plugin::plugin_message`]: plugin/trait.Plugin.html#method.plugin_message 586 | pub fn send_plugin_message>(&self, message: S) { 587 | let text = to_cstring!(message.as_ref()); 588 | (TS3_FUNCTIONS 589 | .read() 590 | .unwrap() 591 | .as_ref() 592 | .expect("Functions should be loaded") 593 | .send_plugin_command)( 594 | self.get_id().0, 595 | to_cstring!(self.api.get_plugin_id()).as_ptr(), 596 | text.as_ptr(), 597 | PluginTargetMode::Server as i32, 598 | std::ptr::null(), 599 | std::ptr::null(), 600 | ); 601 | } 602 | 603 | /// Print a message into the server or channel tab of this server. This is only 604 | /// visible in the window of this client and will not be sent to the server. 605 | pub fn print_message>(&self, message: S, target: MessageTarget) { 606 | let text = to_cstring!(message.as_ref()); 607 | (TS3_FUNCTIONS.read().unwrap().as_ref().expect("Functions should be loaded").print_message)( 608 | self.get_id().0, 609 | text.as_ptr(), 610 | target, 611 | ); 612 | } 613 | } 614 | 615 | // ********** Channel ********** 616 | #[derive(Clone)] 617 | pub struct Channel<'a> { 618 | api: &'a TsApi, 619 | data: Result<&'a ChannelData, (ServerId, ChannelId)>, 620 | } 621 | 622 | impl<'a, 'b> PartialEq> for Channel<'a> { 623 | fn eq(&self, other: &Channel<'b>) -> bool { 624 | self.get_server_id() == other.get_server_id() && self.get_id() == other.get_id() 625 | } 626 | } 627 | impl<'a> Eq for Channel<'a> {} 628 | impl<'a> fmt::Debug for Channel<'a> { 629 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 630 | write!(f, "Channel({})", self.get_id().0) 631 | } 632 | } 633 | 634 | impl PartialEq for ChannelData { 635 | fn eq(&self, other: &ChannelData) -> bool { 636 | self.server_id == other.server_id && self.id == other.id 637 | } 638 | } 639 | impl Eq for ChannelData {} 640 | 641 | impl ChannelData { 642 | /// Get a channel property that is stored as a string. 643 | fn get_property_as_string( 644 | server_id: ServerId, id: ChannelId, property: ChannelProperties, 645 | ) -> Result { 646 | unsafe { 647 | let mut name: *mut c_char = std::ptr::null_mut(); 648 | let res: Error = transmute((TS3_FUNCTIONS 649 | .read() 650 | .unwrap() 651 | .as_ref() 652 | .expect("Functions should be loaded") 653 | .get_channel_variable_as_string)( 654 | server_id.0, id.0, property as usize, &mut name 655 | )); 656 | match res { 657 | Error::Ok => Ok(to_string!(name)), 658 | _ => Err(res), 659 | } 660 | } 661 | } 662 | 663 | /// Get a channel property that is stored as an int. 664 | fn get_property_as_int( 665 | server_id: ServerId, id: ChannelId, property: ChannelProperties, 666 | ) -> Result { 667 | unsafe { 668 | let mut number: c_int = 0; 669 | let res: Error = transmute((TS3_FUNCTIONS 670 | .read() 671 | .unwrap() 672 | .as_ref() 673 | .expect("Functions should be loaded") 674 | .get_channel_variable_as_int)( 675 | server_id.0, id.0, property as usize, &mut number 676 | )); 677 | match res { 678 | Error::Ok => Ok(number as i32), 679 | _ => Err(res), 680 | } 681 | } 682 | } 683 | 684 | /// Get a channel property that is stored as an uint64. 685 | fn get_property_as_uint64( 686 | server_id: ServerId, id: ChannelId, property: ChannelProperties, 687 | ) -> Result { 688 | unsafe { 689 | let mut number: u64 = 0; 690 | let res: Error = transmute((TS3_FUNCTIONS 691 | .read() 692 | .unwrap() 693 | .as_ref() 694 | .expect("Functions should be loaded") 695 | .get_channel_variable_as_uint64)( 696 | server_id.0, id.0, property as usize, &mut number 697 | )); 698 | match res { 699 | Error::Ok => Ok(number as i32), 700 | _ => Err(res), 701 | } 702 | } 703 | } 704 | 705 | /// Ask the TeamSpeak api about the parent channel id of a channel. 706 | fn query_parent_channel_id(server_id: ServerId, id: ChannelId) -> Result { 707 | unsafe { 708 | let mut number: u64 = 0; 709 | let res: Error = 710 | transmute((TS3_FUNCTIONS 711 | .read() 712 | .unwrap() 713 | .as_ref() 714 | .expect("Functions should be loaded") 715 | .get_parent_channel_of_channel)(server_id.0, id.0, &mut number)); 716 | match res { 717 | Error::Ok => Ok(ChannelId(number)), 718 | _ => Err(res), 719 | } 720 | } 721 | } 722 | } 723 | 724 | impl<'a> Channel<'a> { 725 | fn new(api: &'a TsApi, data: &'a ChannelData) -> Channel<'a> { 726 | Channel { api, data: Ok(data) } 727 | } 728 | 729 | fn new_owned(api: &'a TsApi, server_id: ServerId, channel_id: ChannelId) -> Channel<'a> { 730 | Channel { api, data: Err((server_id, channel_id)) } 731 | } 732 | 733 | fn get_server_id(&self) -> ServerId { 734 | match self.data { 735 | Ok(data) => data.get_server_id(), 736 | Err((server_id, _)) => server_id, 737 | } 738 | } 739 | 740 | pub fn get_id(&self) -> ChannelId { 741 | match self.data { 742 | Ok(data) => data.get_id(), 743 | Err((_, channel_id)) => channel_id, 744 | } 745 | } 746 | 747 | /// Get the server of this channel. 748 | pub fn get_server(&self) -> Server<'a> { 749 | self.api.get_server_unwrap(self.get_server_id()) 750 | } 751 | 752 | pub fn get_parent_channel(&self) -> Result>, Error> { 753 | match self.data { 754 | Ok(data) => data.get_parent_channel_id().map(|parent_channel_id| { 755 | if parent_channel_id.0 == 0 { 756 | None 757 | } else { 758 | Some(self.get_server().get_channel_unwrap(parent_channel_id)) 759 | } 760 | }), 761 | Err(_) => Err(Error::Ok), 762 | } 763 | } 764 | 765 | /// Send a message to this channel chat. 766 | pub fn send_message>(&self, message: S) -> Result<(), Error> { 767 | unsafe { 768 | let text = to_cstring!(message.as_ref()); 769 | let res: Error = transmute((TS3_FUNCTIONS 770 | .read() 771 | .unwrap() 772 | .as_ref() 773 | .expect("Functions should be loaded") 774 | .request_send_channel_text_msg)( 775 | self.data.unwrap().server_id.0, 776 | text.as_ptr(), 777 | self.data.unwrap().id.0, 778 | std::ptr::null(), 779 | )); 780 | match res { 781 | Error::Ok => Ok(()), 782 | _ => Err(res), 783 | } 784 | } 785 | } 786 | } 787 | 788 | // ********** Connection ********** 789 | #[derive(Clone)] 790 | pub struct Connection<'a> { 791 | api: &'a TsApi, 792 | data: Result<&'a ConnectionData, (ServerId, ConnectionId)>, 793 | } 794 | 795 | impl<'a, 'b> PartialEq> for Connection<'a> { 796 | fn eq(&self, other: &Connection<'b>) -> bool { 797 | self.get_server_id() == other.get_server_id() && self.get_id() == other.get_id() 798 | } 799 | } 800 | impl<'a> Eq for Connection<'a> {} 801 | impl<'a> fmt::Debug for Connection<'a> { 802 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 803 | write!(f, "Connection({})", self.get_id().0) 804 | } 805 | } 806 | 807 | impl PartialEq for ConnectionData { 808 | fn eq(&self, other: &ConnectionData) -> bool { 809 | self.server_id == other.server_id && self.id == other.id 810 | } 811 | } 812 | impl Eq for ConnectionData {} 813 | 814 | impl ConnectionData { 815 | /// Get a connection property that is stored as a string. 816 | fn get_connection_property_as_string( 817 | server_id: ServerId, id: ConnectionId, property: ConnectionProperties, 818 | ) -> Result { 819 | unsafe { 820 | let mut name: *mut c_char = std::ptr::null_mut(); 821 | let res: Error = transmute((TS3_FUNCTIONS 822 | .read() 823 | .unwrap() 824 | .as_ref() 825 | .expect("Functions should be loaded") 826 | .get_connection_variable_as_string)( 827 | server_id.0, id.0, property as usize, &mut name 828 | )); 829 | match res { 830 | Error::Ok => Ok(to_string!(name)), 831 | _ => Err(res), 832 | } 833 | } 834 | } 835 | 836 | /// Get a connection property that is stored as a uint64. 837 | fn get_connection_property_as_uint64( 838 | server_id: ServerId, id: ConnectionId, property: ConnectionProperties, 839 | ) -> Result { 840 | unsafe { 841 | let mut number: u64 = 0; 842 | let res: Error = transmute((TS3_FUNCTIONS 843 | .read() 844 | .unwrap() 845 | .as_ref() 846 | .expect("Functions should be loaded") 847 | .get_connection_variable_as_uint64)( 848 | server_id.0, id.0, property as usize, &mut number 849 | )); 850 | match res { 851 | Error::Ok => Ok(number), 852 | _ => Err(res), 853 | } 854 | } 855 | } 856 | 857 | /// Get a connection property that is stored as a double. 858 | fn get_connection_property_as_double( 859 | server_id: ServerId, id: ConnectionId, property: ConnectionProperties, 860 | ) -> Result { 861 | unsafe { 862 | let mut number: f64 = 0.0; 863 | let res: Error = transmute((TS3_FUNCTIONS 864 | .read() 865 | .unwrap() 866 | .as_ref() 867 | .expect("Functions should be loaded") 868 | .get_connection_variable_as_double)( 869 | server_id.0, id.0, property as usize, &mut number 870 | )); 871 | match res { 872 | Error::Ok => Ok(number), 873 | _ => Err(res), 874 | } 875 | } 876 | } 877 | 878 | /// Get a client property that is stored as a string. 879 | fn get_client_property_as_string( 880 | server_id: ServerId, id: ConnectionId, property: ClientProperties, 881 | ) -> Result { 882 | unsafe { 883 | let mut name: *mut c_char = std::ptr::null_mut(); 884 | let res: Error = transmute((TS3_FUNCTIONS 885 | .read() 886 | .unwrap() 887 | .as_ref() 888 | .expect("Functions should be loaded") 889 | .get_client_variable_as_string)( 890 | server_id.0, id.0, property as usize, &mut name 891 | )); 892 | match res { 893 | Error::Ok => Ok(to_string!(name)), 894 | _ => Err(res), 895 | } 896 | } 897 | } 898 | 899 | /// Get a client property that is stored as an int. 900 | fn get_client_property_as_int( 901 | server_id: ServerId, id: ConnectionId, property: ClientProperties, 902 | ) -> Result { 903 | unsafe { 904 | let mut number: c_int = 0; 905 | let res: Error = transmute((TS3_FUNCTIONS 906 | .read() 907 | .unwrap() 908 | .as_ref() 909 | .expect("Functions should be loaded") 910 | .get_client_variable_as_int)( 911 | server_id.0, id.0, property as usize, &mut number 912 | )); 913 | match res { 914 | Error::Ok => Ok(number), 915 | _ => Err(res), 916 | } 917 | } 918 | } 919 | 920 | /// Ask the TeamSpeak api about the current channel id of a connection. 921 | fn query_channel_id(server_id: ServerId, id: ConnectionId) -> Result { 922 | unsafe { 923 | let mut number: u64 = 0; 924 | let res: Error = transmute((TS3_FUNCTIONS 925 | .read() 926 | .unwrap() 927 | .as_ref() 928 | .expect("Functions should be loaded") 929 | .get_channel_of_client)(server_id.0, id.0, &mut number)); 930 | match res { 931 | Error::Ok => Ok(ChannelId(number)), 932 | _ => Err(res), 933 | } 934 | } 935 | } 936 | 937 | /// Ask the TeamSpeak api, if the specified connection is currently whispering to our own 938 | /// client. 939 | fn query_whispering(server_id: ServerId, id: ConnectionId) -> Result { 940 | unsafe { 941 | let mut number: c_int = 0; 942 | let res: Error = transmute((TS3_FUNCTIONS 943 | .read() 944 | .unwrap() 945 | .as_ref() 946 | .expect("Functions should be loaded") 947 | .is_whispering)(server_id.0, id.0, &mut number)); 948 | match res { 949 | Error::Ok => Ok(number != 0), 950 | _ => Err(res), 951 | } 952 | } 953 | } 954 | } 955 | 956 | impl<'a> Connection<'a> { 957 | fn new(api: &'a TsApi, data: &'a ConnectionData) -> Connection<'a> { 958 | Connection { api, data: Ok(data) } 959 | } 960 | 961 | fn new_err(api: &'a TsApi, server_id: ServerId, connection_id: ConnectionId) -> Connection<'a> { 962 | Connection { api, data: Err((server_id, connection_id)) } 963 | } 964 | 965 | fn get_server_id(&self) -> ServerId { 966 | match self.data { 967 | Ok(data) => data.get_server_id(), 968 | Err((server_id, _)) => server_id, 969 | } 970 | } 971 | 972 | pub fn get_id(&self) -> ConnectionId { 973 | match self.data { 974 | Ok(data) => data.get_id(), 975 | Err((_, connection_id)) => connection_id, 976 | } 977 | } 978 | 979 | /// Get the server of this connection. 980 | pub fn get_server(&self) -> Server<'a> { 981 | self.api.get_server_unwrap(self.get_server_id()) 982 | } 983 | 984 | /// Get the channel of this connection. 985 | pub fn get_channel(&self) -> Result, Error> { 986 | match self.data { 987 | Ok(data) => data.get_channel_id().map(|c| self.get_server().get_channel_unwrap(c)), 988 | Err(_) => Err(Error::Ok), 989 | } 990 | } 991 | 992 | pub fn get_channel_group_inherited_channel(&self) -> Result, Error> { 993 | match self.data { 994 | Ok(data) => data 995 | .get_channel_group_inherited_channel_id() 996 | .map(|c| self.get_server().get_channel_unwrap(c)), 997 | Err(_) => Err(Error::Ok), 998 | } 999 | } 1000 | 1001 | /*/// The connection properties that are only available for our own client. 1002 | pub fn get_own_data(&self) -> Option<&OwnConnectionData> { 1003 | self.data.ok().and_then(|data| data.own_data.as_ref()) 1004 | } 1005 | 1006 | /// The connection properties that are only available for server queries. 1007 | pub fn get_serverquery_data(&self) -> Option<&ServerqueryConnectionData> { 1008 | self.data.ok().and_then(|data| data.serverquery_data.as_ref()) 1009 | } 1010 | 1011 | /// The connection properties that are only available on request. 1012 | pub fn get_optional_data(&self) -> Option<&OptionalConnectionData> { 1013 | self.data.ok().map(|data| &data.optional_data) 1014 | }*/ 1015 | 1016 | /// Send a private message to this connection. 1017 | pub fn send_message>(&self, message: S) -> Result<(), Error> { 1018 | unsafe { 1019 | let text = to_cstring!(message.as_ref()); 1020 | let res: Error = transmute((TS3_FUNCTIONS 1021 | .read() 1022 | .unwrap() 1023 | .as_ref() 1024 | .expect("Functions should be loaded") 1025 | .request_send_private_text_msg)( 1026 | self.data.unwrap().server_id.0, 1027 | text.as_ptr(), 1028 | self.data.unwrap().id.0, 1029 | std::ptr::null(), 1030 | )); 1031 | match res { 1032 | Error::Ok => Ok(()), 1033 | _ => Err(res), 1034 | } 1035 | } 1036 | } 1037 | } 1038 | 1039 | pub struct TsApiLock { 1040 | guard: MutexGuard<'static, (Option<(TsApi, Box)>, Option)>, 1041 | } 1042 | impl Deref for TsApiLock { 1043 | type Target = TsApi; 1044 | fn deref(&self) -> &Self::Target { 1045 | &self.guard.0.as_ref().unwrap().0 1046 | } 1047 | } 1048 | impl DerefMut for TsApiLock { 1049 | fn deref_mut(&mut self) -> &mut Self::Target { 1050 | &mut self.guard.0.as_mut().unwrap().0 1051 | } 1052 | } 1053 | 1054 | pub struct PluginLock { 1055 | guard: MutexGuard<'static, (Option<(TsApi, Box)>, Option)>, 1056 | } 1057 | impl Deref for PluginLock { 1058 | type Target = dyn Plugin; 1059 | fn deref(&self) -> &Self::Target { 1060 | &*self.guard.0.as_ref().unwrap().1 1061 | } 1062 | } 1063 | impl DerefMut for PluginLock { 1064 | fn deref_mut(&mut self) -> &mut Self::Target { 1065 | &mut *self.guard.0.as_mut().unwrap().1 1066 | } 1067 | } 1068 | 1069 | // ********** TsApi ********** 1070 | /// The main struct that contains all permanently save data. 1071 | pub struct TsApi { 1072 | /// All known servers. 1073 | servers: Map, 1074 | /// The plugin id from TeamSpeak. 1075 | plugin_id: String, 1076 | } 1077 | 1078 | // Don't provide a default Implementation because we don't want the TsApi 1079 | // to be publicly constructable. 1080 | impl TsApi { 1081 | /// Create a new TsApi instance without loading anything. 1082 | fn new(plugin_id: String) -> TsApi { 1083 | TsApi { servers: Map::new(), plugin_id: plugin_id } 1084 | } 1085 | 1086 | /// Load all currently connected server and their data. 1087 | /// This should normally be executed after `new()`. 1088 | fn load(&mut self) -> Result<(), Error> { 1089 | // Query available connections 1090 | let mut result: *mut u64 = std::ptr::null_mut(); 1091 | let res: Error = unsafe { 1092 | transmute((TS3_FUNCTIONS 1093 | .read() 1094 | .unwrap() 1095 | .as_ref() 1096 | .expect("Functions should be loaded") 1097 | .get_server_connection_handler_list)(&mut result)) 1098 | }; 1099 | match res { 1100 | Error::Ok => unsafe { 1101 | let mut counter = 0; 1102 | while *result.offset(counter) != 0 { 1103 | // Test if we have a connection to this server. 1104 | // We get open tabs, even if they are disconnected. 1105 | let mut status: c_int = 0; 1106 | let res: Error = transmute((TS3_FUNCTIONS 1107 | .read() 1108 | .unwrap() 1109 | .as_ref() 1110 | .expect("Functions should be loaded") 1111 | .get_connection_status)( 1112 | *result.offset(counter), &mut status 1113 | )); 1114 | if res == Error::Ok 1115 | && transmute::(status) != ConnectStatus::Disconnected 1116 | { 1117 | self.add_server(ServerId(*result.offset(counter))); 1118 | } 1119 | counter += 1; 1120 | } 1121 | }, 1122 | _ => return Err(res), 1123 | } 1124 | Ok(()) 1125 | } 1126 | 1127 | /// Lock the global `TsApi` object. This will be `None` when the plugin is 1128 | /// constructed. 1129 | pub fn lock_api() -> Option { 1130 | let guard = ts3interface::DATA.lock().unwrap(); 1131 | if guard.0.is_none() { None } else { Some(TsApiLock { guard }) } 1132 | } 1133 | 1134 | /// Lock the global `Plugin` object. 1135 | pub fn lock_plugin() -> Option { 1136 | let guard = ts3interface::DATA.lock().unwrap(); 1137 | if guard.0.is_none() { None } else { Some(PluginLock { guard }) } 1138 | } 1139 | 1140 | /// Please try to use the member method `log_message` instead of this static method. 1141 | pub fn static_log_message, S2: AsRef>( 1142 | message: S1, channel: S2, severity: LogLevel, 1143 | ) -> Result<(), Error> { 1144 | unsafe { 1145 | let res: Error = transmute((TS3_FUNCTIONS 1146 | .read() 1147 | .unwrap() 1148 | .as_ref() 1149 | .expect("Functions should be loaded") 1150 | .log_message)( 1151 | to_cstring!(message.as_ref()).as_ptr(), 1152 | severity, 1153 | to_cstring!(channel.as_ref()).as_ptr(), 1154 | 0, 1155 | )); 1156 | match res { 1157 | Error::Ok => Ok(()), 1158 | _ => Err(res), 1159 | } 1160 | } 1161 | } 1162 | 1163 | /// Please try to use the member method `log_or_print` instead of this static method. 1164 | pub fn static_log_or_print, S2: AsRef>( 1165 | message: S1, channel: S2, severity: LogLevel, 1166 | ) { 1167 | if let Err(error) = TsApi::static_log_message(message.as_ref(), channel.as_ref(), severity) 1168 | { 1169 | println!( 1170 | "Error {:?} while printing '{}' to '{}' ({:?})", 1171 | error, 1172 | message.as_ref(), 1173 | channel.as_ref(), 1174 | severity 1175 | ); 1176 | } 1177 | } 1178 | 1179 | /// Please try to use the member method `get_error_message` instead of this static method. 1180 | pub fn static_get_error_message(error: Error) -> Result { 1181 | unsafe { 1182 | let mut message: *mut c_char = std::ptr::null_mut(); 1183 | let res: Error = transmute((TS3_FUNCTIONS 1184 | .read() 1185 | .unwrap() 1186 | .as_ref() 1187 | .expect("Functions should be loaded") 1188 | .get_error_message)(error as u32, &mut message)); 1189 | match res { 1190 | Error::Ok => Ok(to_string!(message)), 1191 | _ => Err(res), 1192 | } 1193 | } 1194 | } 1195 | 1196 | // ********** Private Interface ********** 1197 | 1198 | /// Add the server with the specified id to the server list. 1199 | /// The currently available data of this server will be stored. 1200 | fn add_server(&mut self, server_id: ServerId) -> &mut ServerData { 1201 | self.servers.insert(server_id, ServerData::new(server_id)); 1202 | let server = self.servers.get_mut(&server_id).unwrap(); 1203 | server.update(); 1204 | server 1205 | } 1206 | 1207 | /// Returns true if a server was removed 1208 | fn remove_server(&mut self, server_id: ServerId) -> Option { 1209 | self.servers.remove(&server_id) 1210 | } 1211 | 1212 | /// Get the plugin id assigned by TeamSpeak. 1213 | pub fn get_plugin_id(&self) -> &str { 1214 | &self.plugin_id 1215 | } 1216 | 1217 | /// Update the data of a connection with the data from the same connection 1218 | /// as an invoker if possible. 1219 | fn try_update_invoker(&mut self, server_id: ServerId, invoker: &InvokerData) { 1220 | if let Some(server) = self.get_mut_server(server_id) { 1221 | if let Some(connection) = server.get_mut_connection(invoker.get_id()) { 1222 | if connection.get_uid() != Ok(invoker.get_uid()) { 1223 | connection.uid = Ok(invoker.get_uid().clone()); 1224 | } 1225 | if connection.get_name() != Ok(invoker.get_name()) { 1226 | connection.name = Ok(invoker.get_name().clone()) 1227 | } 1228 | } 1229 | } 1230 | } 1231 | 1232 | /// A reusable function that takes a TeamSpeak3 api function like 1233 | /// `get_plugin_path` and returns the path. 1234 | /// The buffer that holds the path will be automatically enlarged up to a 1235 | /// limit. 1236 | /// The function that is colled takes a pointer to a string buffer that will 1237 | /// be filled and the max lenght of the buffer. 1238 | fn get_path(fun: F) -> String { 1239 | const START_SIZE: usize = 512; 1240 | const MAX_SIZE: usize = 100_000; 1241 | let mut size = START_SIZE; 1242 | loop { 1243 | let mut buf = vec![0 as u8; size]; 1244 | fun(buf.as_mut_ptr() as *mut c_char, size - 1); 1245 | // Test if the allocated buffer was long enough 1246 | if buf[size - 3] != 0 { 1247 | size *= 2; 1248 | if size > MAX_SIZE { 1249 | return String::new(); 1250 | } 1251 | } else { 1252 | // Be sure that the string is terminated 1253 | buf[size - 1] = 0; 1254 | let s = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }; 1255 | let result = s.to_string_lossy(); 1256 | return result.into_owned(); 1257 | } 1258 | } 1259 | } 1260 | 1261 | /// Get the mutable server that has the specified id, returns `None` if there is no 1262 | /// such server. 1263 | fn get_mut_server(&mut self, server_id: ServerId) -> Option<&mut ServerData> { 1264 | self.servers.get_mut(&server_id) 1265 | } 1266 | 1267 | fn get_server_unwrap<'a>(&'a self, server_id: ServerId) -> Server<'a> { 1268 | self.servers.get(&server_id).map(|s| Server::<'a>::new(&self, s)).unwrap_or_else(|| { 1269 | // Ignore here, there are too many messages when we are not yet 1270 | // fully connected (or already disconnected), but sound is sent. 1271 | // self.log_or_print(format!("Can't find server {:?}\n{:?}", 1272 | // server_id, backtrace::Backtrace::new()), "rust-ts3plugin", ::LogLevel::Warning); 1273 | Server::new_err(&self, server_id) 1274 | }) 1275 | } 1276 | 1277 | // ********** Public Interface ********** 1278 | 1279 | /// Get the raw TeamSpeak api functions. 1280 | /// These functions can be used to invoke actions that are not yet 1281 | /// implemented by this library. You should file a bug report or make a pull 1282 | /// request if you need to use this function. 1283 | //pub unsafe fn get_raw_api() -> &'static Ts3Functions { unsafe { TS3_FUNCTIONS.lock().unwrap().as_ref().unwrap() }} 1284 | 1285 | /// Get all servers to which this client is currently connected. 1286 | pub fn get_servers<'a>(&'a self) -> Vec> { 1287 | self.servers.values().map(|s| Server::new(&self, &s)).collect() 1288 | } 1289 | 1290 | /// Log a message using the TeamSpeak logging API. 1291 | pub fn log_message, S2: AsRef>( 1292 | &self, message: S1, channel: S2, severity: LogLevel, 1293 | ) -> Result<(), Error> { 1294 | TsApi::static_log_message(message, channel, severity) 1295 | } 1296 | 1297 | /// Log a message using the TeamSpeak logging API. 1298 | /// If that fails, print the message to stdout. 1299 | pub fn log_or_print, S2: AsRef>( 1300 | &self, message: S1, channel: S2, severity: LogLevel, 1301 | ) { 1302 | TsApi::static_log_or_print(message, channel, severity) 1303 | } 1304 | 1305 | /// Get the server that has the specified id, returns `None` if there is no 1306 | /// such server. 1307 | pub fn get_server(&'_ self, server_id: ServerId) -> Option> { 1308 | self.servers.get(&server_id).map(|s| Server::new(&self, s)) 1309 | } 1310 | 1311 | pub fn get_permission(&self, _permission_id: PermissionId) -> Option<&Permission> { 1312 | // TODO 1313 | Some(&Permission {}) 1314 | } 1315 | 1316 | /// Print a message to the currently selected tab. This is only 1317 | /// visible in the window of this client and will not be sent to the server. 1318 | pub fn print_message>(&self, message: S) { 1319 | let text = to_cstring!(message.as_ref()); 1320 | (TS3_FUNCTIONS 1321 | .read() 1322 | .unwrap() 1323 | .as_ref() 1324 | .expect("Functions should be loaded") 1325 | .print_message_to_current_tab)(text.as_ptr()); 1326 | } 1327 | 1328 | /// Get the application path of the TeamSpeak executable. 1329 | pub fn get_app_path(&self) -> String { 1330 | TsApi::get_path(|p, l| { 1331 | (TS3_FUNCTIONS 1332 | .read() 1333 | .unwrap() 1334 | .as_ref() 1335 | .expect("Functions should be loaded") 1336 | .get_app_path)(p, l) 1337 | }) 1338 | } 1339 | 1340 | /// Get the resource path of TeamSpeak. 1341 | pub fn get_resources_path(&self) -> String { 1342 | TsApi::get_path(|p, l| { 1343 | (TS3_FUNCTIONS 1344 | .read() 1345 | .unwrap() 1346 | .as_ref() 1347 | .expect("Functions should be loaded") 1348 | .get_resources_path)(p, l) 1349 | }) 1350 | } 1351 | 1352 | /// Get the path, where configuration files are stored. 1353 | /// This is e.g. `~/.ts3client` on linux or `%AppData%/TS3Client` on Windows. 1354 | pub fn get_config_path(&self) -> String { 1355 | TsApi::get_path(|p, l| { 1356 | (TS3_FUNCTIONS 1357 | .read() 1358 | .unwrap() 1359 | .as_ref() 1360 | .expect("Functions should be loaded") 1361 | .get_config_path)(p, l) 1362 | }) 1363 | } 1364 | 1365 | /// Get the path where TeamSpeak plugins are stored. 1366 | pub fn get_plugin_path(&self) -> String { 1367 | TsApi::get_path(|p, l| { 1368 | (TS3_FUNCTIONS 1369 | .read() 1370 | .unwrap() 1371 | .as_ref() 1372 | .expect("Functions should be loaded") 1373 | .get_plugin_path)(p, l, to_cstring!(self.plugin_id.as_str()).as_ptr()) 1374 | }) 1375 | } 1376 | } 1377 | -------------------------------------------------------------------------------- /src/ts3interface.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::mem::transmute; 3 | use std::os::raw::{c_char, c_int, c_short, c_uint}; 4 | use std::slice; 5 | use std::sync::Mutex; 6 | 7 | use ts3plugin_sys::public_definitions::*; 8 | use ts3plugin_sys::ts3functions::Ts3Functions; 9 | 10 | use crate::plugin::Plugin; 11 | 12 | lazy_static! { 13 | /// The api, plugin and plugin id 14 | pub(crate) static ref DATA: Mutex<(Option<(crate::TsApi, Box)>, Option)> = 15 | Mutex::new((None, None)); 16 | } 17 | 18 | /// Get the current file without the preceding path 19 | macro_rules! filename { 20 | () => {{ 21 | let f = file!(); 22 | &f[f.rfind(|c| c == '/' || c == '\\').map_or(0, |i| i + 1)..] 23 | }}; 24 | } 25 | 26 | /// Log an error with a description and the current line and file 27 | macro_rules! error { 28 | ($api: ident, $description: expr_2021, $error: expr_2021) => { 29 | $api.log_or_print( 30 | format!("Error {:?} ({}) in in {}:L{}", $error, $description, filename!(), line!()), 31 | "rust-ts3plugin", 32 | crate::LogLevel::Error, 33 | ); 34 | }; 35 | } 36 | 37 | /// Initialises the internal data. 38 | /// T is the plugin type. 39 | /// This function will be called from `create_plugin!`, please don't call it manually. 40 | #[doc(hidden)] 41 | pub unsafe fn private_init() -> Result<(), crate::InitError> { 42 | // Create the TsApi 43 | let plugin_id = { 44 | let mut data = DATA.lock().unwrap(); 45 | data.1.take().unwrap() 46 | }; 47 | let mut api = crate::TsApi::new(plugin_id); 48 | if let Err(error) = api.load() { 49 | error!(api, "Can't create TsApi", error); 50 | return Err(crate::InitError::Failure); 51 | } 52 | 53 | // Create the plugin 54 | match T::new(&mut api) { 55 | Ok(plugin) => { 56 | let mut data = DATA.lock().unwrap(); 57 | data.0 = Some((api, plugin)); 58 | Ok(()) 59 | } 60 | Err(error) => Err(error), 61 | } 62 | } 63 | 64 | // ************************** Interface for TeamSpeak ************************** 65 | 66 | #[allow(non_snake_case)] 67 | #[unsafe(no_mangle)] 68 | #[doc(hidden)] 69 | pub extern "C" fn ts3plugin_apiVersion() -> c_int { 26 } 70 | 71 | #[allow(non_snake_case)] 72 | #[unsafe(no_mangle)] 73 | #[doc(hidden)] 74 | pub unsafe extern "C" fn ts3plugin_setFunctionPointers(funs: Ts3Functions) { 75 | crate::TS3_FUNCTIONS.write().unwrap().replace(funs); 76 | } 77 | 78 | /// Called when the plugin should be unloaded. 79 | #[unsafe(no_mangle)] 80 | #[doc(hidden)] 81 | pub unsafe extern "C" fn ts3plugin_shutdown() { 82 | let mut data = DATA.lock().unwrap(); 83 | if let Some(data) = data.0.as_mut() { 84 | let api = &mut data.0; 85 | let plugin = &mut data.1; 86 | plugin.shutdown(api); 87 | } 88 | // Drop the api and the plugin 89 | *data = (None, None); 90 | } 91 | 92 | /// Called when settings is opened and QtThread or NewThread is set as configurable offer. 93 | /// https://github.com/teamspeak/ts3client-pluginsdk/blob/4aa90a53aa150cbf81e13bc97e68c0431b26499f/src/plugin.h#L32 94 | #[allow(non_snake_case)] 95 | #[unsafe(no_mangle)] 96 | #[doc(hidden)] 97 | pub extern "C" fn ts3plugin_configure(_handle: *mut std::os::raw::c_void, _qParentWidget: *mut std::os::raw::c_void) { 98 | // The handle parameter and qParentWidget parameter is not really needed to show a settings window using egui or whatever framework is chosen. 99 | // Maybe expose the pointers anyway later if needed. 100 | let mut data = DATA.lock().unwrap(); 101 | let data = data.0.as_mut().unwrap(); 102 | let api = &mut data.0; 103 | let plugin = &mut data.1; 104 | plugin.configure(api); 105 | } 106 | 107 | #[allow(non_snake_case)] 108 | #[unsafe(no_mangle)] 109 | #[doc(hidden)] 110 | pub unsafe extern "C" fn ts3plugin_registerPluginID(plugin_id: *const c_char) { unsafe { 111 | let mut data = DATA.lock().unwrap(); 112 | data.1 = Some(to_string!(plugin_id)); 113 | }} 114 | 115 | #[allow(non_snake_case)] 116 | #[unsafe(no_mangle)] 117 | #[doc(hidden)] 118 | pub unsafe extern "C" fn ts3plugin_onConnectStatusChangeEvent( 119 | server_id: u64, status: c_int, error: c_uint, 120 | ) { unsafe { 121 | let server_id = crate::ServerId(server_id); 122 | let status = transmute(status); 123 | let error = transmute(error); 124 | let mut data = DATA.lock().unwrap(); 125 | let data = data.0.as_mut().unwrap(); 126 | let api = &mut data.0; 127 | let plugin = &mut data.1; 128 | // Add the server if we can get information about it 129 | // and don't have that server cached already. 130 | if status != ConnectStatus::Connecting && api.get_server(server_id).is_none() { 131 | api.add_server(server_id); 132 | } 133 | { 134 | let server = api.get_server_unwrap(server_id); 135 | // Execute plugin callback 136 | plugin.connect_status_change(api, &server, status, error); 137 | } 138 | // Remove server if we disconnected 139 | if status == ConnectStatus::Disconnected { 140 | api.remove_server(server_id); 141 | } 142 | }} 143 | 144 | #[allow(non_snake_case)] 145 | #[unsafe(no_mangle)] 146 | #[doc(hidden)] 147 | pub unsafe extern "C" fn ts3plugin_onServerStopEvent(server_id: u64, message: *const c_char) { unsafe { 148 | let server_id = crate::ServerId(server_id); 149 | let message = to_string!(message); 150 | let mut data = DATA.lock().unwrap(); 151 | let data = data.0.as_mut().unwrap(); 152 | let api = &mut data.0; 153 | let plugin = &mut data.1; 154 | let server = api.get_server_unwrap(server_id); 155 | plugin.server_stop(api, &server, message); 156 | }} 157 | 158 | #[allow(non_snake_case)] 159 | #[unsafe(no_mangle)] 160 | #[doc(hidden)] 161 | pub unsafe extern "C" fn ts3plugin_onServerErrorEvent( 162 | server_id: u64, message: *const c_char, error: c_uint, return_code: *const c_char, 163 | extra_message: *const c_char, 164 | ) -> c_int { unsafe { 165 | let server_id = crate::ServerId(server_id); 166 | let message = to_string!(message); 167 | let error = transmute(error); 168 | let return_code = to_string!(return_code); 169 | let extra_message = to_string!(extra_message); 170 | let mut data = DATA.lock().unwrap(); 171 | let data = data.0.as_mut().unwrap(); 172 | let api = &mut data.0; 173 | let plugin = &mut data.1; 174 | let server = api.get_server_unwrap(server_id); 175 | let b = plugin.server_error(api, &server, error, message, return_code, extra_message); 176 | if b { 1 } else { 0 } 177 | }} 178 | 179 | #[allow(non_snake_case)] 180 | #[unsafe(no_mangle)] 181 | #[doc(hidden)] 182 | pub unsafe extern "C" fn ts3plugin_onServerEditedEvent( 183 | server_id: u64, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 184 | ) { unsafe { 185 | let server_id = crate::ServerId(server_id); 186 | let invoker = if invoker_id == 0 { 187 | None 188 | } else { 189 | Some(crate::InvokerData::new( 190 | crate::ConnectionId(invoker_id), 191 | to_string!(invoker_uid), 192 | to_string!(invoker_name), 193 | )) 194 | }; 195 | let mut data = DATA.lock().unwrap(); 196 | let data = data.0.as_mut().unwrap(); 197 | let api = &mut data.0; 198 | let plugin = &mut data.1; 199 | if let Some(ref invoker) = invoker { 200 | api.try_update_invoker(server_id, invoker); 201 | } 202 | if let Some(ref mut server) = api.get_mut_server(server_id) { 203 | server.update(); 204 | } 205 | let server = api.get_server_unwrap(server_id); 206 | plugin.server_edited(api, &server, invoker.map(|i| crate::Invoker::new(server.clone(), i)).as_ref()); 207 | }} 208 | 209 | #[allow(non_snake_case)] 210 | #[unsafe(no_mangle)] 211 | #[doc(hidden)] 212 | pub unsafe extern "C" fn ts3plugin_onServerConnectionInfoEvent(server_id: u64) { 213 | let server_id = crate::ServerId(server_id); 214 | let mut data = DATA.lock().unwrap(); 215 | let data = data.0.as_mut().unwrap(); 216 | let api = &mut data.0; 217 | let plugin = &mut data.1; 218 | let server = api.get_server_unwrap(server_id); 219 | plugin.server_connection_info(api, &server); 220 | } 221 | 222 | #[allow(non_snake_case)] 223 | #[unsafe(no_mangle)] 224 | #[doc(hidden)] 225 | pub unsafe extern "C" fn ts3plugin_onConnectionInfoEvent(server_id: u64, connection_id: u16) { 226 | let server_id = crate::ServerId(server_id); 227 | let connection_id = crate::ConnectionId(connection_id); 228 | let mut data = DATA.lock().unwrap(); 229 | let data = data.0.as_mut().unwrap(); 230 | let api = &mut data.0; 231 | let plugin = &mut data.1; 232 | let server = api.get_server_unwrap(server_id); 233 | let connection = server.get_connection_unwrap(connection_id); 234 | plugin.connection_info(api, &server, &connection); 235 | } 236 | 237 | #[allow(non_snake_case)] 238 | #[unsafe(no_mangle)] 239 | #[doc(hidden)] 240 | pub unsafe extern "C" fn ts3plugin_onUpdateClientEvent( 241 | server_id: u64, connection_id: u16, invoker_id: u16, invoker_name: *const c_char, 242 | invoker_uid: *const c_char, 243 | ) { unsafe { 244 | let server_id = crate::ServerId(server_id); 245 | let connection_id = crate::ConnectionId(connection_id); 246 | let invoker_id = crate::ConnectionId(invoker_id); 247 | let invoker_name = to_string!(invoker_name); 248 | let invoker_uid = to_string!(invoker_uid); 249 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 250 | let mut data = DATA.lock().unwrap(); 251 | let data = data.0.as_mut().unwrap(); 252 | let api = &mut data.0; 253 | let plugin = &mut data.1; 254 | api.try_update_invoker(server_id, &invoker); 255 | 256 | // Save the old connection 257 | let old_connection; 258 | { 259 | let server = api.get_mut_server(server_id).unwrap(); 260 | // Try to get the old channel 261 | old_connection = server 262 | .remove_connection(connection_id) 263 | .unwrap_or(crate::ConnectionData::new(server_id, connection_id)); 264 | let connection = server.add_connection(connection_id); 265 | // Copy optional data from old connection 266 | connection.update_from(&old_connection); 267 | } 268 | let server = api.get_server_unwrap(server_id); 269 | let connection = server.get_connection_unwrap(connection_id); 270 | let old_connection = crate::Connection::new(api, &old_connection); 271 | plugin.connection_properties_changed( 272 | api, 273 | &server, 274 | &connection, 275 | &old_connection, 276 | crate::get_connection_changes(old_connection.properties(), connection.properties()), 277 | &crate::Invoker::new(server.clone(), invoker), 278 | ); 279 | }} 280 | 281 | #[allow(non_snake_case)] 282 | #[unsafe(no_mangle)] 283 | #[doc(hidden)] 284 | pub unsafe extern "C" fn ts3plugin_onClientMoveEvent( 285 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 286 | visibility: c_int, move_message: *const c_char, 287 | ) { unsafe { 288 | let server_id = crate::ServerId(server_id); 289 | let connection_id = crate::ConnectionId(connection_id); 290 | let old_channel_id = crate::ChannelId(old_channel_id); 291 | let new_channel_id = crate::ChannelId(new_channel_id); 292 | let visibility = transmute(visibility); 293 | let move_message = to_string!(move_message); 294 | let mut data = DATA.lock().unwrap(); 295 | let data = data.0.as_mut().unwrap(); 296 | let api = &mut data.0; 297 | let plugin = &mut data.1; 298 | if old_channel_id == crate::ChannelId(0) { 299 | // Connection connected, this will also be called for ourselves 300 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 301 | let server = api.get_server_unwrap(server_id); 302 | let connection = server.get_connection_unwrap(connection_id); 303 | plugin.connection_changed(api, &server, &connection, true, move_message) 304 | } else if new_channel_id == crate::ChannelId(0) { 305 | // Connection disconnected 306 | { 307 | let server = api.get_server_unwrap(server_id); 308 | let connection = server.get_connection_unwrap(connection_id); 309 | plugin.connection_changed(api, &server, &connection, false, move_message); 310 | } 311 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 312 | } else if old_channel_id == new_channel_id { 313 | // Connection announced 314 | match visibility { 315 | Visibility::Enter => { 316 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 317 | let server = api.get_server_unwrap(server_id); 318 | let connection = server.get_connection_unwrap(connection_id); 319 | plugin.connection_announced(api, &server, &connection, true); 320 | } 321 | Visibility::Leave => { 322 | { 323 | let server = api.get_server_unwrap(server_id); 324 | let connection = server.get_connection_unwrap(connection_id); 325 | plugin.connection_announced(api, &server, &connection, false); 326 | } 327 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 328 | } 329 | Visibility::Retain => {} 330 | } 331 | } else { 332 | // Connection switched channel 333 | // Add the connection if it entered visibility 334 | if visibility == Visibility::Enter { 335 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 336 | } 337 | // Update the channel 338 | { 339 | if let Some(connection) = 340 | api.get_mut_server(server_id).and_then(|s| s.get_mut_connection(connection_id)) 341 | { 342 | connection.channel_id = Ok(new_channel_id); 343 | } 344 | } 345 | { 346 | let server = api.get_server_unwrap(server_id); 347 | let connection = server.get_connection_unwrap(connection_id); 348 | let old_channel = server.get_channel_unwrap(old_channel_id); 349 | let new_channel = server.get_channel_unwrap(new_channel_id); 350 | plugin.connection_move( 351 | api, 352 | &server, 353 | &connection, 354 | &old_channel, 355 | &new_channel, 356 | visibility, 357 | ); 358 | } 359 | // Remove the connection if it left visibility 360 | if visibility == Visibility::Leave { 361 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 362 | } 363 | } 364 | }} 365 | 366 | #[allow(non_snake_case)] 367 | #[unsafe(no_mangle)] 368 | #[doc(hidden)] 369 | pub unsafe extern "C" fn ts3plugin_onClientMoveMovedEvent( 370 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 371 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 372 | move_message: *const c_char, 373 | ) { unsafe { 374 | let server_id = crate::ServerId(server_id); 375 | let connection_id = crate::ConnectionId(connection_id); 376 | let old_channel_id = crate::ChannelId(old_channel_id); 377 | let new_channel_id = crate::ChannelId(new_channel_id); 378 | let visibility = transmute(visibility); 379 | let invoker_id = crate::ConnectionId(invoker_id); 380 | let invoker_name = to_string!(invoker_name); 381 | let invoker_uid = to_string!(invoker_uid); 382 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 383 | let move_message = to_string!(move_message); 384 | let mut data = DATA.lock().unwrap(); 385 | let data = data.0.as_mut().unwrap(); 386 | let api = &mut data.0; 387 | let plugin = &mut data.1; 388 | // Appart from the invoker, the same code as for ClientMove 389 | api.try_update_invoker(server_id, &invoker); 390 | if old_channel_id == crate::ChannelId(0) { 391 | // Connection connected, this will also be called for ourselves 392 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 393 | let server = api.get_server_unwrap(server_id); 394 | let connection = server.get_connection_unwrap(connection_id); 395 | plugin.connection_changed(api, &server, &connection, true, move_message) 396 | } else if new_channel_id == crate::ChannelId(0) { 397 | // Connection disconnected 398 | { 399 | let server = api.get_server_unwrap(server_id); 400 | let connection = server.get_connection_unwrap(connection_id); 401 | plugin.connection_changed(api, &server, &connection, false, move_message); 402 | } 403 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 404 | } else if old_channel_id == new_channel_id { 405 | // Connection announced 406 | match visibility { 407 | Visibility::Enter => { 408 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 409 | let server = api.get_server_unwrap(server_id); 410 | let connection = server.get_connection_unwrap(connection_id); 411 | plugin.connection_announced(api, &server, &connection, true); 412 | } 413 | Visibility::Leave => { 414 | { 415 | let server = api.get_server_unwrap(server_id); 416 | let connection = server.get_connection_unwrap(connection_id); 417 | plugin.connection_announced(api, &server, &connection, false); 418 | } 419 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 420 | } 421 | Visibility::Retain => {} 422 | } 423 | } else { 424 | // Connection switched channel 425 | // Add the connection if it entered visibility 426 | if visibility == Visibility::Enter { 427 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 428 | } 429 | // Update the channel 430 | { 431 | if let Some(connection) = 432 | api.get_mut_server(server_id).and_then(|s| s.get_mut_connection(connection_id)) 433 | { 434 | connection.channel_id = Ok(new_channel_id); 435 | } 436 | } 437 | { 438 | let server = api.get_server_unwrap(server_id); 439 | let connection = server.get_connection_unwrap(connection_id); 440 | let old_channel = server.get_channel_unwrap(old_channel_id); 441 | let new_channel = server.get_channel_unwrap(new_channel_id); 442 | plugin.connection_moved( 443 | api, 444 | &server, 445 | &connection, 446 | &old_channel, 447 | &new_channel, 448 | visibility, 449 | &crate::Invoker::new(server.clone(), invoker), 450 | ); 451 | } 452 | // Remove the connection if it left visibility 453 | if visibility == Visibility::Leave { 454 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 455 | } 456 | } 457 | }} 458 | 459 | #[allow(non_snake_case, unused_variables)] 460 | #[unsafe(no_mangle)] 461 | #[doc(hidden)] 462 | pub unsafe extern "C" fn ts3plugin_onClientMoveSubscriptionEvent( 463 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, visibility: c_int, 464 | ) { unsafe { 465 | let server_id = crate::ServerId(server_id); 466 | let connection_id = crate::ConnectionId(connection_id); 467 | //let old_channel_id = ::ChannelId(old_channel_id); 468 | //let new_channel_id = ::ChannelId(new_channel_id); 469 | let visibility = transmute(visibility); 470 | let mut data = DATA.lock().unwrap(); 471 | let data = data.0.as_mut().unwrap(); 472 | let api = &mut data.0; 473 | let plugin = &mut data.1; 474 | // Connection announced 475 | match visibility { 476 | Visibility::Enter => { 477 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 478 | let server = api.get_server_unwrap(server_id); 479 | let connection = server.get_connection_unwrap(connection_id); 480 | plugin.connection_announced(api, &server, &connection, true); 481 | } 482 | Visibility::Leave => { 483 | { 484 | let server = api.get_server_unwrap(server_id); 485 | let connection = server.get_connection_unwrap(connection_id); 486 | plugin.connection_announced(api, &server, &connection, false); 487 | } 488 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 489 | } 490 | Visibility::Retain => {} 491 | } 492 | }} 493 | 494 | #[allow(non_snake_case, unused_variables)] 495 | #[unsafe(no_mangle)] 496 | #[doc(hidden)] 497 | pub unsafe extern "C" fn ts3plugin_onClientMoveTimeoutEvent( 498 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 499 | visibility: c_int, timeout_message: *const c_char, 500 | ) { unsafe { 501 | let server_id = crate::ServerId(server_id); 502 | let connection_id = crate::ConnectionId(connection_id); 503 | //let old_channel_id = ::ChannelId(old_channel_id); 504 | //let new_channel_id = ::ChannelId(new_channel_id); 505 | //let visibility = transmute(visibility); 506 | let timeout_message = to_string!(timeout_message); 507 | let mut data = DATA.lock().unwrap(); 508 | let data = data.0.as_mut().unwrap(); 509 | let api = &mut data.0; 510 | let plugin = &mut data.1; 511 | { 512 | let server = api.get_server_unwrap(server_id); 513 | let connection = server.get_connection_unwrap(connection_id); 514 | plugin.connection_timeout(api, &server, &connection); 515 | } 516 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 517 | }} 518 | 519 | #[allow(non_snake_case, unused_variables)] 520 | #[unsafe(no_mangle)] 521 | #[doc(hidden)] 522 | pub unsafe extern "C" fn ts3plugin_onNewChannelEvent( 523 | server_id: u64, channel_id: u64, parent_channel_id: u64, 524 | ) { 525 | let server_id = crate::ServerId(server_id); 526 | let channel_id = crate::ChannelId(channel_id); 527 | //let parent_channel_id = ::ChannelId(parent_channel_id); 528 | let mut data = DATA.lock().unwrap(); 529 | let data = data.0.as_mut().unwrap(); 530 | let api = &mut data.0; 531 | let plugin = &mut data.1; 532 | let err = api.get_mut_server(server_id).unwrap().add_channel(channel_id).err(); 533 | if let Some(error) = err { 534 | error!(api, "Can't get channel information", error); 535 | } 536 | let server = api.get_server_unwrap(server_id); 537 | let channel = server.get_channel_unwrap(channel_id); 538 | plugin.channel_announced(api, &server, &channel); 539 | } 540 | 541 | #[allow(non_snake_case)] 542 | #[unsafe(no_mangle)] 543 | #[doc(hidden)] 544 | pub unsafe extern "C" fn ts3plugin_onChannelDescriptionUpdateEvent( 545 | server_id: u64, channel_id: u64, 546 | ) { 547 | let server_id = crate::ServerId(server_id); 548 | let channel_id = crate::ChannelId(channel_id); 549 | let mut data = DATA.lock().unwrap(); 550 | let data = data.0.as_mut().unwrap(); 551 | let api = &mut data.0; 552 | let plugin = &mut data.1; 553 | // FIXME 554 | /*// Seems like I really like constructions like that, I failed to do it simpler 555 | // because I can't borrow api to print an error message in the inner part. 556 | if let Err(error) = if let Some(channel) = api.get_mut_server(server_id) 557 | .unwrap().get_mut_channel(channel_id) { 558 | channel.optional_data.update_description(); 559 | channel.get_optional_data().get_description().map(|_| ()) 560 | } else { 561 | Ok(()) 562 | } { 563 | error!(api, "Can't get channel description", error); 564 | }*/ 565 | let server = api.get_server_unwrap(server_id); 566 | let channel = server.get_channel_unwrap(channel_id); 567 | plugin.channel_description_updated(api, &server, &channel); 568 | } 569 | 570 | #[allow(non_snake_case)] 571 | #[unsafe(no_mangle)] 572 | #[doc(hidden)] 573 | pub unsafe extern "C" fn ts3plugin_onUpdateChannelEvent(server_id: u64, channel_id: u64) { 574 | let server_id = crate::ServerId(server_id); 575 | let channel_id = crate::ChannelId(channel_id); 576 | let mut data = DATA.lock().unwrap(); 577 | let data = data.0.as_mut().unwrap(); 578 | let api = &mut data.0; 579 | let plugin = &mut data.1; 580 | let old_channel; 581 | if let Err(error) = { 582 | let server = api.get_mut_server(server_id).unwrap(); 583 | // Try to get the old channel 584 | old_channel = 585 | server.remove_channel(channel_id).unwrap_or(crate::ChannelData::new(server_id, channel_id)); 586 | match server.add_channel(channel_id) { 587 | Ok(_) => { 588 | let channel = server.get_mut_channel(channel_id).unwrap(); 589 | // Copy optional data from old channel 590 | channel.update_from(&old_channel); 591 | Ok(()) 592 | } 593 | Err(error) => Err(error), 594 | } 595 | } { 596 | error!(api, "Can't get channel information", error); 597 | } 598 | let server = api.get_server_unwrap(server_id); 599 | let channel = server.get_channel_unwrap(channel_id); 600 | plugin.channel_updated(api, &server, &channel, &crate::Channel::new(api, &old_channel)); 601 | } 602 | 603 | #[allow(non_snake_case, unused_variables)] 604 | #[unsafe(no_mangle)] 605 | #[doc(hidden)] 606 | pub unsafe extern "C" fn ts3plugin_onNewChannelCreatedEvent( 607 | server_id: u64, channel_id: u64, parent_channel_id: u64, invoker_id: u16, 608 | invoker_name: *const c_char, invoker_uid: *const c_char, 609 | ) { unsafe { 610 | let server_id = crate::ServerId(server_id); 611 | let channel_id = crate::ChannelId(channel_id); 612 | let parent_channel_id = crate::ChannelId(parent_channel_id); 613 | let invoker = if invoker_id == 0 { 614 | None 615 | } else { 616 | Some(crate::InvokerData::new( 617 | crate::ConnectionId(invoker_id), 618 | to_string!(invoker_uid), 619 | to_string!(invoker_name), 620 | )) 621 | }; 622 | let mut data = DATA.lock().unwrap(); 623 | let data = data.0.as_mut().unwrap(); 624 | let api = &mut data.0; 625 | let plugin = &mut data.1; 626 | if let Some(ref invoker) = invoker { 627 | api.try_update_invoker(server_id, invoker); 628 | } 629 | if let Some(error) = match api.get_mut_server(server_id).unwrap().add_channel(channel_id) { 630 | Ok(channel) => { 631 | channel.parent_channel_id = Ok(parent_channel_id); 632 | None 633 | } 634 | Err(error) => Some(error), 635 | } { 636 | error!(api, "Can't get channel information", error); 637 | } 638 | let server = api.get_server_unwrap(server_id); 639 | let channel = server.get_channel_unwrap(channel_id); 640 | plugin.channel_created( 641 | api, 642 | &server, 643 | &channel, 644 | invoker.map(|i| crate::Invoker::new(server.clone(), i)).as_ref(), 645 | ); 646 | }} 647 | 648 | #[allow(non_snake_case)] 649 | #[unsafe(no_mangle)] 650 | #[doc(hidden)] 651 | pub unsafe extern "C" fn ts3plugin_onDelChannelEvent( 652 | server_id: u64, channel_id: u64, invoker_id: u16, invoker_name: *const c_char, 653 | invoker_uid: *const c_char, 654 | ) { unsafe { 655 | let server_id = crate::ServerId(server_id); 656 | let channel_id = crate::ChannelId(channel_id); 657 | let invoker = if invoker_id == 0 { 658 | None 659 | } else { 660 | Some(crate::InvokerData::new( 661 | crate::ConnectionId(invoker_id), 662 | to_string!(invoker_uid), 663 | to_string!(invoker_name), 664 | )) 665 | }; 666 | let mut data = DATA.lock().unwrap(); 667 | let data = data.0.as_mut().unwrap(); 668 | let api = &mut data.0; 669 | let plugin = &mut data.1; 670 | if let Some(ref invoker) = invoker { 671 | api.try_update_invoker(server_id, invoker); 672 | } 673 | { 674 | let server = api.get_server_unwrap(server_id); 675 | let channel = server.get_channel_unwrap(channel_id); 676 | plugin.channel_deleted( 677 | api, 678 | &server, 679 | &channel, 680 | invoker.map(|i| crate::Invoker::new(server.clone(), i)).as_ref(), 681 | ); 682 | } 683 | if api.get_mut_server(server_id).and_then(|s| s.remove_channel(channel_id)).is_none() { 684 | api.log_or_print("Can't remove channel", "rust-ts3plugin", crate::LogLevel::Error); 685 | } 686 | }} 687 | 688 | #[allow(non_snake_case)] 689 | #[unsafe(no_mangle)] 690 | #[doc(hidden)] 691 | pub unsafe extern "C" fn ts3plugin_onUpdateChannelEditedEvent( 692 | server_id: u64, channel_id: u64, invoker_id: u16, invoker_name: *const c_char, 693 | invoker_uid: *const c_char, 694 | ) { unsafe { 695 | let server_id = crate::ServerId(server_id); 696 | let channel_id = crate::ChannelId(channel_id); 697 | let invoker_id = crate::ConnectionId(invoker_id); 698 | let invoker_name = to_string!(invoker_name); 699 | let invoker_uid = to_string!(invoker_uid); 700 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 701 | let mut data = DATA.lock().unwrap(); 702 | let data = data.0.as_mut().unwrap(); 703 | let api = &mut data.0; 704 | let plugin = &mut data.1; 705 | api.try_update_invoker(server_id, &invoker); 706 | let old_channel; 707 | if let Err(error) = { 708 | let server = api.get_mut_server(server_id).unwrap(); 709 | // Try to get the old channel 710 | old_channel = 711 | server.remove_channel(channel_id).unwrap_or(crate::ChannelData::new(server_id, channel_id)); 712 | match server.add_channel(channel_id) { 713 | Ok(_) => { 714 | let channel = server.get_mut_channel(channel_id).unwrap(); 715 | // Copy optional data from old channel 716 | channel.update_from(&old_channel); 717 | Ok(()) 718 | } 719 | Err(error) => Err(error), 720 | } 721 | } { 722 | error!(api, "Can't get channel information", error); 723 | } 724 | let server = api.get_server_unwrap(server_id); 725 | let channel = server.get_channel_unwrap(channel_id); 726 | plugin.channel_edited( 727 | api, 728 | &server, 729 | &channel, 730 | &crate::Channel::new(api, &old_channel), 731 | &crate::Invoker::new(server.clone(), invoker), 732 | ); 733 | }} 734 | 735 | #[allow(non_snake_case)] 736 | #[unsafe(no_mangle)] 737 | #[doc(hidden)] 738 | pub unsafe extern "C" fn ts3plugin_onChannelPasswordChangedEvent(server_id: u64, channel_id: u64) { 739 | let server_id = crate::ServerId(server_id); 740 | let channel_id = crate::ChannelId(channel_id); 741 | let mut data = DATA.lock().unwrap(); 742 | let data = data.0.as_mut().unwrap(); 743 | let api = &mut data.0; 744 | let plugin = &mut data.1; 745 | let server = api.get_server_unwrap(server_id); 746 | let channel = server.get_channel_unwrap(channel_id); 747 | plugin.channel_password_updated(api, &server, &channel); 748 | } 749 | 750 | #[allow(non_snake_case)] 751 | #[unsafe(no_mangle)] 752 | #[doc(hidden)] 753 | pub unsafe extern "C" fn ts3plugin_onChannelMoveEvent( 754 | server_id: u64, channel_id: u64, new_parent_channel_id: u64, invoker_id: u16, 755 | invoker_name: *const c_char, invoker_uid: *const c_char, 756 | ) { unsafe { 757 | let server_id = crate::ServerId(server_id); 758 | let channel_id = crate::ChannelId(channel_id); 759 | let new_parent_channel_id = crate::ChannelId(new_parent_channel_id); 760 | let invoker = if invoker_id == 0 { 761 | None 762 | } else { 763 | Some(crate::InvokerData::new( 764 | crate::ConnectionId(invoker_id), 765 | to_string!(invoker_uid), 766 | to_string!(invoker_name), 767 | )) 768 | }; 769 | let mut data = DATA.lock().unwrap(); 770 | let data = data.0.as_mut().unwrap(); 771 | let api = &mut data.0; 772 | let plugin = &mut data.1; 773 | if let Some(ref invoker) = invoker { 774 | api.try_update_invoker(server_id, invoker); 775 | } 776 | { 777 | let server = api.get_server_unwrap(server_id); 778 | let channel = server.get_channel_unwrap(channel_id); 779 | let new_parent_channel = server.get_channel_unwrap(new_parent_channel_id); 780 | plugin.channel_moved( 781 | api, 782 | &server, 783 | &channel, 784 | &new_parent_channel, 785 | invoker.map(|i| crate::Invoker::new(server.clone(), i)).as_ref(), 786 | ); 787 | } 788 | if let Some(channel) = api.get_mut_server(server_id).and_then(|s| s.get_mut_channel(channel_id)) 789 | { 790 | channel.parent_channel_id = Ok(new_parent_channel_id); 791 | } 792 | }} 793 | 794 | // Ignore clippy warnings, we can't change the TeamSpeak interface 795 | #[allow(clippy::too_many_arguments)] 796 | #[allow(non_snake_case)] 797 | #[unsafe(no_mangle)] 798 | #[doc(hidden)] 799 | pub unsafe extern "C" fn ts3plugin_onTextMessageEvent( 800 | server_id: u64, target_mode: u16, receiver_id: u16, invoker_id: u16, 801 | invoker_name: *const c_char, invoker_uid: *const c_char, message: *const c_char, 802 | ignored: c_int, 803 | ) -> c_int { unsafe { 804 | let server_id = crate::ServerId(server_id); 805 | let target_mode = transmute(target_mode as i32); 806 | let receiver_id = crate::ConnectionId(receiver_id); 807 | let invoker_id = crate::ConnectionId(invoker_id); 808 | let invoker_name = to_string!(invoker_name); 809 | let invoker_uid = to_string!(invoker_uid); 810 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 811 | let message = to_string!(message); 812 | let ignored = ignored != 0; 813 | let mut data = DATA.lock().unwrap(); 814 | let data = data.0.as_mut().unwrap(); 815 | let api = &mut data.0; 816 | let plugin = &mut data.1; 817 | api.try_update_invoker(server_id, &invoker); 818 | let message_receiver = match target_mode { 819 | crate::TextMessageTargetMode::Client => crate::MessageReceiver::Connection(receiver_id), 820 | crate::TextMessageTargetMode::Channel => crate::MessageReceiver::Channel, 821 | crate::TextMessageTargetMode::Server => crate::MessageReceiver::Server, 822 | _ => { 823 | api.log_or_print( 824 | "Got invalid TextMessageTargetMode", 825 | "rust-ts3plugin", 826 | crate::LogLevel::Error, 827 | ); 828 | crate::MessageReceiver::Server 829 | } 830 | }; 831 | let server = api.get_server_unwrap(server_id); 832 | if plugin.message( 833 | api, 834 | &server, 835 | &crate::Invoker::new(server.clone(), invoker), 836 | message_receiver, 837 | message, 838 | ignored, 839 | ) { 840 | 1 841 | } else { 842 | 0 843 | } 844 | }} 845 | 846 | #[allow(non_snake_case)] 847 | #[unsafe(no_mangle)] 848 | #[doc(hidden)] 849 | pub unsafe extern "C" fn ts3plugin_onClientPokeEvent( 850 | server_id: u64, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 851 | message: *const c_char, ignored: c_int, 852 | ) -> c_int { unsafe { 853 | let server_id = crate::ServerId(server_id); 854 | let invoker_id = crate::ConnectionId(invoker_id); 855 | let invoker_name = to_string!(invoker_name); 856 | let invoker_uid = to_string!(invoker_uid); 857 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 858 | let message = to_string!(message); 859 | let ignored = ignored != 0; 860 | let mut data = DATA.lock().unwrap(); 861 | let data = data.0.as_mut().unwrap(); 862 | let api = &mut data.0; 863 | let plugin = &mut data.1; 864 | api.try_update_invoker(server_id, &invoker); 865 | let server = api.get_server_unwrap(server_id); 866 | if plugin.poke(api, &server, &crate::Invoker::new(server.clone(), invoker), message, ignored) { 867 | 1 868 | } else { 869 | 0 870 | } 871 | }} 872 | 873 | #[allow(clippy::too_many_arguments)] 874 | #[allow(non_snake_case)] 875 | #[unsafe(no_mangle)] 876 | #[doc(hidden)] 877 | pub unsafe extern "C" fn ts3plugin_onClientKickFromChannelEvent( 878 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 879 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 880 | message: *const c_char, 881 | ) { unsafe { 882 | let server_id = crate::ServerId(server_id); 883 | let connection_id = crate::ConnectionId(connection_id); 884 | let old_channel_id = crate::ChannelId(old_channel_id); 885 | let new_channel_id = crate::ChannelId(new_channel_id); 886 | let visibility = transmute(visibility); 887 | let invoker_id = crate::ConnectionId(invoker_id); 888 | let invoker_name = to_string!(invoker_name); 889 | let invoker_uid = to_string!(invoker_uid); 890 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 891 | let message = to_string!(message); 892 | let mut data = DATA.lock().unwrap(); 893 | let data = data.0.as_mut().unwrap(); 894 | let api = &mut data.0; 895 | let plugin = &mut data.1; 896 | api.try_update_invoker(server_id, &invoker); 897 | { 898 | let server = api.get_server_unwrap(server_id); 899 | let connection = server.get_connection_unwrap(connection_id); 900 | let old_channel = server.get_channel_unwrap(old_channel_id); 901 | let new_channel = server.get_channel_unwrap(new_channel_id); 902 | plugin.channel_kick( 903 | api, 904 | &server, 905 | &connection, 906 | &old_channel, 907 | &new_channel, 908 | visibility, 909 | &crate::Invoker::new(server.clone(), invoker), 910 | message, 911 | ); 912 | } 913 | // Remove the kicked connection if it is not visible anymore 914 | if visibility == crate::Visibility::Leave { 915 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 916 | } else if let Some(connection) = api.get_mut_server(server_id).and_then(|s| 917 | // Update the current channel of the connection 918 | s.get_mut_connection(connection_id)) 919 | { 920 | connection.channel_id = Ok(new_channel_id); 921 | } 922 | }} 923 | 924 | #[allow(clippy::too_many_arguments)] 925 | #[allow(non_snake_case, unused_variables)] 926 | #[unsafe(no_mangle)] 927 | #[doc(hidden)] 928 | pub unsafe extern "C" fn ts3plugin_onClientKickFromServerEvent( 929 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 930 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 931 | message: *const c_char, 932 | ) { unsafe { 933 | let server_id = crate::ServerId(server_id); 934 | let connection_id = crate::ConnectionId(connection_id); 935 | let old_channel_id = crate::ChannelId(old_channel_id); 936 | let new_channel_id = crate::ChannelId(new_channel_id); 937 | //let visibility = transmute(visibility); 938 | let invoker_id = crate::ConnectionId(invoker_id); 939 | let invoker_name = to_string!(invoker_name); 940 | let invoker_uid = to_string!(invoker_uid); 941 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 942 | let message = to_string!(message); 943 | let mut data = DATA.lock().unwrap(); 944 | let data = data.0.as_mut().unwrap(); 945 | let api = &mut data.0; 946 | let plugin = &mut data.1; 947 | api.try_update_invoker(server_id, &invoker); 948 | { 949 | let server = api.get_server_unwrap(server_id); 950 | let connection = server.get_connection_unwrap(connection_id); 951 | plugin.server_kick( 952 | api, 953 | &server, 954 | &connection, 955 | &crate::Invoker::new(server.clone(), invoker), 956 | message, 957 | ); 958 | } 959 | // Remove the kicked connection 960 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 961 | }} 962 | 963 | #[allow(clippy::too_many_arguments)] 964 | #[allow(non_snake_case, unused_variables)] 965 | #[unsafe(no_mangle)] 966 | #[doc(hidden)] 967 | pub unsafe extern "C" fn ts3plugin_onClientBanFromServerEvent( 968 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 969 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 970 | time: u64, message: *const c_char, 971 | ) { unsafe { 972 | let server_id = crate::ServerId(server_id); 973 | let connection_id = crate::ConnectionId(connection_id); 974 | //let old_channel_id = ::ChannelId(old_channel_id); 975 | //let new_channel_id = ::ChannelId(new_channel_id); 976 | //let visibility = transmute(visibility); 977 | let invoker_id = crate::ConnectionId(invoker_id); 978 | let invoker_name = to_string!(invoker_name); 979 | let invoker_uid = to_string!(invoker_uid); 980 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 981 | let message = to_string!(message); 982 | let mut data = DATA.lock().unwrap(); 983 | let data = data.0.as_mut().unwrap(); 984 | let api = &mut data.0; 985 | let plugin = &mut data.1; 986 | api.try_update_invoker(server_id, &invoker); 987 | { 988 | let server = api.get_server_unwrap(server_id); 989 | let connection = server.get_connection_unwrap(connection_id); 990 | plugin.server_ban( 991 | api, 992 | &server, 993 | &connection, 994 | &crate::Invoker::new(server.clone(), invoker), 995 | message, 996 | time, 997 | ); 998 | } 999 | // Remove the banned connection 1000 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 1001 | }} 1002 | 1003 | #[allow(non_snake_case)] 1004 | #[unsafe(no_mangle)] 1005 | #[doc(hidden)] 1006 | pub unsafe extern "C" fn ts3plugin_onTalkStatusChangeEvent( 1007 | server_id: u64, talking: c_int, whispering: c_int, connection_id: u16, 1008 | ) { unsafe { 1009 | let server_id = crate::ServerId(server_id); 1010 | let talking = transmute(talking); 1011 | let whispering = whispering != 0; 1012 | let connection_id = crate::ConnectionId(connection_id); 1013 | let mut data = DATA.lock().unwrap(); 1014 | let data = data.0.as_mut().unwrap(); 1015 | let api = &mut data.0; 1016 | let plugin = &mut data.1; 1017 | { 1018 | let server = api.get_server_unwrap(server_id); 1019 | let connection = server.get_connection_unwrap(connection_id); 1020 | plugin.talking_changed(api, &server, &connection, talking, whispering); 1021 | } 1022 | // Update the connection 1023 | if let Some(connection) = 1024 | api.get_mut_server(server_id).and_then(|s| s.get_mut_connection(connection_id)) 1025 | { 1026 | connection.talking = Ok(talking); 1027 | connection.whispering = Ok(whispering); 1028 | } 1029 | }} 1030 | 1031 | #[allow(non_snake_case)] 1032 | #[unsafe(no_mangle)] 1033 | #[doc(hidden)] 1034 | pub unsafe extern "C" fn ts3plugin_onAvatarUpdated( 1035 | server_id: u64, connection_id: u16, avatar_path: *const c_char, 1036 | ) { unsafe { 1037 | let server_id = crate::ServerId(server_id); 1038 | let connection_id = crate::ConnectionId(connection_id); 1039 | let path = if avatar_path.is_null() { None } else { Some(to_string!(avatar_path)) }; 1040 | let mut data = DATA.lock().unwrap(); 1041 | let data = data.0.as_mut().unwrap(); 1042 | let api = &mut data.0; 1043 | let plugin = &mut data.1; 1044 | let server = api.get_server_unwrap(server_id); 1045 | let connection = server.get_connection_unwrap(connection_id); 1046 | plugin.avatar_changed(api, &server, &connection, path); 1047 | }} 1048 | #[allow(non_snake_case)] 1049 | #[unsafe(no_mangle)] 1050 | #[doc(hidden)] 1051 | pub unsafe extern "C" fn ts3plugin_onClientChannelGroupChangedEvent( 1052 | server_id: u64, channel_group_id: u64, channel_id: u64, connection_id: u16, invoker_id: u16, 1053 | invoker_name: *const c_char, invoker_uid: *const c_char, 1054 | ) { unsafe { 1055 | let server_id = crate::ServerId(server_id); 1056 | let channel_group_id = crate::ChannelGroupId(channel_group_id); 1057 | let channel_id = crate::ChannelId(channel_id); 1058 | let connection_id = crate::ConnectionId(connection_id); 1059 | let invoker_id = crate::ConnectionId(invoker_id); 1060 | let invoker_name = to_string!(invoker_name); 1061 | let invoker_uid = to_string!(invoker_uid); 1062 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 1063 | let mut data = DATA.lock().unwrap(); 1064 | let data = data.0.as_mut().unwrap(); 1065 | let api = &mut data.0; 1066 | let plugin = &mut data.1; 1067 | api.try_update_invoker(server_id, &invoker); 1068 | let server = api.get_server_unwrap(server_id); 1069 | let connection = server.get_connection_unwrap(connection_id); 1070 | let channel_group = server.get_channel_group_unwrap(channel_group_id); 1071 | let channel = server.get_channel_unwrap(channel_id); 1072 | plugin.connection_channel_group_changed( 1073 | api, 1074 | &server, 1075 | &connection, 1076 | &channel_group, 1077 | &channel, 1078 | &crate::Invoker::new(server.clone(), invoker), 1079 | ); 1080 | }} 1081 | 1082 | #[allow(clippy::too_many_arguments)] 1083 | #[allow(non_snake_case)] 1084 | #[unsafe(no_mangle)] 1085 | #[doc(hidden)] 1086 | pub unsafe extern "C" fn ts3plugin_onServerGroupClientAddedEvent( 1087 | server_id: u64, connection_id: u16, connection_name: *const c_char, 1088 | connection_uid: *const c_char, server_group_id: u64, invoker_id: u16, 1089 | invoker_name: *const c_char, invoker_uid: *const c_char, 1090 | ) { unsafe { 1091 | let server_id = crate::ServerId(server_id); 1092 | let connection_id = crate::ConnectionId(connection_id); 1093 | let connection_name = to_string!(connection_name); 1094 | let connection_uid = to_string!(connection_uid); 1095 | let connection = crate::InvokerData::new(connection_id, connection_uid, connection_name); 1096 | let server_group_id = crate::ServerGroupId(server_group_id); 1097 | let invoker_id = crate::ConnectionId(invoker_id); 1098 | let invoker_name = to_string!(invoker_name); 1099 | let invoker_uid = to_string!(invoker_uid); 1100 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 1101 | let mut data = DATA.lock().unwrap(); 1102 | let data = data.0.as_mut().unwrap(); 1103 | let api = &mut data.0; 1104 | let plugin = &mut data.1; 1105 | api.try_update_invoker(server_id, &invoker); 1106 | let server = api.get_server_unwrap(server_id); 1107 | let server_group = server.get_server_group_unwrap(server_group_id); 1108 | plugin.connection_server_group_added( 1109 | api, 1110 | &server, 1111 | &crate::Invoker::new(server.clone(), connection), 1112 | &server_group, 1113 | &crate::Invoker::new(server.clone(), invoker), 1114 | ); 1115 | }} 1116 | 1117 | #[allow(clippy::too_many_arguments)] 1118 | #[allow(non_snake_case)] 1119 | #[unsafe(no_mangle)] 1120 | #[doc(hidden)] 1121 | pub unsafe extern "C" fn ts3plugin_onServerGroupClientDeletedEvent( 1122 | server_id: u64, connection_id: u16, connection_name: *const c_char, 1123 | connection_uid: *const c_char, server_group_id: u64, invoker_id: u16, 1124 | invoker_name: *const c_char, invoker_uid: *const c_char, 1125 | ) { unsafe { 1126 | let server_id = crate::ServerId(server_id); 1127 | let connection_id = crate::ConnectionId(connection_id); 1128 | let connection_name = to_string!(connection_name); 1129 | let connection_uid = to_string!(connection_uid); 1130 | let connection = crate::InvokerData::new(connection_id, connection_uid, connection_name); 1131 | let server_group_id = crate::ServerGroupId(server_group_id); 1132 | let invoker_id = crate::ConnectionId(invoker_id); 1133 | let invoker_name = to_string!(invoker_name); 1134 | let invoker_uid = to_string!(invoker_uid); 1135 | let invoker = crate::InvokerData::new(invoker_id, invoker_uid, invoker_name); 1136 | let mut data = DATA.lock().unwrap(); 1137 | let data = data.0.as_mut().unwrap(); 1138 | let api = &mut data.0; 1139 | let plugin = &mut data.1; 1140 | api.try_update_invoker(server_id, &invoker); 1141 | let server = api.get_server_unwrap(server_id); 1142 | let server_group = server.get_server_group_unwrap(server_group_id); 1143 | plugin.connection_server_group_removed( 1144 | api, 1145 | &server, 1146 | &crate::Invoker::new(server.clone(), connection), 1147 | &server_group, 1148 | &crate::Invoker::new(server.clone(), invoker), 1149 | ); 1150 | }} 1151 | 1152 | #[allow(non_snake_case)] 1153 | #[unsafe(no_mangle)] 1154 | #[doc(hidden)] 1155 | pub unsafe extern "C" fn ts3plugin_onServerPermissionErrorEvent( 1156 | server_id: u64, message: *const c_char, error: c_uint, return_code: *const c_char, 1157 | permission_id: c_uint, 1158 | ) -> c_int { unsafe { 1159 | let server_id = crate::ServerId(server_id); 1160 | let message = to_string!(message); 1161 | let error = transmute(error); 1162 | let return_code = to_string!(return_code); 1163 | let permission_id = crate::PermissionId(permission_id); 1164 | let mut data = DATA.lock().unwrap(); 1165 | let data = data.0.as_mut().unwrap(); 1166 | let api = &mut data.0; 1167 | let plugin = &mut data.1; 1168 | let server = api.get_server_unwrap(server_id); 1169 | let permission = api.get_permission(permission_id).unwrap(); 1170 | if plugin.permission_error(api, &server, permission, error, message, return_code) { 1171 | 1 1172 | } else { 1173 | 0 1174 | } 1175 | }} 1176 | 1177 | #[allow(non_snake_case)] 1178 | #[unsafe(no_mangle)] 1179 | #[doc(hidden)] 1180 | pub unsafe extern "C" fn ts3plugin_onEditPlaybackVoiceDataEvent( 1181 | server_id: u64, connection_id: u16, samples: *mut c_short, sample_count: c_int, channels: c_int, 1182 | ) { unsafe { 1183 | let server_id = crate::ServerId(server_id); 1184 | let connection_id = crate::ConnectionId(connection_id); 1185 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1186 | let mut data = DATA.lock().unwrap(); 1187 | let data = data.0.as_mut().unwrap(); 1188 | let api = &mut data.0; 1189 | let plugin = &mut data.1; 1190 | let server = api.get_server_unwrap(server_id); 1191 | let connection = server.get_connection_unwrap(connection_id); 1192 | plugin.playback_voice_data(api, &server, &connection, samples, channels); 1193 | }} 1194 | 1195 | #[allow(non_snake_case)] 1196 | #[unsafe(no_mangle)] 1197 | #[doc(hidden)] 1198 | pub unsafe extern "C" fn ts3plugin_onEditPostProcessVoiceDataEvent( 1199 | server_id: u64, connection_id: u16, samples: *mut c_short, sample_count: c_int, 1200 | channels: c_int, channel_speaker_array: *const c_uint, channel_fill_mask: *mut c_uint, 1201 | ) { unsafe { 1202 | let server_id = crate::ServerId(server_id); 1203 | let connection_id = crate::ConnectionId(connection_id); 1204 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1205 | let channel_speaker_array = 1206 | slice::from_raw_parts(channel_speaker_array as *mut crate::Speaker, channels as usize); 1207 | let channel_fill_mask = channel_fill_mask.as_mut().unwrap(); 1208 | let mut data = DATA.lock().unwrap(); 1209 | let data = data.0.as_mut().unwrap(); 1210 | let api = &mut data.0; 1211 | let plugin = &mut data.1; 1212 | let server = api.get_server_unwrap(server_id); 1213 | let connection = server.get_connection_unwrap(connection_id); 1214 | plugin.post_process_voice_data( 1215 | api, 1216 | &server, 1217 | &connection, 1218 | samples, 1219 | channels, 1220 | channel_speaker_array, 1221 | channel_fill_mask, 1222 | ); 1223 | }} 1224 | 1225 | #[allow(non_snake_case)] 1226 | #[unsafe(no_mangle)] 1227 | #[doc(hidden)] 1228 | pub unsafe extern "C" fn ts3plugin_onEditMixedPlaybackVoiceDataEvent( 1229 | server_id: u64, samples: *mut c_short, sample_count: c_int, channels: c_int, 1230 | channel_speaker_array: *const c_uint, channel_fill_mask: *mut c_uint, 1231 | ) { unsafe { 1232 | let server_id = crate::ServerId(server_id); 1233 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1234 | let channel_speaker_array = 1235 | slice::from_raw_parts(channel_speaker_array as *mut crate::Speaker, channels as usize); 1236 | let channel_fill_mask = channel_fill_mask.as_mut().unwrap(); 1237 | let mut data = DATA.lock().unwrap(); 1238 | let data = data.0.as_mut().unwrap(); 1239 | let api = &mut data.0; 1240 | let plugin = &mut data.1; 1241 | let server = api.get_server_unwrap(server_id); 1242 | plugin.mixed_playback_voice_data( 1243 | api, 1244 | &server, 1245 | samples, 1246 | channels, 1247 | channel_speaker_array, 1248 | channel_fill_mask, 1249 | ); 1250 | }} 1251 | 1252 | #[allow(non_snake_case)] 1253 | #[unsafe(no_mangle)] 1254 | #[doc(hidden)] 1255 | pub unsafe extern "C" fn ts3plugin_onEditCapturedVoiceDataEvent( 1256 | server_id: u64, samples: *mut c_short, sample_count: c_int, channels: c_int, edited: *mut c_int, 1257 | ) { unsafe { 1258 | let server_id = crate::ServerId(server_id); 1259 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1260 | let mut send = (*edited & 2) != 0; 1261 | let mut data = DATA.lock().unwrap(); 1262 | let data = data.0.as_mut().unwrap(); 1263 | let api = &mut data.0; 1264 | let plugin = &mut data.1; 1265 | let server = api.get_server_unwrap(server_id); 1266 | // Set the first bit if the sound data were edited 1267 | *edited |= plugin.captured_voice_data(api, &server, samples, channels, &mut send) as c_int; 1268 | // Set the second bit of `edited` to `send` 1269 | *edited = (*edited & !2) | ((send as c_int) << 1); 1270 | }} 1271 | 1272 | #[allow(non_snake_case)] 1273 | #[unsafe(no_mangle)] 1274 | #[doc(hidden)] 1275 | pub unsafe extern "C" fn ts3plugin_onPluginCommandEvent( 1276 | server_id: u64, plugin_name: *const c_char, plugin_command: *const c_char, invoker_id: u16, 1277 | invoker_name: *const c_char, invoker_uid: *const c_char, 1278 | ) { unsafe { 1279 | let server_id = crate::ServerId(server_id); 1280 | let invoker = if invoker_id == 0 { 1281 | None 1282 | } else { 1283 | Some(crate::InvokerData::new( 1284 | crate::ConnectionId(invoker_id), 1285 | to_string!(invoker_uid), 1286 | to_string!(invoker_name), 1287 | )) 1288 | }; 1289 | let mut data = DATA.lock().unwrap(); 1290 | let data = data.0.as_mut().unwrap(); 1291 | let api = &mut data.0; 1292 | let plugin = &mut data.1; 1293 | if let Some(ref invoker) = invoker { 1294 | api.try_update_invoker(server_id, invoker); 1295 | } 1296 | let server = api.get_server_unwrap(server_id); 1297 | plugin.plugin_message( 1298 | api, 1299 | &server, 1300 | to_string!(plugin_name), 1301 | to_string!(plugin_command), 1302 | invoker.map(|i| crate::Invoker::new(server.clone(), i)).as_ref(), 1303 | ); 1304 | }} 1305 | 1306 | #[allow(non_snake_case)] 1307 | #[unsafe(no_mangle)] 1308 | #[doc(hidden)] 1309 | pub unsafe extern "C" fn ts3plugin_processCommand(server_id: u64, command: *const c_char) -> c_int { unsafe { 1310 | let server_id = crate::ServerId(server_id); 1311 | let mut data = DATA.lock().unwrap(); 1312 | let data = data.0.as_mut().unwrap(); 1313 | let api = &mut data.0; 1314 | let plugin = &mut data.1; 1315 | let server = api.get_server_unwrap(server_id); 1316 | if plugin.process_command(api, &server, to_string!(command)) { 0 } else { 1 } 1317 | }} 1318 | --------------------------------------------------------------------------------