├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── build ├── build.rs ├── channel.rs ├── connection.rs ├── macros.tera ├── server.rs └── struct.rs.tera ├── clippy.toml └── src ├── lib.rs ├── plugin.rs └── ts3interface.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | /target/ 3 | /Cargo.lock 4 | 5 | *.~ 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ts3plugin" 3 | version = "0.3.0" 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 = "2015" 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" 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build/build.rs: -------------------------------------------------------------------------------- 1 | // Limit for error_chain 2 | #![recursion_limit = "1024"] 3 | 4 | extern crate anyhow; 5 | extern crate serde; 6 | extern crate tera; 7 | 8 | use std::borrow::Cow; 9 | use std::collections::{BTreeMap, HashMap}; 10 | use std::env; 11 | use std::fs::File; 12 | use std::io::Write; 13 | use std::path::Path; 14 | 15 | use anyhow::Result; 16 | use serde::ser::SerializeStruct; 17 | use tera::Tera; 18 | 19 | type Map = BTreeMap; 20 | 21 | mod channel; 22 | mod connection; 23 | mod server; 24 | 25 | #[derive(Default, Clone)] 26 | struct Property<'a> { 27 | name: Cow<'a, str>, 28 | type_s: Cow<'a, str>, 29 | /// If the property should be wrapped into a result. 30 | result: bool, 31 | documentation: Cow<'a, str>, 32 | initialise: bool, 33 | /// The code that creates the content of this property. 34 | initialisation: Option>, 35 | /// The code that updates the content of this property. 36 | update: Option>, 37 | /// If an update method should be generated for this property. 38 | should_update: bool, 39 | /// Use a fixed function 40 | method_name: Option>, 41 | /// The name that is used to initialise this value: enum_name::value_name 42 | enum_name: Cow<'a, str>, 43 | value_name: Option>, 44 | /// Map type_s → used function 45 | functions: Map, Cow<'a, str>>, 46 | /// Types that are transmutable, the standard type that is taken is int. 47 | transmutable: Vec>, 48 | /// Arguments passed to the function 49 | default_args: Cow<'a, str>, 50 | /// Arguments passed to the function when updating the property. 51 | default_args_update: Cow<'a, str>, 52 | /// If an api getter should be created for this property. 53 | api_getter: bool, 54 | /// If the getter method should be public. 55 | public: bool, 56 | /// If this property needs to be requested. 57 | requested: bool, 58 | } 59 | 60 | impl<'a> Property<'a> { 61 | fn is_ref_type(&self) -> bool { 62 | ["String", "Permissions"].contains(&self.type_s.as_ref()) 63 | || self.type_s.starts_with("Option") 64 | || self.type_s.starts_with("Map<") 65 | || self.type_s.starts_with("Vec<") 66 | } 67 | 68 | fn create_return_type(&self) -> String { 69 | // Build the result type 70 | let is_ref_type = self.is_ref_type(); 71 | let mut result_type = String::new(); 72 | if self.result { 73 | result_type.push_str("Result<") 74 | } 75 | if is_ref_type { 76 | result_type.push('&'); 77 | } 78 | if self.type_s == "String" { 79 | result_type.push_str("str"); 80 | } else { 81 | result_type.push_str(self.type_s.as_ref()); 82 | } 83 | if self.result { 84 | result_type.push_str(", "); 85 | result_type.push_str("::Error>"); 86 | } 87 | result_type 88 | } 89 | 90 | fn create_ref_type(&self) -> String { 91 | // Build the result type 92 | let is_ref_type = self.is_ref_type(); 93 | let mut result_type = String::new(); 94 | if is_ref_type { 95 | result_type.push_str("&'a "); 96 | } 97 | if self.type_s == "String" { 98 | result_type.push_str("str"); 99 | } else { 100 | result_type.push_str(self.type_s.as_ref()); 101 | } 102 | result_type 103 | } 104 | 105 | fn create_getter_body(&self) -> String { 106 | let is_ref_type = self.is_ref_type(); 107 | let mut body = String::new(); 108 | if !self.result && is_ref_type { 109 | body.push('&'); 110 | } 111 | body.push_str(format!("self.{}", self.name).as_str()); 112 | if self.result && is_ref_type { 113 | body.push_str(".as_ref()"); 114 | if self.type_s == "String" { 115 | body.push_str(".map(|v| v.as_str())"); 116 | } 117 | body.push_str(".map_err(|e| *e)"); 118 | } 119 | body 120 | } 121 | 122 | fn create_constructor_body(&self) -> String { 123 | let p = self.create_initialisation(); 124 | if p.is_empty() { self.name.clone().into_owned() } else { p } 125 | } 126 | 127 | fn create_update_body(&self) -> String { 128 | self.intern_create_initialisation(self.default_args_update.as_ref(), true) 129 | } 130 | 131 | fn create_initialisation(&self) -> String { 132 | if self.result { 133 | String::from("Err(::Error::Ok)") 134 | } else { 135 | self.intern_create_initialisation(self.default_args.as_ref(), false) 136 | } 137 | } 138 | 139 | fn intern_create_initialisation(&self, default_args: &str, update: bool) -> String { 140 | if !self.initialise || (update && !self.should_update) { 141 | return String::new(); 142 | } else if update && self.update.is_some() { 143 | return self.update.as_ref().unwrap().clone().into_owned(); 144 | } else if self.initialisation.is_some() { 145 | return self.initialisation.as_ref().unwrap().clone().into_owned(); 146 | } 147 | let value_name = self 148 | .value_name 149 | .as_ref() 150 | .map(|s| s.clone()) 151 | .unwrap_or(to_pascal_case(self.name.as_ref()).into()); 152 | let mut s = String::new(); 153 | // Ignore unknown types 154 | if let Some(function) = self.method_name.as_ref() { 155 | // Special defined function 156 | s.push_str( 157 | format!("{}({}{}::{})", function, default_args, self.enum_name, value_name) 158 | .as_str(), 159 | ); 160 | } else if let Some(function) = self.functions.get(self.type_s.as_ref()) { 161 | // From function list 162 | s.push_str( 163 | format!("{}({}{}::{})", function, default_args, self.enum_name, value_name) 164 | .as_str(), 165 | ); 166 | } else if self.transmutable.contains(&self.type_s) { 167 | // Try to get an int 168 | for t in &["i32", "u64"] { 169 | if let Some(function) = self.functions.get(*t) { 170 | s.push_str( 171 | format!( 172 | "{}({}{}::{}).map(|v| unsafe {{ transmute(v) }})", 173 | function, default_args, self.enum_name, value_name 174 | ) 175 | .as_str(), 176 | ); 177 | break; 178 | } 179 | } 180 | } else { 181 | match self.type_s.as_ref() { 182 | "Duration" => { 183 | // Try to get an u64 184 | let function: &str = if let Some(f) = self.functions.get("u64") { 185 | f 186 | } else if let Some(f) = self.functions.get("i32") { 187 | f 188 | } else { 189 | "get_property_as_int" 190 | }; 191 | s.push_str( 192 | format!( 193 | "{}({}{}::{}).map(|d| Duration::seconds(d as i64))", 194 | function, default_args, self.enum_name, value_name 195 | ) 196 | .as_str(), 197 | ) 198 | } 199 | "DateTime" => { 200 | // Try to get an u64 201 | let function: &str = if let Some(f) = self.functions.get("u64") { 202 | f 203 | } else if let Some(f) = self.functions.get("i32") { 204 | f 205 | } else { 206 | "get_property_as_int" 207 | }; 208 | s.push_str( 209 | format!( 210 | "{}({}{}::{}).map(|d| DateTime::from_timestamp(d as i64, 0).unwrap())", 211 | function, default_args, self.enum_name, value_name 212 | ) 213 | .as_str(), 214 | ) 215 | } 216 | "bool" => { 217 | for t in &["i32", "u64"] { 218 | if let Some(function) = self.functions.get(*t) { 219 | s.push_str( 220 | format!( 221 | "{}({}{}::{}).map(|v| v != 0)", 222 | function, default_args, self.enum_name, value_name 223 | ) 224 | .as_str(), 225 | ); 226 | break; 227 | } 228 | } 229 | } 230 | _ => {} 231 | } 232 | } 233 | s 234 | } 235 | } 236 | 237 | impl<'a> serde::Serialize for Property<'a> { 238 | fn serialize( 239 | &self, serializer: S, 240 | ) -> std::result::Result { 241 | let mut s = serializer.serialize_struct("Property", 22)?; 242 | 243 | // Attributes 244 | s.serialize_field("name", &self.name)?; 245 | s.serialize_field("type_s", &self.type_s)?; 246 | s.serialize_field("result", &self.result)?; 247 | let documentation = 248 | self.documentation.lines().map(|l| format!("/// {}\n", l)).collect::(); 249 | s.serialize_field("documentation", &documentation)?; 250 | s.serialize_field("initialise", &self.initialise)?; 251 | s.serialize_field("initialisation", &self.initialisation)?; 252 | s.serialize_field("update", &self.update)?; 253 | s.serialize_field("should_update", &self.should_update)?; 254 | s.serialize_field("method_name", &self.method_name)?; 255 | s.serialize_field("enum_name", &self.enum_name)?; 256 | s.serialize_field("value_name", &self.value_name)?; 257 | s.serialize_field("functions", &self.functions)?; 258 | s.serialize_field("transmutable", &self.transmutable)?; 259 | s.serialize_field("default_args", &self.default_args)?; 260 | s.serialize_field("default_args_update", &self.default_args_update)?; 261 | s.serialize_field("api_getter", &self.api_getter)?; 262 | s.serialize_field("public", &self.public)?; 263 | s.serialize_field("requested", &self.requested)?; 264 | 265 | // Extra attributes 266 | s.serialize_field("return_type", &self.create_return_type())?; 267 | s.serialize_field("getter_body", &self.create_getter_body())?; 268 | s.serialize_field("constructor_body", &self.create_constructor_body())?; 269 | s.serialize_field("update_body", &self.create_update_body())?; 270 | 271 | s.end() 272 | } 273 | } 274 | 275 | #[derive(Default, Clone)] 276 | struct PropertyBuilder<'a> { 277 | name: Cow<'a, str>, 278 | type_s: Cow<'a, str>, 279 | result: bool, 280 | documentation: Cow<'a, str>, 281 | initialise: bool, 282 | initialisation: Option>, 283 | update: Option>, 284 | should_update: bool, 285 | method_name: Option>, 286 | enum_name: Cow<'a, str>, 287 | value_name: Option>, 288 | functions: Map, Cow<'a, str>>, 289 | transmutable: Vec>, 290 | default_args: Cow<'a, str>, 291 | default_args_update: Cow<'a, str>, 292 | api_getter: bool, 293 | public: bool, 294 | requested: bool, 295 | } 296 | 297 | #[allow(dead_code)] 298 | impl<'a> PropertyBuilder<'a> { 299 | fn new() -> PropertyBuilder<'a> { 300 | let mut result = Self::default(); 301 | result.initialise = true; 302 | result.result = true; 303 | result.should_update = true; 304 | result.api_getter = true; 305 | result 306 | } 307 | 308 | fn name>>(&self, name: S) -> PropertyBuilder<'a> { 309 | let mut res = self.clone(); 310 | res.name = name.into(); 311 | res 312 | } 313 | 314 | fn type_s>>(&self, type_s: S) -> PropertyBuilder<'a> { 315 | let mut res = self.clone(); 316 | res.type_s = type_s.into(); 317 | res 318 | } 319 | 320 | fn result(&self, result: bool) -> PropertyBuilder<'a> { 321 | let mut res = self.clone(); 322 | res.result = result; 323 | res 324 | } 325 | 326 | fn documentation>>(&self, documentation: S) -> PropertyBuilder<'a> { 327 | let mut res = self.clone(); 328 | res.documentation = documentation.into(); 329 | res 330 | } 331 | 332 | fn initialise(&self, initialise: bool) -> PropertyBuilder<'a> { 333 | let mut res = self.clone(); 334 | res.initialise = initialise; 335 | res 336 | } 337 | 338 | fn initialisation>>(&self, initialisation: S) -> PropertyBuilder<'a> { 339 | let mut res = self.clone(); 340 | res.initialisation = Some(initialisation.into()); 341 | res 342 | } 343 | 344 | fn update>>(&self, update: S) -> PropertyBuilder<'a> { 345 | let mut res = self.clone(); 346 | res.update = Some(update.into()); 347 | res 348 | } 349 | 350 | fn should_update(&self, should_update: bool) -> PropertyBuilder<'a> { 351 | let mut res = self.clone(); 352 | res.should_update = should_update.into(); 353 | res 354 | } 355 | 356 | fn method_name>>(&self, method_name: S) -> PropertyBuilder<'a> { 357 | let mut res = self.clone(); 358 | res.method_name = Some(method_name.into()); 359 | res 360 | } 361 | 362 | fn enum_name>>(&self, enum_name: S) -> PropertyBuilder<'a> { 363 | let mut res = self.clone(); 364 | res.enum_name = enum_name.into(); 365 | res 366 | } 367 | 368 | fn value_name>>(&self, value_name: S) -> PropertyBuilder<'a> { 369 | let mut res = self.clone(); 370 | res.value_name = Some(value_name.into()); 371 | res 372 | } 373 | 374 | fn functions>, S2: Into>>( 375 | &self, functions: Map, 376 | ) -> PropertyBuilder<'a> { 377 | let mut res = self.clone(); 378 | res.functions = functions.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); 379 | res 380 | } 381 | 382 | fn transmutable>>(&self, transmutable: Vec) -> PropertyBuilder<'a> { 383 | let mut res = self.clone(); 384 | res.transmutable = transmutable.into_iter().map(|s| s.into()).collect(); 385 | res 386 | } 387 | 388 | fn default_args>>(&self, default_args: S) -> PropertyBuilder<'a> { 389 | let mut res = self.clone(); 390 | res.default_args = default_args.into(); 391 | res 392 | } 393 | 394 | fn default_args_update>>( 395 | &self, default_args_update: S, 396 | ) -> PropertyBuilder<'a> { 397 | let mut res = self.clone(); 398 | res.default_args_update = default_args_update.into(); 399 | res 400 | } 401 | 402 | fn api_getter(&self, api_getter: bool) -> PropertyBuilder<'a> { 403 | let mut res = self.clone(); 404 | res.api_getter = api_getter; 405 | res 406 | } 407 | 408 | fn public(&self, public: bool) -> PropertyBuilder<'a> { 409 | let mut res = self.clone(); 410 | res.public = public; 411 | res 412 | } 413 | 414 | fn requested(&self, requested: bool) -> PropertyBuilder<'a> { 415 | let mut res = self.clone(); 416 | res.requested = requested; 417 | res 418 | } 419 | 420 | fn finalize(self) -> Property<'a> { 421 | Property { 422 | name: self.name, 423 | type_s: self.type_s, 424 | result: self.result, 425 | documentation: self.documentation, 426 | initialise: self.initialise, 427 | initialisation: self.initialisation, 428 | update: self.update, 429 | should_update: self.should_update, 430 | method_name: self.method_name, 431 | enum_name: self.enum_name, 432 | value_name: self.value_name, 433 | functions: self.functions.clone(), 434 | transmutable: self.transmutable.clone(), 435 | default_args: self.default_args, 436 | default_args_update: self.default_args_update, 437 | api_getter: self.api_getter, 438 | public: self.public, 439 | requested: self.requested, 440 | } 441 | } 442 | } 443 | 444 | struct Struct<'a> { 445 | /// The name of this struct 446 | name: Cow<'a, str>, 447 | /// The name of this struct when exposed by the api 448 | api_name: Cow<'a, str>, 449 | /// The documentation of this struct 450 | documentation: Cow<'a, str>, 451 | /// Members that will be generated for this struct 452 | properties: Vec>, 453 | /// Code that will be put into the struct part 454 | extra_attributes: Cow<'a, str>, 455 | /// Code that will be inserted into the constructor (::new method) 456 | extra_initialisation: Cow<'a, str>, 457 | /// Code that will be inserted into the creation of the struct 458 | extra_creation: Cow<'a, str>, 459 | /// Code that will be inserted into the Implementation of the struct 460 | extra_implementation: Cow<'a, str>, 461 | /// Code that will be inserted into the PropertyType enum 462 | extra_property_type: Cow<'a, str>, 463 | /// Code that will be inserted into the PropertyList enum 464 | /// (type, ref type, enum name) 465 | extra_property_list: Vec<(Cow<'a, str>, Cow<'a, str>, Cow<'a, str>)>, 466 | /// Code that will be inserted into the properties() function 467 | extra_properties: Cow<'a, str>, 468 | /// Arguments that are taken by the constructor 469 | constructor_args: Cow<'a, str>, 470 | /// If the resulting struct is public 471 | public: bool, 472 | // What should be done for this struct 473 | do_struct: bool, 474 | do_impl: bool, 475 | do_api_impl: bool, 476 | do_update: bool, 477 | do_constructor: bool, 478 | do_properties: bool, 479 | } 480 | 481 | #[derive(Default, Clone)] 482 | struct StructBuilder<'a> { 483 | name: Cow<'a, str>, 484 | api_name: Cow<'a, str>, 485 | documentation: Cow<'a, str>, 486 | properties: Vec>, 487 | extra_attributes: Cow<'a, str>, 488 | extra_initialisation: Cow<'a, str>, 489 | extra_creation: Cow<'a, str>, 490 | extra_implementation: Cow<'a, str>, 491 | extra_property_type: Cow<'a, str>, 492 | extra_property_list: Vec<(Cow<'a, str>, Cow<'a, str>, Cow<'a, str>)>, 493 | extra_properties: Cow<'a, str>, 494 | constructor_args: Cow<'a, str>, 495 | public: bool, 496 | do_struct: bool, 497 | do_impl: bool, 498 | do_api_impl: bool, 499 | do_update: bool, 500 | do_constructor: bool, 501 | do_properties: bool, 502 | } 503 | 504 | #[allow(dead_code)] 505 | impl<'a> StructBuilder<'a> { 506 | fn new() -> StructBuilder<'a> { 507 | let mut result = Self::default(); 508 | result.do_struct = true; 509 | result.do_impl = true; 510 | result.do_constructor = true; 511 | result.do_update = true; 512 | result.do_api_impl = false; 513 | result 514 | } 515 | 516 | fn name>>(&mut self, name: S) -> StructBuilder<'a> { 517 | let mut res = self.clone(); 518 | res.name = name.into(); 519 | res 520 | } 521 | 522 | fn api_name>>(&mut self, api_name: S) -> StructBuilder<'a> { 523 | let mut res = self.clone(); 524 | res.api_name = api_name.into(); 525 | res 526 | } 527 | 528 | fn documentation>>(&mut self, documentation: S) -> StructBuilder<'a> { 529 | let mut res = self.clone(); 530 | res.documentation = documentation.into(); 531 | res 532 | } 533 | 534 | fn properties(&mut self, properties: Vec>) -> StructBuilder<'a> { 535 | let mut res = self.clone(); 536 | res.properties = properties; 537 | res 538 | } 539 | 540 | fn extra_attributes>>( 541 | &mut self, extra_attributes: S, 542 | ) -> StructBuilder<'a> { 543 | let mut res = self.clone(); 544 | res.extra_attributes = extra_attributes.into(); 545 | res 546 | } 547 | 548 | fn extra_initialisation>>( 549 | &mut self, extra_initialisation: S, 550 | ) -> StructBuilder<'a> { 551 | let mut res = self.clone(); 552 | res.extra_initialisation = extra_initialisation.into(); 553 | res 554 | } 555 | 556 | fn extra_property_type>>( 557 | &mut self, extra_property_type: S, 558 | ) -> StructBuilder<'a> { 559 | let mut res = self.clone(); 560 | res.extra_property_type = extra_property_type.into(); 561 | res 562 | } 563 | 564 | fn extra_property_list( 565 | &mut self, extra_property_list: Vec<(Cow<'a, str>, Cow<'a, str>, Cow<'a, str>)>, 566 | ) -> StructBuilder<'a> { 567 | let mut res = self.clone(); 568 | res.extra_property_list = extra_property_list; 569 | res 570 | } 571 | 572 | fn extra_properties>>( 573 | &mut self, extra_properties: S, 574 | ) -> StructBuilder<'a> { 575 | let mut res = self.clone(); 576 | res.extra_properties = extra_properties.into(); 577 | res 578 | } 579 | 580 | fn extra_creation>>(&mut self, extra_creation: S) -> StructBuilder<'a> { 581 | let mut res = self.clone(); 582 | res.extra_creation = extra_creation.into(); 583 | res 584 | } 585 | 586 | fn extra_implementation>>( 587 | &mut self, extra_implementation: S, 588 | ) -> StructBuilder<'a> { 589 | let mut res = self.clone(); 590 | res.extra_implementation = extra_implementation.into(); 591 | res 592 | } 593 | 594 | fn constructor_args>>( 595 | &mut self, constructor_args: S, 596 | ) -> StructBuilder<'a> { 597 | let mut res = self.clone(); 598 | res.constructor_args = constructor_args.into(); 599 | res 600 | } 601 | 602 | fn public(&mut self, public: bool) -> StructBuilder<'a> { 603 | let mut res = self.clone(); 604 | res.public = public; 605 | res 606 | } 607 | 608 | fn do_struct(&mut self, do_struct: bool) -> StructBuilder<'a> { 609 | let mut res = self.clone(); 610 | res.do_struct = do_struct; 611 | res 612 | } 613 | 614 | fn do_impl(&mut self, do_impl: bool) -> StructBuilder<'a> { 615 | let mut res = self.clone(); 616 | res.do_impl = do_impl; 617 | res 618 | } 619 | 620 | fn do_api_impl(&mut self, do_api_impl: bool) -> StructBuilder<'a> { 621 | let mut res = self.clone(); 622 | res.do_api_impl = do_api_impl; 623 | res 624 | } 625 | 626 | fn do_update(&mut self, do_update: bool) -> StructBuilder<'a> { 627 | let mut res = self.clone(); 628 | res.do_update = do_update; 629 | res 630 | } 631 | 632 | fn do_constructor(&mut self, do_constructor: bool) -> StructBuilder<'a> { 633 | let mut res = self.clone(); 634 | res.do_constructor = do_constructor; 635 | res 636 | } 637 | 638 | fn do_properties(&mut self, do_properties: bool) -> StructBuilder<'a> { 639 | let mut res = self.clone(); 640 | res.do_properties = do_properties; 641 | res 642 | } 643 | 644 | fn finalize(self) -> Struct<'a> { 645 | Struct { 646 | name: self.name, 647 | api_name: self.api_name, 648 | documentation: self.documentation, 649 | // Move the contents of the properties 650 | properties: self.properties.clone(), 651 | extra_attributes: self.extra_attributes, 652 | extra_initialisation: self.extra_initialisation, 653 | extra_creation: self.extra_creation, 654 | extra_implementation: self.extra_implementation, 655 | extra_property_type: self.extra_property_type, 656 | extra_property_list: self.extra_property_list, 657 | extra_properties: self.extra_properties, 658 | constructor_args: self.constructor_args, 659 | public: self.public, 660 | do_struct: self.do_struct, 661 | do_impl: self.do_impl, 662 | do_api_impl: self.do_api_impl, 663 | do_update: self.do_update, 664 | do_constructor: self.do_constructor, 665 | do_properties: self.do_properties, 666 | } 667 | } 668 | } 669 | 670 | impl<'a> Struct<'a> { 671 | fn create_struct( 672 | &self, f: &mut dyn Write, tera: &Tera, all_structs: &[&Struct<'static>], 673 | ) -> Result<()> { 674 | let mut context = tera::Context::new(); 675 | context.insert("s", &self); 676 | context.insert("all_structs", &all_structs); 677 | 678 | // Assemble properties 679 | let mut properties = Vec::<&Property<'static>>::new(); 680 | for s in all_structs.iter().filter(|s| s.api_name == self.api_name) { 681 | for p in &s.properties { 682 | if properties.iter().all(|p2| p.name != p2.name) { 683 | properties.push(p); 684 | } 685 | } 686 | } 687 | context.insert("properties", &properties); 688 | 689 | // Assemble property_types 690 | let mut property_types = Vec::<(_, _)>::new(); 691 | for s in all_structs.iter().filter(|s| s.api_name == self.api_name) { 692 | for p in &s.properties { 693 | let t = p.type_s.to_string(); 694 | if p.result && p.api_getter && property_types.iter().all(|p| p.0 != t) { 695 | property_types.push((t, p.create_ref_type())); 696 | } 697 | } 698 | for &(ref t, ref r, _) in &s.extra_property_list { 699 | let t = t.as_ref(); 700 | let r = r.as_ref(); 701 | if property_types.iter().all(|p| p.0 != t) { 702 | property_types.push((r.to_string(), t.to_string())); 703 | } 704 | } 705 | } 706 | context.insert("property_types", &property_types); 707 | 708 | let s = tera.render("struct.rs.tera", &context)?; 709 | f.write_all(s.as_bytes())?; 710 | Ok(()) 711 | } 712 | } 713 | 714 | impl<'a> serde::Serialize for Struct<'a> { 715 | fn serialize( 716 | &self, serializer: S, 717 | ) -> std::result::Result { 718 | let mut s = serializer.serialize_struct("Struct", 19)?; 719 | 720 | // Attributes 721 | s.serialize_field("name", &self.name)?; 722 | s.serialize_field("api_name", &self.api_name)?; 723 | let documentation = 724 | self.documentation.lines().map(|l| format!("/// {}\n", l)).collect::(); 725 | s.serialize_field("documentation", &documentation)?; 726 | s.serialize_field("properties", &self.properties)?; 727 | s.serialize_field("extra_attributes", &self.extra_attributes)?; 728 | s.serialize_field("extra_initialisation", &self.extra_initialisation)?; 729 | s.serialize_field("extra_creation", &self.extra_creation)?; 730 | s.serialize_field("extra_implementation", &self.extra_implementation)?; 731 | s.serialize_field("extra_property_type", &self.extra_property_type)?; 732 | s.serialize_field("extra_property_list", &self.extra_property_list)?; 733 | s.serialize_field("extra_properties", &self.extra_properties)?; 734 | s.serialize_field("constructor_args", &self.constructor_args)?; 735 | s.serialize_field("public", &self.public)?; 736 | s.serialize_field("do_struct", &self.do_struct)?; 737 | s.serialize_field("do_impl", &self.do_impl)?; 738 | s.serialize_field("do_api_impl", &self.do_api_impl)?; 739 | s.serialize_field("do_update", &self.do_update)?; 740 | s.serialize_field("do_constructor", &self.do_constructor)?; 741 | s.serialize_field("do_properties", &self.do_properties)?; 742 | 743 | s.end() 744 | } 745 | } 746 | 747 | // Build parts of lib.rs as most of the structs are very repetitive 748 | fn main() -> Result<()> { 749 | let out_dir = env::var("OUT_DIR")?; 750 | for f in 751 | &["build.rs", "channel.rs", "connection.rs", "server.rs", "struct.rs.tera", "macros.tera"] 752 | { 753 | println!("cargo:rerun-if-changed=build/{}", f); 754 | } 755 | 756 | let mut tera = Tera::new("build/*.tera")?; 757 | tera.register_filter("indent", |value: &tera::Value, args: &HashMap| { 758 | if let Some(&tera::Value::Number(ref n)) = args.get("depth") { 759 | if let tera::Value::String(s) = value { 760 | if let Some(n) = n.as_u64() { 761 | Ok(tera::Value::String(indent(s, n as usize))) 762 | } else { 763 | Err("the indent depth must be a number".into()) 764 | } 765 | } else { 766 | Err("indent expects a string to filter".into()) 767 | } 768 | } else { 769 | Err("Expected argument 'depth' for indent".into()) 770 | } 771 | }); 772 | tera.register_filter("title", |value: &tera::Value, _: &_| { 773 | if let tera::Value::String(ref s) = value { 774 | Ok(tera::Value::String(to_pascal_case(s))) 775 | } else { 776 | Err("title expects a string to filter".into()) 777 | } 778 | }); 779 | tera.register_filter("simplify", |value: &tera::Value, _: &_| { 780 | if let tera::Value::String(s) = value { 781 | Ok(tera::Value::String(s.replace(&['<', '>', ',', ' '] as &[char], ""))) 782 | } else { 783 | Err("title expects a string to filter".into()) 784 | } 785 | }); 786 | 787 | let path = Path::new(&out_dir); 788 | 789 | let channel_f = File::create(&path.join("channel.rs"))?; 790 | let connection_f = File::create(&path.join("connection.rs"))?; 791 | let server_f = File::create(&path.join("server.rs"))?; 792 | 793 | let mut files = vec![channel_f, connection_f, server_f]; 794 | let structs = vec![channel::create(), connection::create(), server::create()]; 795 | 796 | let mut all_structs = Vec::new(); 797 | for s in &structs { 798 | all_structs.extend(s); 799 | } 800 | 801 | for (i, ss) in structs.iter().enumerate() { 802 | let f = &mut files[i]; 803 | for s in ss { 804 | s.create_struct(f, &tera, all_structs.as_slice())?; 805 | } 806 | } 807 | 808 | Ok(()) 809 | } 810 | 811 | fn to_pascal_case>(text: S) -> String { 812 | let sref = text.as_ref(); 813 | let mut s = String::with_capacity(sref.len()); 814 | let mut uppercase = true; 815 | for c in sref.chars() { 816 | if c == '_' { 817 | uppercase = true; 818 | } else { 819 | if uppercase { 820 | s.push(c.to_uppercase().next().unwrap()); 821 | uppercase = false; 822 | } else { 823 | s.push(c); 824 | } 825 | } 826 | } 827 | s 828 | } 829 | 830 | /// Indent a string by a given count using tabs. 831 | fn indent>(s: S, count: usize) -> String { 832 | let sref = s.as_ref(); 833 | let line_count = sref.lines().count(); 834 | let mut result = String::with_capacity(sref.len() + line_count * count * 4); 835 | for l in sref.lines() { 836 | if !l.is_empty() { 837 | result.push_str(std::iter::repeat("\t").take(count).collect::().as_str()); 838 | } 839 | result.push_str(l); 840 | result.push('\n'); 841 | } 842 | result 843 | } 844 | -------------------------------------------------------------------------------- /build/channel.rs: -------------------------------------------------------------------------------- 1 | use *; 2 | 3 | pub(crate) fn create() -> Vec> { 4 | // Map types to functions that will get that type 5 | let default_functions = { 6 | let mut m = Map::new(); 7 | m.insert("i32", "ChannelData::get_property_as_int"); 8 | m.insert("u64", "ChannelData::get_property_as_uint64"); 9 | m.insert("String", "ChannelData::get_property_as_string"); 10 | m 11 | }; 12 | let transmutable = vec!["CodecType", "HostbannerMode"]; 13 | 14 | let builder = PropertyBuilder::new() 15 | .functions(default_functions) 16 | .transmutable(transmutable) 17 | .default_args("server_id, id, ") 18 | .default_args_update("self.server_id, self.id, ") 19 | .enum_name("ChannelProperties"); 20 | let builder_string = builder.type_s("String"); 21 | let builder_i32 = builder.type_s("i32"); 22 | let builder_bool = builder.type_s("bool"); 23 | 24 | let channel = StructBuilder::new() 25 | .name("ChannelData") 26 | .api_name("Channel") 27 | .do_api_impl(true) 28 | .do_properties(true) 29 | .constructor_args("server_id: ServerId, id: ChannelId") 30 | .extra_property_list(vec![( 31 | "Option>".into(), 32 | "OptionChannel".into(), 33 | "ParentChannel,".into(), 34 | )]) 35 | .extra_properties( 36 | "\ 37 | ChannelProperty::OptionChannel {\n\tproperty: \ 38 | ChannelOptionChannelProperty::ParentChannel,\n\tdata: self.get_parent_channel(),\n},", 39 | ) 40 | .properties(vec![ 41 | builder.name("id").type_s("ChannelId").result(false).api_getter(false).finalize(), 42 | builder.name("server_id").type_s("ServerId").result(false).api_getter(false).finalize(), 43 | builder 44 | .name("parent_channel_id") 45 | .type_s("ChannelId") 46 | .update("Self::query_parent_channel_id(self.server_id, self.id)") 47 | .documentation("The id of the parent channel, 0 if there is no parent channel") 48 | .api_getter(false) 49 | .finalize(), 50 | builder_string.name("name").finalize(), 51 | builder_string.name("topic").finalize(), 52 | builder.name("codec").type_s("CodecType").finalize(), 53 | builder_i32.name("codec_quality").finalize(), 54 | builder_i32.name("max_clients").finalize(), 55 | builder_i32.name("max_family_clients").finalize(), 56 | builder_i32.name("order").finalize(), 57 | builder_bool.name("permanent").value_name("FlagPermanent").finalize(), 58 | builder_bool.name("semi_permanent").value_name("FlagSemiPermanent").finalize(), 59 | builder_bool.name("default").value_name("FlagDefault").finalize(), 60 | builder_bool.name("password").value_name("FlagPassword").finalize(), 61 | builder_i32.name("codec_latency_factor").finalize(), 62 | builder_bool.name("codec_is_unencrypted").finalize(), 63 | builder_i32.name("delete_delay").finalize(), 64 | builder_bool 65 | .name("max_clients_unlimited") 66 | .value_name("FlagMaxClientsUnlimited") 67 | .finalize(), 68 | builder_bool 69 | .name("max_family_clients_unlimited") 70 | .value_name("FlagMaxFamilyClientsUnlimited") 71 | .finalize(), 72 | // Clone so we can change the documentation 73 | builder_bool 74 | .name("subscribed") 75 | .value_name("FlagAreSubscribed") 76 | .documentation("If we are subscribed to this channel") 77 | .finalize(), 78 | builder_i32.name("needed_talk_power").finalize(), 79 | builder_i32.name("forced_silence").finalize(), 80 | builder_string.name("phonetic_name").value_name("NamePhonetic").finalize(), 81 | builder_i32.name("icon_id").finalize(), 82 | builder_string.name("banner_gfx_url").value_name("BannerGfxUrl").finalize(), 83 | builder 84 | .name("banner_mode") 85 | .value_name("BannerMode") 86 | .type_s("HostbannerMode") 87 | .finalize(), 88 | // Requested 89 | builder_string.name("description").requested(true).finalize(), 90 | ]) 91 | .finalize(); 92 | 93 | vec![channel] 94 | } 95 | -------------------------------------------------------------------------------- /build/connection.rs: -------------------------------------------------------------------------------- 1 | use *; 2 | 3 | pub(crate) fn create() -> Vec> { 4 | // Map types to functions that will get that type 5 | let default_functions = { 6 | let mut m = Map::new(); 7 | m.insert("i32", "ConnectionData::get_connection_property_as_uint64"); 8 | m.insert("u64", "ConnectionData::get_connection_property_as_uint64"); 9 | m.insert("String", "ConnectionData::get_connection_property_as_string"); 10 | m 11 | }; 12 | let client_functions = { 13 | let mut m = Map::new(); 14 | m.insert("i32", "ConnectionData::get_client_property_as_int"); 15 | m.insert("String", "ConnectionData::get_client_property_as_string"); 16 | m 17 | }; 18 | let transmutable = vec![ 19 | "InputDeactivationStatus", 20 | "TalkStatus", 21 | "MuteInputStatus", 22 | "MuteOutputStatus", 23 | "HardwareInputStatus", 24 | "HardwareOutputStatus", 25 | "AwayStatus", 26 | ]; 27 | 28 | let builder = PropertyBuilder::new() 29 | .functions(default_functions) 30 | .transmutable(transmutable) 31 | .default_args("server_id, id, ") 32 | .default_args_update("self.server_id, self.id, ") 33 | .enum_name("ConnectionProperties"); 34 | let builder_string = builder.type_s("String"); 35 | let builder_u64 = builder.type_s("u64"); 36 | 37 | let client_b = builder.enum_name("ClientProperties").functions(client_functions); 38 | let client_b_string = client_b.type_s("String"); 39 | let client_b_i32 = client_b.type_s("i32"); 40 | 41 | let builder_r = builder.requested(true); 42 | let builder_string_r = builder_string.requested(true); 43 | let builder_u64_r = builder_u64.requested(true); 44 | let client_b_r = client_b.requested(true); 45 | let client_b_string_r = client_b_string.requested(true); 46 | let client_b_i32_r = client_b_i32.requested(true); 47 | 48 | let connection = StructBuilder::new() 49 | .name("ConnectionData") 50 | .api_name("Connection") 51 | .do_api_impl(true) 52 | .do_properties(true) 53 | .constructor_args("server_id: ServerId, id: ConnectionId") 54 | .extra_property_list(vec![( 55 | "Channel<'a>".into(), 56 | "Channel".into(), 57 | "Channel,\nChannelGroupInheritedChannel,".into(), 58 | )]) 59 | .extra_properties( 60 | "\ 61 | ConnectionProperty::Channel {\n\tproperty: ConnectionChannelProperty::Channel,\n\tdata: \ 62 | self.get_channel(),\n}, 63 | ConnectionProperty::Channel {\n\tproperty: \ 64 | ConnectionChannelProperty::ChannelGroupInheritedChannel,\n\tdata: \ 65 | self.get_channel_group_inherited_channel(),\n},", 66 | ) 67 | .properties(vec![ 68 | builder.name("id").type_s("ConnectionId").result(false).api_getter(false).finalize(), 69 | builder.name("server_id").type_s("ServerId").result(false).api_getter(false).finalize(), 70 | builder 71 | .name("channel_id") 72 | .type_s("ChannelId") 73 | .update("Self::query_channel_id(self.server_id, self.id)") 74 | .api_getter(false) 75 | .finalize(), 76 | // ClientProperties 77 | client_b_string.name("uid").value_name("UniqueIdentifier").finalize(), 78 | client_b_string.name("name").value_name("Nickname").finalize(), 79 | client_b.name("talking").type_s("TalkStatus").value_name("FlagTalking").finalize(), 80 | client_b 81 | .name("whispering") 82 | .type_s("bool") 83 | .update("Self::query_whispering(self.server_id, self.id)") 84 | .finalize(), 85 | client_b.name("away").type_s("AwayStatus").finalize(), 86 | client_b_string.name("away_message").finalize(), 87 | client_b.name("input_muted").type_s("MuteInputStatus").finalize(), 88 | client_b.name("output_muted").type_s("MuteOutputStatus").finalize(), 89 | client_b.name("output_only_muted").type_s("MuteOutputStatus").finalize(), 90 | client_b.name("input_hardware").type_s("HardwareInputStatus").finalize(), 91 | client_b.name("output_hardware").type_s("HardwareOutputStatus").finalize(), 92 | client_b_string.name("phonetic_name").value_name("NicknamePhonetic").finalize(), 93 | client_b.name("recording").type_s("bool").value_name("IsRecording").finalize(), 94 | client_b 95 | .name("database_id") 96 | .type_s("u64") 97 | .documentation("Only valid data if we have the appropriate permissions.") 98 | .finalize(), 99 | client_b.name("channel_group_id").type_s("ChannelGroupId").finalize(), 100 | client_b.name("server_groups").type_s("Vec").finalize(), 101 | client_b.name("talk_power").type_s("i32").finalize(), 102 | // When this client requested to talk 103 | client_b.name("talk_request").type_s("DateTime").finalize(), 104 | client_b 105 | .name("talk_request_message") 106 | .type_s("String") 107 | .value_name("TalkRequestMsg") 108 | .finalize(), 109 | client_b 110 | .name("channel_group_inherited_channel_id") 111 | .type_s("ChannelId") 112 | .api_getter(false) 113 | .documentation("The channel that sets the current channel id of this client.") 114 | .finalize(), 115 | // Only for own client 116 | builder_string.name("server_ip").documentation("Only available for oneself").finalize(), 117 | builder 118 | .name("server_port") 119 | .type_s("u16") 120 | .documentation("Only available for oneself") 121 | .finalize(), 122 | client_b 123 | .name("input_deactivated") 124 | .type_s("InputDeactivationStatus") 125 | .documentation("Only available for oneself") 126 | .finalize(), 127 | client_b 128 | .name("default_channel") 129 | .type_s("ChannelId") 130 | .documentation("Only available for oneself") 131 | .finalize(), 132 | client_b_string 133 | .name("default_token") 134 | .documentation("Only available for oneself") 135 | .finalize(), 136 | // Only for server queries 137 | client_b_string 138 | .name("login_name") 139 | .documentation("Only available for server queries") 140 | .finalize(), 141 | client_b_string 142 | .name("login_password") 143 | .documentation("Only available for server queries") 144 | .finalize(), 145 | // Requested 146 | client_b_string_r.name("version").finalize(), 147 | client_b_string_r.name("platform").finalize(), 148 | client_b_r.name("created").type_s("DateTime").finalize(), 149 | client_b_r 150 | .name("last_connected") 151 | .type_s("DateTime") 152 | .value_name("Lastconnected") 153 | .finalize(), 154 | client_b_i32_r.name("total_connections").value_name("Totalconnections").finalize(), 155 | builder_r.name("ping").type_s("Duration").finalize(), 156 | builder_r.name("ping_deviation").type_s("Duration").finalize(), 157 | builder_r.name("connected_time").type_s("Duration").finalize(), 158 | builder_r.name("idle_time").type_s("Duration").finalize(), 159 | builder_string_r.name("client_ip").finalize(), 160 | builder_r 161 | .name("client_port") 162 | .type_s("u16") 163 | .update( 164 | "ConnectionData::get_connection_property_as_uint64(self.server_id, self.id, \ 165 | ConnectionProperties::ClientPort).map(|p| p as u16)", 166 | ) 167 | .finalize(), 168 | // Network 169 | builder_u64_r.name("packets_sent_speech").finalize(), 170 | builder_u64_r.name("packets_sent_keepalive").finalize(), 171 | builder_u64_r.name("packets_sent_control").finalize(), 172 | builder_u64_r.name("packets_sent_total").finalize(), 173 | builder_u64_r.name("bytes_sent_speech").finalize(), 174 | builder_u64_r.name("bytes_sent_keepalive").finalize(), 175 | builder_u64_r.name("bytes_sent_control").finalize(), 176 | builder_u64_r.name("bytes_sent_total").finalize(), 177 | builder_u64_r.name("packets_received_speech").finalize(), 178 | builder_u64_r.name("packets_received_keepalive").finalize(), 179 | builder_u64_r.name("packets_received_control").finalize(), 180 | builder_u64_r.name("packets_received_total").finalize(), 181 | builder_u64_r.name("bytes_received_speech").finalize(), 182 | builder_u64_r.name("bytes_received_keepalive").finalize(), 183 | builder_u64_r.name("bytes_received_control").finalize(), 184 | builder_u64_r.name("bytes_received_total").finalize(), 185 | builder_u64_r.name("packetloss_speech").finalize(), 186 | builder_u64_r.name("packetloss_keepalive").finalize(), 187 | builder_u64_r.name("packetloss_control").finalize(), 188 | builder_u64_r.name("packetloss_total").finalize(), 189 | builder_u64_r 190 | .name("server_to_client_packetloss_speech") 191 | .value_name("Server2ClientPacketlossSpeech") 192 | .finalize(), 193 | builder_u64_r 194 | .name("server_to_client_packetloss_keepalive") 195 | .value_name("Server2ClientPacketlossKeepalive") 196 | .finalize(), 197 | builder_u64_r 198 | .name("server_to_client_packetloss_control") 199 | .value_name("Server2ClientPacketlossControl") 200 | .finalize(), 201 | builder_u64_r 202 | .name("server_to_client_packetloss_total") 203 | .value_name("Server2ClientPacketlossTotal") 204 | .finalize(), 205 | builder_u64_r 206 | .name("client_to_server_packetloss_speech") 207 | .value_name("Client2ServerPacketlossSpeech") 208 | .finalize(), 209 | builder_u64_r 210 | .name("client_to_server_packetloss_keepalive") 211 | .value_name("Client2ServerPacketlossKeepalive") 212 | .finalize(), 213 | builder_u64_r 214 | .name("client_to_server_packetloss_control") 215 | .value_name("Client2ServerPacketlossControl") 216 | .finalize(), 217 | builder_u64_r 218 | .name("client_to_server_packetloss_total") 219 | .value_name("Client2ServerPacketlossTotal") 220 | .finalize(), 221 | builder_u64_r.name("bandwidth_sent_last_second_speech").finalize(), 222 | builder_u64_r.name("bandwidth_sent_last_second_keepalive").finalize(), 223 | builder_u64_r.name("bandwidth_sent_last_second_control").finalize(), 224 | builder_u64_r.name("bandwidth_sent_last_second_total").finalize(), 225 | builder_u64_r.name("bandwidth_sent_last_minute_speech").finalize(), 226 | builder_u64_r.name("bandwidth_sent_last_minute_keepalive").finalize(), 227 | builder_u64_r.name("bandwidth_sent_last_minute_control").finalize(), 228 | builder_u64_r.name("bandwidth_sent_last_minute_total").finalize(), 229 | builder_u64_r.name("bandwidth_received_last_second_speech").finalize(), 230 | builder_u64_r.name("bandwidth_received_last_second_keepalive").finalize(), 231 | builder_u64_r.name("bandwidth_received_last_second_control").finalize(), 232 | builder_u64_r.name("bandwidth_received_last_second_total").finalize(), 233 | builder_u64_r.name("bandwidth_received_last_minute_speech").finalize(), 234 | builder_u64_r.name("bandwidth_received_last_minute_keepalive").finalize(), 235 | builder_u64_r.name("bandwidth_received_last_minute_control").finalize(), 236 | builder_u64_r.name("bandwidth_received_last_minute_total").finalize(), 237 | // End network 238 | client_b_i32_r.name("month_bytes_uploaded").finalize(), 239 | client_b_i32_r.name("month_bytes_downloaded").finalize(), 240 | client_b_i32_r.name("total_bytes_uploaded").finalize(), 241 | client_b_i32_r.name("total_bytes_downloaded").finalize(), 242 | client_b_string_r.name("default_channel_password").finalize(), 243 | client_b_string_r.name("server_password").finalize(), 244 | client_b_r 245 | .name("is_muted") 246 | .type_s("bool") 247 | .documentation("If the client is locally muted.") 248 | .finalize(), 249 | client_b_i32_r.name("volume_modificator").finalize(), 250 | client_b_r.name("version_sign").type_s("bool").finalize(), 251 | client_b_r.name("avatar").type_s("bool").value_name("FlagAvatar").finalize(), 252 | client_b_string_r.name("description").finalize(), 253 | client_b_r.name("talker").type_s("bool").value_name("IsTalker").finalize(), 254 | client_b_r 255 | .name("priority_speaker") 256 | .type_s("bool") 257 | .value_name("IsPrioritySpeaker") 258 | .finalize(), 259 | client_b_r.name("unread_messages").type_s("bool").finalize(), 260 | client_b_i32_r.name("needed_serverquery_view_power").finalize(), 261 | client_b_i32_r.name("icon_id").finalize(), 262 | client_b_r.name("is_channel_commander").type_s("bool").finalize(), 263 | client_b_string_r.name("country").finalize(), 264 | client_b_string_r.name("badges").finalize(), 265 | client_b_string_r.name("myteamspeak_id").finalize(), 266 | client_b_string_r.name("integrations").finalize(), 267 | client_b_string_r.name("active_integrations_info").finalize(), 268 | ]) 269 | .finalize(); 270 | 271 | vec![connection] 272 | } 273 | -------------------------------------------------------------------------------- /build/macros.tera: -------------------------------------------------------------------------------- 1 | {% macro do_doc(prop,depth) %} 2 | {% if prop.documentation %} 3 | {{ prop.documentation | indent(depth=depth) }}{% endif %} 4 | {% if prop.requested %} 5 | {% filter indent(depth=depth) %} 6 | {% if prop.documentation %} 7 | /// 8 | {% endif %} 9 | /// Only available if requested explicitly through `request_data`. 10 | {% endfilter %} 11 | {% endif %} 12 | {% endmacro do_doc %} 13 | -------------------------------------------------------------------------------- /build/server.rs: -------------------------------------------------------------------------------- 1 | use *; 2 | 3 | pub(crate) fn create() -> Vec> { 4 | // Map types to functions that will get that type 5 | let default_functions = { 6 | let mut m = Map::new(); 7 | m.insert("i32", "ServerData::get_property_as_int"); 8 | m.insert("u64", "ServerData::get_property_as_uint64"); 9 | m.insert("String", "ServerData::get_property_as_string"); 10 | m 11 | }; 12 | let transmutable = vec!["CodecEncryptionMode", "HostbannerMode", "HostmessageMode"]; 13 | 14 | let builder = PropertyBuilder::new() 15 | .functions(default_functions) 16 | .transmutable(transmutable) 17 | .default_args("id, ") 18 | .default_args_update("self.id, ") 19 | .enum_name("VirtualServerProperties"); 20 | let builder_string = builder.type_s("String"); 21 | let builder_i32 = builder.type_s("i32"); 22 | 23 | let builder_r = builder.requested(true); 24 | let builder_string_r = builder_string.requested(true); 25 | let builder_i32_r = builder_i32.requested(true); 26 | 27 | let server = StructBuilder::new() 28 | .name("ServerData") 29 | .api_name("Server") 30 | .do_api_impl(true) 31 | .do_properties(true) 32 | .constructor_args("id: ServerId") 33 | .extra_property_list(vec![( 34 | "Connection<'a>".into(), 35 | "Connection".into(), 36 | "OwnConnection,".into(), 37 | )]) 38 | .extra_properties( 39 | "\ 40 | ServerProperty::Connection {\n\tproperty: ServerConnectionProperty::OwnConnection,\n\tdata: \ 41 | self.get_own_connection(),\n},", 42 | ) 43 | .properties(vec![ 44 | builder 45 | .name("id") 46 | .type_s("ServerId") 47 | .result(false) 48 | .initialisation("id") 49 | .should_update(false) 50 | .api_getter(false) 51 | .finalize(), 52 | builder_string.name("uid").value_name("UniqueIdentifier").finalize(), 53 | builder 54 | .name("own_connection_id") 55 | .type_s("ConnectionId") 56 | .update("Self::query_own_connection_id(self.id)") 57 | .api_getter(false) 58 | .finalize(), 59 | builder_string.name("name").finalize(), 60 | builder_string.name("phonetic_name").value_name("NamePhonetic").finalize(), 61 | builder_string.name("platform").finalize(), 62 | builder_string.name("version").finalize(), 63 | builder_string.name("nickname").finalize(), 64 | builder_string.name("accounting_token").finalize(), 65 | // TODO Always zero when queried as string, int or uint64 66 | builder.name("created").type_s("DateTime").finalize(), 67 | builder.name("codec_encryption_mode").type_s("CodecEncryptionMode").finalize(), 68 | // TODO Update 69 | builder.name("default_server_group").type_s("ServerGroupId").finalize(), 70 | builder.name("default_channel_group").type_s("ChannelGroupId").finalize(), 71 | builder.name("default_channel_admin_group").type_s("ChannelGroupId").finalize(), 72 | // End TODO Update 73 | builder_string.name("hostbanner_url").finalize(), 74 | builder_string.name("hostbanner_gfx_url").finalize(), 75 | builder.name("hostbanner_gfx_interval").type_s("Duration").finalize(), 76 | builder.name("hostbanner_mode").type_s("HostbannerMode").finalize(), 77 | builder_i32.name("priority_speaker_dimm_modificator").finalize(), 78 | builder_string.name("hostbutton_tooltip").finalize(), 79 | builder_string.name("hostbutton_url").finalize(), 80 | builder_string.name("hostbutton_gfx_url").finalize(), 81 | builder_i32.name("icon_id").finalize(), 82 | builder_i32.name("reserved_slots").finalize(), 83 | builder.name("ask_for_privilegekey").type_s("bool").finalize(), 84 | builder.name("channel_temp_delete_delay_default").type_s("Duration").finalize(), 85 | builder 86 | .name("visible_connections") 87 | .type_s("Map") 88 | .result(false) 89 | .initialisation("Map::new()") 90 | .update("Self::query_connections(self.id)") 91 | .api_getter(false) 92 | .finalize(), 93 | builder 94 | .name("channels") 95 | .type_s("Map") 96 | .update("Self::query_channels(self.id)") 97 | .api_getter(false) 98 | .finalize(), 99 | // TODO requested 100 | builder_string_r.name("welcome_message").value_name("Welcomemessage").finalize(), 101 | builder_i32_r.name("max_clients").finalize(), 102 | builder_i32_r.name("clients_online").finalize(), 103 | builder_i32_r.name("channels_online").finalize(), 104 | builder_i32_r.name("client_connections").finalize(), 105 | builder_i32_r.name("query_client_connections").finalize(), 106 | builder_i32_r.name("query_clients_online").value_name("QueryclientsOnline").finalize(), 107 | builder_r.name("uptime").type_s("Duration").finalize(), 108 | builder_r.name("password").type_s("bool").finalize(), 109 | builder_i32_r.name("max_download_total_bandwidth").finalize(), 110 | builder_i32_r.name("max_upload_total_bandwidth").finalize(), 111 | builder_i32_r.name("download_quota").finalize(), 112 | builder_i32_r.name("upload_quota").finalize(), 113 | builder_i32_r.name("month_bytes_downloaded").finalize(), 114 | builder_i32_r.name("month_bytes_uploaded").finalize(), 115 | builder_i32_r.name("total_bytes_downloaded").finalize(), 116 | builder_i32_r.name("total_bytes_uploaded").finalize(), 117 | builder_i32_r.name("complain_autoban_count").finalize(), 118 | builder_r.name("complain_autoban_time").type_s("Duration").finalize(), 119 | builder_r.name("complain_remove_time").type_s("Duration").finalize(), 120 | builder_i32_r.name("min_clients_in_channel_before_forced_silence").finalize(), 121 | builder_i32_r.name("antiflood_points_tick_reduce").finalize(), 122 | builder_i32_r.name("antiflood_points_needed_command_block").finalize(), 123 | builder_i32_r.name("antiflood_points_needed_ip_block").finalize(), 124 | builder_i32_r.name("port").finalize(), 125 | builder_r.name("autostart").type_s("bool").finalize(), 126 | builder_i32_r.name("machine_id").finalize(), 127 | builder_i32_r.name("needed_identity_security_level").finalize(), 128 | builder_r.name("log_client").type_s("bool").finalize(), 129 | builder_r.name("log_query").type_s("bool").finalize(), 130 | builder_r.name("log_channel").type_s("bool").finalize(), 131 | builder_r.name("log_permissions").type_s("bool").finalize(), 132 | builder_r.name("log_server").type_s("bool").finalize(), 133 | builder_r.name("log_filetransfer").type_s("bool").finalize(), 134 | builder_string_r.name("min_client_version").finalize(), 135 | builder_i32_r.name("total_packetloss_speech").finalize(), 136 | builder_i32_r.name("total_packetloss_keepalive").finalize(), 137 | builder_i32_r.name("total_packetloss_control").finalize(), 138 | builder_i32_r.name("total_packetloss_total").finalize(), 139 | builder_i32_r.name("total_ping").finalize(), 140 | builder_r.name("weblist_enabled").type_s("bool").finalize(), 141 | builder_string 142 | .name("hostmessage") 143 | .documentation("Only set on connect and not updated") 144 | .finalize(), 145 | builder 146 | .name("hostmessage_mode") 147 | .type_s("HostmessageMode") 148 | .documentation("Only set on connect and not updated") 149 | .finalize(), 150 | builder_i32.name("antiflood_points_needed_plugin_block").finalize(), 151 | ]) 152 | .finalize(); 153 | 154 | vec![server] 155 | } 156 | -------------------------------------------------------------------------------- /build/struct.rs.tera: -------------------------------------------------------------------------------- 1 | {% import "macros.tera" as macros %} 2 | {% if s.do_struct %} 3 | {% if s.documentation %} 4 | /// {{ s.documentation }} 5 | {% endif %} 6 | #[derive(Debug, Clone)] 7 | {% if s.public %} 8 | pub {% endif %}struct {{ s.name }} { 9 | {% for prop in s.properties %} 10 | {{ prop.name }}: {% if prop.result %} 11 | Result<{{ prop.type_s }}, ::Error>{% else %} 12 | {{ prop.type_s }}{% endif %}, 13 | {% endfor %} 14 | 15 | {{ s.extra_attributes | indent(depth=1) }}} 16 | 17 | {% endif %} 18 | {% if s.do_impl or s.do_api_impl or s.do_update or s.do_constructor or s.extra_implementation %} 19 | {% set impl = true %} 20 | impl {{ s.name }} { 21 | {% endif %} 22 | {% if s.do_impl %} 23 | {% for prop in s.properties %} 24 | {% if prop.public %} 25 | {{ macros::do_doc(prop=prop,depth=1) }}{% endif %} 26 | #[inline] 27 | {% if prop.public %} 28 | pub {% else %} {% endif %} 29 | fn get_{{ prop.name }}(&self) -> {{ prop.return_type }} { 30 | {{ prop.getter_body }} 31 | } 32 | {% endfor %} 33 | 34 | {% endif %} 35 | {% if s.do_update %} 36 | {% for prop in s.properties %} 37 | {% if prop.update_body %} 38 | fn update_{{ prop.name }}(&mut self) { 39 | self.{{ prop.name }} = {{ prop.update_body }} 40 | } 41 | {% endif %} 42 | {% endfor %} 43 | 44 | fn update(&mut self) { 45 | {% for prop in s.properties %} 46 | {% if prop.update_body %} 47 | self.update_{{ prop.name }}(); 48 | {% endif %} 49 | {% endfor %} 50 | } 51 | 52 | fn update_from(&mut self, other: &Self) { 53 | {% for prop in s.properties %} 54 | {% if prop.result %} 55 | if self.{{ prop.name }}.is_err() { 56 | self.{{ prop.name }} = other.{{ prop.name }}.clone(); 57 | } 58 | {% endif %} 59 | {% endfor %} 60 | } 61 | 62 | {% endif %} 63 | {% if s.do_constructor %} 64 | fn new({{ s.constructor_args }}) -> {{ s.name }} { 65 | {{ s.extra_initialisation | indent(depth=2) }} 66 | {{ s.name }} { 67 | {% for prop in s.properties %} 68 | {{ prop.name }}: {{ prop.constructor_body }}, 69 | {% endfor %} 70 | 71 | {{ s.extra_creation | indent(depth=3) }} 72 | } 73 | } 74 | 75 | {% endif %} 76 | {% if s.do_impl %} 77 | {% if impl %} 78 | {{ s.extra_implementation | indent(depth=1) }}} 79 | 80 | {% endif %} 81 | {% if s.do_api_impl %} 82 | impl<'a> {{ s.api_name }}<'a> { 83 | {% for prop in s.properties %} 84 | {% if prop.api_getter %} 85 | {{ macros::do_doc(prop=prop,depth=1) }} #[inline] 86 | pub fn get_{{ prop.name }}(&self) -> {{ prop.return_type }} { 87 | match self.data { 88 | Ok(data) => data.get_{{ prop.name }}(), 89 | Err(_) => Err(Error::Ok), 90 | } 91 | } 92 | {% endif %} 93 | {% endfor %} 94 | {% if s.do_properties %} 95 | 96 | pub {% else %} {% endif %}fn properties(&self) -> Vec<{{ s.api_name }}Property> { 97 | let mut v = vec![ 98 | {% for prop in s.properties %} 99 | {% if prop.result and prop.api_getter %} 100 | {{ s.api_name }}Property::{{ prop.type_s | simplify | title }} { 101 | property: {{ s.api_name }}{{ prop.type_s | simplify | title }}Property::{{ prop.name | simplify | title }}, 102 | data: self.get_{{ prop.name }}(), 103 | }, 104 | {% endif %} 105 | {% endfor %} 106 | {{ s.extra_properties | indent(depth=3) }} 107 | ]; 108 | v.retain(|p| if let Some(Error::Ok) = p.error() { 109 | false 110 | } else { 111 | true 112 | }); 113 | v 114 | } 115 | {% endif %} 116 | } 117 | 118 | {% endif %} 119 | {% if s.do_properties %} 120 | {% for t in property_types %} 121 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 122 | pub enum {{ s.api_name }}{{ t.0 | simplify | title }}Property { 123 | {% for prop in properties %} 124 | {% if prop.result and prop.api_getter and prop.type_s == t.0 %} 125 | {{ macros::do_doc(prop=prop,depth=1) }}{{ prop.name | title }}, 126 | {% endif %} 127 | {% endfor %} 128 | {% for ps in s.extra_property_list %} 129 | {% if ps.0 == t.1 %} 130 | {{ ps.2 | indent(depth=1) }} 131 | {% endif %} 132 | {% endfor %} 133 | } 134 | {% endfor %} 135 | 136 | #[derive(Debug, PartialEq, Eq, Clone)] 137 | pub enum {{ s.api_name }}Property<'a> { 138 | {% for t in property_types %} 139 | {{ t.0 | simplify | title }} { 140 | property: {{ s.api_name }}{{ t.0 | simplify | title }}Property, 141 | data: Result<{{ t.1 }}, Error>, 142 | }, 143 | {% endfor %} 144 | {{ s.extra_property_type | indent(depth=1) }} 145 | } 146 | 147 | impl<'a> {{ s.api_name }}Property<'a> { 148 | pub fn error(&self) -> Option { 149 | match *self { 150 | {% for t in property_types %} 151 | {{ s.api_name }}Property::{{ t.0 | simplify | title }} { ref data, .. } => 152 | data.as_ref().err().cloned(), 153 | {% endfor %} 154 | } 155 | } 156 | pub fn property_eq(&self, other: &{{ s.api_name }}Property) -> bool { 157 | match *self { 158 | {% for t in property_types %} 159 | {{ s.api_name }}Property::{{ t.0 | simplify | title }} { ref property, .. } => 160 | if let {{ s.api_name }}Property::{{ t.0 | simplify | title }} { property: ref p2, .. } = *other { 161 | return property == p2; 162 | }, 163 | {% endfor %} 164 | } 165 | false 166 | } 167 | } 168 | 169 | pub type {{ s.api_name }}Changes<'a> = Vec<(Option<{{ s.api_name }}Property<'a>>, {{ s.api_name}}Property<'a>)>; 170 | 171 | fn get_{{ s.api_name | lower }}_changes<'a>(mut old: Vec<{{ s.api_name }}Property<'a>>, 172 | new: Vec<{{ s.api_name }}Property<'a>>) 173 | -> {{ s.api_name }}Changes<'a> { 174 | new.into_iter().filter_map(|p| { 175 | if let Some(i_old) = old.iter() 176 | .position(|p_old| p.property_eq(p_old)) { 177 | let p_old = old.remove(i_old); 178 | if p_old == p { 179 | None 180 | } else { 181 | Some((Some(p_old.clone()), p)) 182 | } 183 | } else { 184 | Some((None, p)) 185 | } 186 | }).collect() 187 | } 188 | {% endif %} 189 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["MiB", "GiB", "TiB", "PiB", "EiB", "TeamSpeak", "TeamSpeak3"] 2 | -------------------------------------------------------------------------------- /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 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; 88 | 89 | /// Converts a normal `String` to a `CString`. 90 | macro_rules! to_cstring { 91 | ($string: expr) => { 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) => {{ 99 | String::from_utf8_lossy(CStr::from_ptr($string).to_bytes()).into_owned() 100 | }}; 101 | } 102 | 103 | // Declare modules here so the macros are visible in the modules 104 | pub mod plugin; 105 | pub mod ts3interface; 106 | 107 | // Import automatically generated structs 108 | include!(concat!(env!("OUT_DIR"), "/channel.rs")); 109 | include!(concat!(env!("OUT_DIR"), "/connection.rs")); 110 | include!(concat!(env!("OUT_DIR"), "/server.rs")); 111 | 112 | /// The api functions provided by TeamSpeak 113 | /// 114 | /// This is not part of the official api and is only public to permit dirty 115 | /// hacks! 116 | #[doc(hidden)] 117 | pub static mut TS3_FUNCTIONS: Option = None; 118 | 119 | // ******************** Structs ******************** 120 | /// The possible receivers of a message. A message can be sent to a specific 121 | /// connection, to the current channel chat or to the server chat. 122 | #[derive(Clone)] 123 | pub enum MessageReceiver { 124 | Connection(ConnectionId), 125 | Channel, 126 | Server, 127 | } 128 | 129 | /// Permissions - TODO not yet implemented 130 | #[derive(Debug, PartialEq, Eq, Clone)] 131 | pub struct Permissions; 132 | 133 | /// A wrapper for a server id. 134 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 135 | pub struct ServerId(u64); 136 | 137 | /// A wrapper for a channel id. 138 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 139 | pub struct ChannelId(u64); 140 | 141 | /// A wrapper for a connection id. 142 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 143 | pub struct ConnectionId(u16); 144 | 145 | #[derive(Debug, Clone)] 146 | pub struct Permission {} 147 | 148 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 149 | pub struct PermissionId(u32); 150 | 151 | #[derive(Debug, Clone)] 152 | pub struct ServerGroup {} 153 | 154 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 155 | pub struct ServerGroupId(u64); 156 | 157 | #[derive(Debug, Clone)] 158 | pub struct ChannelGroup {} 159 | 160 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 161 | pub struct ChannelGroupId(u64); 162 | 163 | // ******************** Implementation ******************** 164 | 165 | // ********** Invoker ********** 166 | #[derive(Debug, Eq)] 167 | pub struct InvokerData { 168 | id: ConnectionId, 169 | uid: String, 170 | name: String, 171 | } 172 | 173 | impl PartialEq for InvokerData { 174 | fn eq(&self, other: &InvokerData) -> bool { self.id == other.id } 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 { self.id } 184 | 185 | /// Get the unique id of this invoker. 186 | pub fn get_uid(&self) -> &String { &self.uid } 187 | 188 | /// Get the name of this invoker. 189 | pub fn get_name(&self) -> &String { &self.name } 190 | } 191 | 192 | /// The invoker is maybe not visible to the user, but we can get events caused 193 | /// by him, so some information about him are passed along with his id. 194 | #[derive(Debug, Eq)] 195 | pub struct Invoker<'a> { 196 | server: Server<'a>, 197 | data: InvokerData, 198 | } 199 | 200 | impl<'a, 'b> PartialEq> for Invoker<'a> { 201 | fn eq(&self, other: &Invoker) -> bool { self.server == other.server && self.data == other.data } 202 | } 203 | impl<'a> Deref for Invoker<'a> { 204 | type Target = InvokerData; 205 | fn deref(&self) -> &Self::Target { &self.data } 206 | } 207 | 208 | impl<'a> Invoker<'a> { 209 | fn new(server: Server<'a>, data: InvokerData) -> Invoker<'a> { Invoker { server, data } } 210 | 211 | pub fn get_connection(&self) -> Option { self.server.get_connection(self.id) } 212 | } 213 | 214 | // ********** Server ********** 215 | #[derive(Clone)] 216 | pub struct Server<'a> { 217 | api: &'a TsApi, 218 | data: Result<&'a ServerData, ServerId>, 219 | } 220 | 221 | impl<'a, 'b> PartialEq> for Server<'a> { 222 | fn eq(&self, other: &Server<'b>) -> bool { self.get_id() == other.get_id() } 223 | } 224 | impl<'a> Eq for Server<'a> {} 225 | impl<'a> fmt::Debug for Server<'a> { 226 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 227 | write!(f, "Server({})", self.get_id().0) 228 | } 229 | } 230 | 231 | impl PartialEq for ServerData { 232 | fn eq(&self, other: &ServerData) -> bool { self.id == other.id } 233 | } 234 | impl Eq for ServerData {} 235 | 236 | impl ServerData { 237 | /// Get a server property that is stored as a string. 238 | fn get_property_as_string( 239 | id: ServerId, property: VirtualServerProperties, 240 | ) -> Result { 241 | unsafe { 242 | let mut name: *mut c_char = std::ptr::null_mut(); 243 | let res: Error = 244 | transmute((TS3_FUNCTIONS 245 | .as_ref() 246 | .expect("Functions should be loaded") 247 | .get_server_variable_as_string)(id.0, property as usize, &mut name)); 248 | match res { 249 | Error::Ok => Ok(to_string!(name)), 250 | _ => Err(res), 251 | } 252 | } 253 | } 254 | 255 | /// Get a server property that is stored as an int. 256 | fn get_property_as_int(id: ServerId, property: VirtualServerProperties) -> Result { 257 | unsafe { 258 | let mut number: c_int = 0; 259 | let res: Error = 260 | transmute((TS3_FUNCTIONS 261 | .as_ref() 262 | .expect("Functions should be loaded") 263 | .get_server_variable_as_int)(id.0, property as usize, &mut number)); 264 | match res { 265 | Error::Ok => Ok(number as i32), 266 | _ => Err(res), 267 | } 268 | } 269 | } 270 | 271 | /// Get a server property that is stored as an int. 272 | fn get_property_as_uint64( 273 | id: ServerId, property: VirtualServerProperties, 274 | ) -> Result { 275 | unsafe { 276 | let mut number: u64 = 0; 277 | let res: Error = transmute((TS3_FUNCTIONS 278 | .as_ref() 279 | .expect("Functions should be loaded") 280 | .get_server_variable_as_uint64)( 281 | id.0, property as usize, &mut number 282 | )); 283 | match res { 284 | Error::Ok => Ok(number), 285 | _ => Err(res), 286 | } 287 | } 288 | } 289 | 290 | /// Get the connection id of our own client. 291 | /// Called when a new Server is created. 292 | fn query_own_connection_id(id: ServerId) -> Result { 293 | unsafe { 294 | let mut number: u16 = 0; 295 | let res: Error = transmute((TS3_FUNCTIONS 296 | .as_ref() 297 | .expect("Functions should be loaded") 298 | .get_client_id)(id.0, &mut number)); 299 | match res { 300 | Error::Ok => Ok(ConnectionId(number)), 301 | _ => Err(res), 302 | } 303 | } 304 | } 305 | 306 | /// Get all currently active connections on this server. 307 | /// Called when a new Server is created. 308 | /// When an error occurs, users are not inserted into the map. 309 | fn query_connections(id: ServerId) -> Map { 310 | let mut map = Map::new(); 311 | // Query connected connections 312 | let mut result: *mut u16 = std::ptr::null_mut(); 313 | let res: Error = 314 | unsafe { 315 | transmute((TS3_FUNCTIONS 316 | .as_ref() 317 | .expect("Functions should be loaded") 318 | .get_client_list)(id.0, &mut result)) 319 | }; 320 | if res == Error::Ok { 321 | unsafe { 322 | let mut counter = 0; 323 | while *result.offset(counter) != 0 { 324 | let connection_id = ConnectionId(*result.offset(counter)); 325 | let mut connection = ConnectionData::new(id, connection_id); 326 | connection.update(); 327 | map.insert(connection_id, connection); 328 | counter += 1; 329 | } 330 | } 331 | } 332 | map 333 | } 334 | 335 | /// Get all channels on this server. 336 | /// Called when a new Server is created. 337 | /// When an error occurs, channels are not inserted into the map. 338 | fn query_channels(id: ServerId) -> Result, Error> { 339 | let mut map = Map::new(); 340 | // Query connected connections 341 | let mut result: *mut u64 = std::ptr::null_mut(); 342 | let res: Error = unsafe { 343 | transmute((TS3_FUNCTIONS 344 | .as_ref() 345 | .expect("Functions should be loaded") 346 | .get_channel_list)(id.0, &mut result)) 347 | }; 348 | if res == Error::Ok { 349 | unsafe { 350 | let mut counter = 0; 351 | while *result.offset(counter) != 0 { 352 | let channel_id = ChannelId(*result.offset(counter)); 353 | let mut channel = ChannelData::new(id, channel_id); 354 | channel.update(); 355 | map.insert(channel_id, channel); 356 | counter += 1; 357 | } 358 | } 359 | Ok(map) 360 | } else { 361 | Err(res) 362 | } 363 | } 364 | 365 | // ********** Private Interface ********** 366 | 367 | fn add_connection(&mut self, connection_id: ConnectionId) -> &mut ConnectionData { 368 | let mut connection = ConnectionData::new(self.id, connection_id); 369 | connection.update(); 370 | self.visible_connections.insert(connection_id, connection); 371 | self.visible_connections.get_mut(&connection_id).unwrap() 372 | } 373 | 374 | fn remove_connection(&mut self, connection_id: ConnectionId) -> Option { 375 | self.visible_connections.remove(&connection_id) 376 | } 377 | 378 | fn add_channel(&mut self, channel_id: ChannelId) -> Result<&mut ChannelData, Error> { 379 | match self.channels { 380 | Ok(ref mut cs) => { 381 | let mut channel = ChannelData::new(self.id, channel_id); 382 | channel.update(); 383 | cs.insert(channel_id, channel); 384 | Ok(cs.get_mut(&channel_id).unwrap()) 385 | } 386 | Err(error) => Err(error), 387 | } 388 | } 389 | 390 | fn remove_channel(&mut self, channel_id: ChannelId) -> Option { 391 | self.channels.as_mut().ok().and_then(|cs| cs.remove(&channel_id)) 392 | } 393 | 394 | /// Get the mutable connection on this server that has the specified id, returns 395 | /// `None` if there is no such connection. 396 | fn get_mut_connection(&mut self, connection_id: ConnectionId) -> Option<&mut ConnectionData> { 397 | self.visible_connections.get_mut(&connection_id) 398 | } 399 | 400 | /// Get the mutable channel on this server that has the specified id, returns 401 | /// `None` if there is no such channel. 402 | fn get_mut_channel(&mut self, channel_id: ChannelId) -> Option<&mut ChannelData> { 403 | self.channels.as_mut().ok().and_then(|cs| cs.get_mut(&channel_id)) 404 | } 405 | } 406 | 407 | impl<'a> Server<'a> { 408 | fn new(api: &'a TsApi, data: &'a ServerData) -> Server<'a> { Server { api, data: Ok(data) } } 409 | 410 | fn new_err(api: &'a TsApi, server_id: ServerId) -> Server<'a> { 411 | Server { api, data: Err(server_id) } 412 | } 413 | 414 | pub fn get_id(&self) -> ServerId { 415 | match self.data { 416 | Ok(data) => data.get_id(), 417 | Err(id) => id, 418 | } 419 | } 420 | 421 | /// Get the connection on this server that has the specified id, returns 422 | /// `None` if there is no such connection. 423 | fn get_connection_unwrap(&self, connection_id: ConnectionId) -> Connection<'a> { 424 | self.get_connection(connection_id).unwrap_or_else(|| { 425 | self.api.log_or_print( 426 | format!("Can't find connection {:?}", connection_id), 427 | "rust-ts3plugin", 428 | ::LogLevel::Warning, 429 | ); 430 | Connection::new_err(&self.api, self.get_id(), connection_id) 431 | }) 432 | } 433 | 434 | /// Get the channel on this server that has the specified id, returns 435 | /// `None` if there is no such channel. 436 | fn get_channel_unwrap(&self, channel_id: ChannelId) -> Channel<'a> { 437 | self.get_channel(channel_id).unwrap_or_else(|| { 438 | self.api.log_or_print( 439 | format!("Can't find channel {:?}", channel_id), 440 | "rust-ts3plugin", 441 | ::LogLevel::Warning, 442 | ); 443 | Channel::new_owned(&self.api, self.get_id(), channel_id) 444 | }) 445 | } 446 | 447 | fn get_server_group_unwrap(&self, server_group_id: ServerGroupId) -> ServerGroup { 448 | self.get_server_group(server_group_id).unwrap_or_else(|| { 449 | /*self.api.log_or_print( 450 | format!("Can't find server group {:?}", server_group_id), 451 | "rust-ts3plugin", ::LogLevel::Warning);*/ 452 | ServerGroup {} 453 | }) 454 | } 455 | 456 | fn get_channel_group_unwrap(&self, channel_group_id: ChannelGroupId) -> ChannelGroup { 457 | self.get_channel_group(channel_group_id).unwrap_or_else(|| { 458 | //self.api.log_or_print(format!("Can't find channel group {:?}", channel_group_id), 459 | // "rust-ts3plugin", ::LogLevel::Warning); 460 | ChannelGroup {} 461 | }) 462 | } 463 | 464 | // ********** Public Interface ********** 465 | 466 | /*/// The server properties that are only available on request. 467 | pub fn get_optional_data(&self) -> Option<&OptionalServerData> { 468 | self.data.ok().map(|data| &data.optional_data) 469 | }*/ 470 | 471 | /// Get the own connection to the server. 472 | pub fn get_own_connection(&self) -> Result, Error> { 473 | match self.data { 474 | Ok(data) => data.get_own_connection_id().map(|id| self.get_connection_unwrap(id)), 475 | Err(_) => Err(Error::Ok), 476 | } 477 | } 478 | 479 | /// Get the ids of all visible connections on this server. 480 | pub fn get_connections(&self) -> Vec> { 481 | match self.data { 482 | Ok(data) => { 483 | data.visible_connections.values().map(|c| Connection::new(self.api, &c)).collect() 484 | } 485 | Err(_) => Vec::new(), 486 | } 487 | } 488 | 489 | /// Get the ids of all channels on this server. 490 | pub fn get_channels(&self) -> Vec> { 491 | match self.data { 492 | Ok(data) => match data.channels { 493 | Ok(ref cs) => cs.values().map(|c| Channel::new(self.api, &c)).collect(), 494 | Err(_) => Vec::new(), 495 | }, 496 | Err(_) => Vec::new(), 497 | } 498 | } 499 | 500 | /// Get the connection on this server that has the specified id, returns 501 | /// `None` if there is no such connection. 502 | pub fn get_connection(&self, connection_id: ConnectionId) -> Option> { 503 | self.data.ok().and_then(|data| { 504 | data.visible_connections.get(&connection_id).map(|c| Connection::new(&self.api, c)) 505 | }) 506 | } 507 | 508 | /// Get the channel on this server that has the specified id, returns 509 | /// `None` if there is no such channel. 510 | pub fn get_channel(&self, channel_id: ChannelId) -> Option> { 511 | self.data.ok().and_then(|data| { 512 | data.channels 513 | .as_ref() 514 | .ok() 515 | .and_then(|cs| cs.get(&channel_id)) 516 | .map(|c| Channel::new(&self.api, c)) 517 | }) 518 | } 519 | 520 | pub fn get_server_group(&self, _server_group_id: ServerGroupId) -> Option { 521 | todo!() 522 | } 523 | 524 | pub fn get_channel_group(&self, _channel_group_id: ChannelGroupId) -> Option { 525 | todo!() 526 | } 527 | 528 | /// Send a message to the server chat. 529 | pub fn send_message>(&self, message: S) -> Result<(), Error> { 530 | unsafe { 531 | let text = to_cstring!(message.as_ref()); 532 | let res: Error = transmute((TS3_FUNCTIONS 533 | .as_ref() 534 | .expect("Functions should be loaded") 535 | .request_send_server_text_msg)( 536 | self.get_id().0, text.as_ptr(), std::ptr::null() 537 | )); 538 | match res { 539 | Error::Ok => Ok(()), 540 | _ => Err(res), 541 | } 542 | } 543 | } 544 | 545 | /// Sends a plugin message to all connections on the server. 546 | /// 547 | /// Messages can be received in [`Plugin::plugin_message`]. 548 | /// This is refered to as `PluginCommand` in TeamSpeak. 549 | /// 550 | /// [`Plugin::plugin_message`]: plugin/trait.Plugin.html#method.plugin_message 551 | pub fn send_plugin_message>(&self, message: S) { 552 | unsafe { 553 | let text = to_cstring!(message.as_ref()); 554 | (TS3_FUNCTIONS.as_ref().expect("Functions should be loaded").send_plugin_command)( 555 | self.get_id().0, 556 | to_cstring!(self.api.get_plugin_id()).as_ptr(), 557 | text.as_ptr(), 558 | PluginTargetMode::Server as i32, 559 | std::ptr::null(), 560 | std::ptr::null(), 561 | ); 562 | } 563 | } 564 | 565 | /// Print a message into the server or channel tab of this server. This is only 566 | /// visible in the window of this client and will not be sent to the server. 567 | pub fn print_message>(&self, message: S, target: MessageTarget) { 568 | unsafe { 569 | let text = to_cstring!(message.as_ref()); 570 | (TS3_FUNCTIONS.as_ref().expect("Functions should be loaded").print_message)( 571 | self.get_id().0, 572 | text.as_ptr(), 573 | target, 574 | ); 575 | } 576 | } 577 | } 578 | 579 | // ********** Channel ********** 580 | #[derive(Clone)] 581 | pub struct Channel<'a> { 582 | api: &'a TsApi, 583 | data: Result<&'a ChannelData, (ServerId, ChannelId)>, 584 | } 585 | 586 | impl<'a, 'b> PartialEq> for Channel<'a> { 587 | fn eq(&self, other: &Channel<'b>) -> bool { 588 | self.get_server_id() == other.get_server_id() && self.get_id() == other.get_id() 589 | } 590 | } 591 | impl<'a> Eq for Channel<'a> {} 592 | impl<'a> fmt::Debug for Channel<'a> { 593 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 594 | write!(f, "Channel({})", self.get_id().0) 595 | } 596 | } 597 | 598 | impl PartialEq for ChannelData { 599 | fn eq(&self, other: &ChannelData) -> bool { 600 | self.server_id == other.server_id && self.id == other.id 601 | } 602 | } 603 | impl Eq for ChannelData {} 604 | 605 | impl ChannelData { 606 | /// Get a channel property that is stored as a string. 607 | fn get_property_as_string( 608 | server_id: ServerId, id: ChannelId, property: ChannelProperties, 609 | ) -> Result { 610 | unsafe { 611 | let mut name: *mut c_char = std::ptr::null_mut(); 612 | let res: Error = transmute((TS3_FUNCTIONS 613 | .as_ref() 614 | .expect("Functions should be loaded") 615 | .get_channel_variable_as_string)( 616 | server_id.0, id.0, property as usize, &mut name 617 | )); 618 | match res { 619 | Error::Ok => Ok(to_string!(name)), 620 | _ => Err(res), 621 | } 622 | } 623 | } 624 | 625 | /// Get a channel property that is stored as an int. 626 | fn get_property_as_int( 627 | server_id: ServerId, id: ChannelId, property: ChannelProperties, 628 | ) -> Result { 629 | unsafe { 630 | let mut number: c_int = 0; 631 | let res: Error = transmute((TS3_FUNCTIONS 632 | .as_ref() 633 | .expect("Functions should be loaded") 634 | .get_channel_variable_as_int)( 635 | server_id.0, id.0, property as usize, &mut number 636 | )); 637 | match res { 638 | Error::Ok => Ok(number as i32), 639 | _ => Err(res), 640 | } 641 | } 642 | } 643 | 644 | /// Get a channel property that is stored as an uint64. 645 | fn get_property_as_uint64( 646 | server_id: ServerId, id: ChannelId, property: ChannelProperties, 647 | ) -> Result { 648 | unsafe { 649 | let mut number: u64 = 0; 650 | let res: Error = transmute((TS3_FUNCTIONS 651 | .as_ref() 652 | .expect("Functions should be loaded") 653 | .get_channel_variable_as_uint64)( 654 | server_id.0, id.0, property as usize, &mut number 655 | )); 656 | match res { 657 | Error::Ok => Ok(number as i32), 658 | _ => Err(res), 659 | } 660 | } 661 | } 662 | 663 | /// Ask the TeamSpeak api about the parent channel id of a channel. 664 | fn query_parent_channel_id(server_id: ServerId, id: ChannelId) -> Result { 665 | unsafe { 666 | let mut number: u64 = 0; 667 | let res: Error = 668 | transmute((TS3_FUNCTIONS 669 | .as_ref() 670 | .expect("Functions should be loaded") 671 | .get_parent_channel_of_channel)(server_id.0, id.0, &mut number)); 672 | match res { 673 | Error::Ok => Ok(ChannelId(number)), 674 | _ => Err(res), 675 | } 676 | } 677 | } 678 | } 679 | 680 | impl<'a> Channel<'a> { 681 | fn new(api: &'a TsApi, data: &'a ChannelData) -> Channel<'a> { Channel { api, data: Ok(data) } } 682 | 683 | fn new_owned(api: &'a TsApi, server_id: ServerId, channel_id: ChannelId) -> Channel<'a> { 684 | Channel { api, data: Err((server_id, channel_id)) } 685 | } 686 | 687 | fn get_server_id(&self) -> ServerId { 688 | match self.data { 689 | Ok(data) => data.get_server_id(), 690 | Err((server_id, _)) => server_id, 691 | } 692 | } 693 | 694 | pub fn get_id(&self) -> ChannelId { 695 | match self.data { 696 | Ok(data) => data.get_id(), 697 | Err((_, channel_id)) => channel_id, 698 | } 699 | } 700 | 701 | /// Get the server of this channel. 702 | pub fn get_server(&self) -> Server<'a> { self.api.get_server_unwrap(self.get_server_id()) } 703 | 704 | pub fn get_parent_channel(&self) -> Result>, Error> { 705 | match self.data { 706 | Ok(data) => data.get_parent_channel_id().map(|parent_channel_id| { 707 | if parent_channel_id.0 == 0 { 708 | None 709 | } else { 710 | Some(self.get_server().get_channel_unwrap(parent_channel_id)) 711 | } 712 | }), 713 | Err(_) => Err(Error::Ok), 714 | } 715 | } 716 | 717 | /// Send a message to this channel chat. 718 | pub fn send_message>(&self, message: S) -> Result<(), Error> { 719 | unsafe { 720 | let text = to_cstring!(message.as_ref()); 721 | let res: Error = transmute((TS3_FUNCTIONS 722 | .as_ref() 723 | .expect("Functions should be loaded") 724 | .request_send_channel_text_msg)( 725 | self.data.unwrap().server_id.0, 726 | text.as_ptr(), 727 | self.data.unwrap().id.0, 728 | std::ptr::null(), 729 | )); 730 | match res { 731 | Error::Ok => Ok(()), 732 | _ => Err(res), 733 | } 734 | } 735 | } 736 | } 737 | 738 | // ********** Connection ********** 739 | #[derive(Clone)] 740 | pub struct Connection<'a> { 741 | api: &'a TsApi, 742 | data: Result<&'a ConnectionData, (ServerId, ConnectionId)>, 743 | } 744 | 745 | impl<'a, 'b> PartialEq> for Connection<'a> { 746 | fn eq(&self, other: &Connection<'b>) -> bool { 747 | self.get_server_id() == other.get_server_id() && self.get_id() == other.get_id() 748 | } 749 | } 750 | impl<'a> Eq for Connection<'a> {} 751 | impl<'a> fmt::Debug for Connection<'a> { 752 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 753 | write!(f, "Connection({})", self.get_id().0) 754 | } 755 | } 756 | 757 | impl PartialEq for ConnectionData { 758 | fn eq(&self, other: &ConnectionData) -> bool { 759 | self.server_id == other.server_id && self.id == other.id 760 | } 761 | } 762 | impl Eq for ConnectionData {} 763 | 764 | impl ConnectionData { 765 | /// Get a connection property that is stored as a string. 766 | fn get_connection_property_as_string( 767 | server_id: ServerId, id: ConnectionId, property: ConnectionProperties, 768 | ) -> Result { 769 | unsafe { 770 | let mut name: *mut c_char = std::ptr::null_mut(); 771 | let res: Error = transmute((TS3_FUNCTIONS 772 | .as_ref() 773 | .expect("Functions should be loaded") 774 | .get_connection_variable_as_string)( 775 | server_id.0, id.0, property as usize, &mut name 776 | )); 777 | match res { 778 | Error::Ok => Ok(to_string!(name)), 779 | _ => Err(res), 780 | } 781 | } 782 | } 783 | 784 | /// Get a connection property that is stored as a uint64. 785 | fn get_connection_property_as_uint64( 786 | server_id: ServerId, id: ConnectionId, property: ConnectionProperties, 787 | ) -> Result { 788 | unsafe { 789 | let mut number: u64 = 0; 790 | let res: Error = transmute((TS3_FUNCTIONS 791 | .as_ref() 792 | .expect("Functions should be loaded") 793 | .get_connection_variable_as_uint64)( 794 | server_id.0, id.0, property as usize, &mut number 795 | )); 796 | match res { 797 | Error::Ok => Ok(number), 798 | _ => Err(res), 799 | } 800 | } 801 | } 802 | 803 | /// Get a connection property that is stored as a double. 804 | fn get_connection_property_as_double( 805 | server_id: ServerId, id: ConnectionId, property: ConnectionProperties, 806 | ) -> Result { 807 | unsafe { 808 | let mut number: f64 = 0.0; 809 | let res: Error = transmute((TS3_FUNCTIONS 810 | .as_ref() 811 | .expect("Functions should be loaded") 812 | .get_connection_variable_as_double)( 813 | server_id.0, id.0, property as usize, &mut number 814 | )); 815 | match res { 816 | Error::Ok => Ok(number), 817 | _ => Err(res), 818 | } 819 | } 820 | } 821 | 822 | /// Get a client property that is stored as a string. 823 | fn get_client_property_as_string( 824 | server_id: ServerId, id: ConnectionId, property: ClientProperties, 825 | ) -> Result { 826 | unsafe { 827 | let mut name: *mut c_char = std::ptr::null_mut(); 828 | let res: Error = transmute((TS3_FUNCTIONS 829 | .as_ref() 830 | .expect("Functions should be loaded") 831 | .get_client_variable_as_string)( 832 | server_id.0, id.0, property as usize, &mut name 833 | )); 834 | match res { 835 | Error::Ok => Ok(to_string!(name)), 836 | _ => Err(res), 837 | } 838 | } 839 | } 840 | 841 | /// Get a client property that is stored as an int. 842 | fn get_client_property_as_int( 843 | server_id: ServerId, id: ConnectionId, property: ClientProperties, 844 | ) -> Result { 845 | unsafe { 846 | let mut number: c_int = 0; 847 | let res: Error = transmute((TS3_FUNCTIONS 848 | .as_ref() 849 | .expect("Functions should be loaded") 850 | .get_client_variable_as_int)( 851 | server_id.0, id.0, property as usize, &mut number 852 | )); 853 | match res { 854 | Error::Ok => Ok(number), 855 | _ => Err(res), 856 | } 857 | } 858 | } 859 | 860 | /// Ask the TeamSpeak api about the current channel id of a connection. 861 | fn query_channel_id(server_id: ServerId, id: ConnectionId) -> Result { 862 | unsafe { 863 | let mut number: u64 = 0; 864 | let res: Error = transmute((TS3_FUNCTIONS 865 | .as_ref() 866 | .expect("Functions should be loaded") 867 | .get_channel_of_client)(server_id.0, id.0, &mut number)); 868 | match res { 869 | Error::Ok => Ok(ChannelId(number)), 870 | _ => Err(res), 871 | } 872 | } 873 | } 874 | 875 | /// Ask the TeamSpeak api, if the specified connection is currently whispering to our own 876 | /// client. 877 | fn query_whispering(server_id: ServerId, id: ConnectionId) -> Result { 878 | unsafe { 879 | let mut number: c_int = 0; 880 | let res: Error = transmute((TS3_FUNCTIONS 881 | .as_ref() 882 | .expect("Functions should be loaded") 883 | .is_whispering)(server_id.0, id.0, &mut number)); 884 | match res { 885 | Error::Ok => Ok(number != 0), 886 | _ => Err(res), 887 | } 888 | } 889 | } 890 | } 891 | 892 | impl<'a> Connection<'a> { 893 | fn new(api: &'a TsApi, data: &'a ConnectionData) -> Connection<'a> { 894 | Connection { api, data: Ok(data) } 895 | } 896 | 897 | fn new_err(api: &'a TsApi, server_id: ServerId, connection_id: ConnectionId) -> Connection<'a> { 898 | Connection { api, data: Err((server_id, connection_id)) } 899 | } 900 | 901 | fn get_server_id(&self) -> ServerId { 902 | match self.data { 903 | Ok(data) => data.get_server_id(), 904 | Err((server_id, _)) => server_id, 905 | } 906 | } 907 | 908 | pub fn get_id(&self) -> ConnectionId { 909 | match self.data { 910 | Ok(data) => data.get_id(), 911 | Err((_, connection_id)) => connection_id, 912 | } 913 | } 914 | 915 | /// Get the server of this connection. 916 | pub fn get_server(&self) -> Server<'a> { self.api.get_server_unwrap(self.get_server_id()) } 917 | 918 | /// Get the channel of this connection. 919 | pub fn get_channel(&self) -> Result, Error> { 920 | match self.data { 921 | Ok(data) => data.get_channel_id().map(|c| self.get_server().get_channel_unwrap(c)), 922 | Err(_) => Err(Error::Ok), 923 | } 924 | } 925 | 926 | pub fn get_channel_group_inherited_channel(&self) -> Result, Error> { 927 | match self.data { 928 | Ok(data) => data 929 | .get_channel_group_inherited_channel_id() 930 | .map(|c| self.get_server().get_channel_unwrap(c)), 931 | Err(_) => Err(Error::Ok), 932 | } 933 | } 934 | 935 | /*/// The connection properties that are only available for our own client. 936 | pub fn get_own_data(&self) -> Option<&OwnConnectionData> { 937 | self.data.ok().and_then(|data| data.own_data.as_ref()) 938 | } 939 | 940 | /// The connection properties that are only available for server queries. 941 | pub fn get_serverquery_data(&self) -> Option<&ServerqueryConnectionData> { 942 | self.data.ok().and_then(|data| data.serverquery_data.as_ref()) 943 | } 944 | 945 | /// The connection properties that are only available on request. 946 | pub fn get_optional_data(&self) -> Option<&OptionalConnectionData> { 947 | self.data.ok().map(|data| &data.optional_data) 948 | }*/ 949 | 950 | /// Send a private message to this connection. 951 | pub fn send_message>(&self, message: S) -> Result<(), Error> { 952 | unsafe { 953 | let text = to_cstring!(message.as_ref()); 954 | let res: Error = transmute((TS3_FUNCTIONS 955 | .as_ref() 956 | .expect("Functions should be loaded") 957 | .request_send_private_text_msg)( 958 | self.data.unwrap().server_id.0, 959 | text.as_ptr(), 960 | self.data.unwrap().id.0, 961 | std::ptr::null(), 962 | )); 963 | match res { 964 | Error::Ok => Ok(()), 965 | _ => Err(res), 966 | } 967 | } 968 | } 969 | } 970 | 971 | pub struct TsApiLock { 972 | guard: MutexGuard<'static, (Option<(TsApi, Box)>, Option)>, 973 | } 974 | impl Deref for TsApiLock { 975 | type Target = TsApi; 976 | fn deref(&self) -> &Self::Target { &self.guard.0.as_ref().unwrap().0 } 977 | } 978 | impl DerefMut for TsApiLock { 979 | fn deref_mut(&mut self) -> &mut Self::Target { &mut self.guard.0.as_mut().unwrap().0 } 980 | } 981 | 982 | pub struct PluginLock { 983 | guard: MutexGuard<'static, (Option<(TsApi, Box)>, Option)>, 984 | } 985 | impl Deref for PluginLock { 986 | type Target = dyn Plugin; 987 | fn deref(&self) -> &Self::Target { &*self.guard.0.as_ref().unwrap().1 } 988 | } 989 | impl DerefMut for PluginLock { 990 | fn deref_mut(&mut self) -> &mut Self::Target { &mut *self.guard.0.as_mut().unwrap().1 } 991 | } 992 | 993 | // ********** TsApi ********** 994 | /// The main struct that contains all permanently save data. 995 | pub struct TsApi { 996 | /// All known servers. 997 | servers: Map, 998 | /// The plugin id from TeamSpeak. 999 | plugin_id: String, 1000 | } 1001 | 1002 | // Don't provide a default Implementation because we don't want the TsApi 1003 | // to be publicly constructable. 1004 | impl TsApi { 1005 | /// Create a new TsApi instance without loading anything. 1006 | fn new(plugin_id: String) -> TsApi { TsApi { servers: Map::new(), plugin_id: plugin_id } } 1007 | 1008 | /// Load all currently connected server and their data. 1009 | /// This should normally be executed after `new()`. 1010 | fn load(&mut self) -> Result<(), Error> { 1011 | // Query available connections 1012 | let mut result: *mut u64 = std::ptr::null_mut(); 1013 | let res: Error = unsafe { 1014 | transmute((TS3_FUNCTIONS 1015 | .as_ref() 1016 | .expect("Functions should be loaded") 1017 | .get_server_connection_handler_list)(&mut result)) 1018 | }; 1019 | match res { 1020 | Error::Ok => unsafe { 1021 | let mut counter = 0; 1022 | while *result.offset(counter) != 0 { 1023 | // Test if we have a connection to this server. 1024 | // We get open tabs, even if they are disconnected. 1025 | let mut status: c_int = 0; 1026 | let res: Error = transmute((TS3_FUNCTIONS 1027 | .as_ref() 1028 | .expect("Functions should be loaded") 1029 | .get_connection_status)( 1030 | *result.offset(counter), &mut status 1031 | )); 1032 | if res == Error::Ok 1033 | && transmute::(status) != ConnectStatus::Disconnected 1034 | { 1035 | self.add_server(ServerId(*result.offset(counter))); 1036 | } 1037 | counter += 1; 1038 | } 1039 | }, 1040 | _ => return Err(res), 1041 | } 1042 | Ok(()) 1043 | } 1044 | 1045 | /// Lock the global `TsApi` object. This will be `None` when the plugin is 1046 | /// constructed. 1047 | pub fn lock_api() -> Option { 1048 | let guard = ts3interface::DATA.lock().unwrap(); 1049 | if guard.0.is_none() { None } else { Some(TsApiLock { guard }) } 1050 | } 1051 | 1052 | /// Lock the global `Plugin` object. 1053 | pub fn lock_plugin() -> Option { 1054 | let guard = ts3interface::DATA.lock().unwrap(); 1055 | if guard.0.is_none() { None } else { Some(PluginLock { guard }) } 1056 | } 1057 | 1058 | /// Please try to use the member method `log_message` instead of this static method. 1059 | pub fn static_log_message, S2: AsRef>( 1060 | message: S1, channel: S2, severity: LogLevel, 1061 | ) -> Result<(), Error> { 1062 | unsafe { 1063 | let res: Error = transmute((TS3_FUNCTIONS 1064 | .as_ref() 1065 | .expect("Functions should be loaded") 1066 | .log_message)( 1067 | to_cstring!(message.as_ref()).as_ptr(), 1068 | severity, 1069 | to_cstring!(channel.as_ref()).as_ptr(), 1070 | 0, 1071 | )); 1072 | match res { 1073 | Error::Ok => Ok(()), 1074 | _ => Err(res), 1075 | } 1076 | } 1077 | } 1078 | 1079 | /// Please try to use the member method `log_or_print` instead of this static method. 1080 | pub fn static_log_or_print, S2: AsRef>( 1081 | message: S1, channel: S2, severity: LogLevel, 1082 | ) { 1083 | if let Err(error) = TsApi::static_log_message(message.as_ref(), channel.as_ref(), severity) 1084 | { 1085 | println!( 1086 | "Error {:?} while printing '{}' to '{}' ({:?})", 1087 | error, 1088 | message.as_ref(), 1089 | channel.as_ref(), 1090 | severity 1091 | ); 1092 | } 1093 | } 1094 | 1095 | /// Please try to use the member method `get_error_message` instead of this static method. 1096 | pub fn static_get_error_message(error: Error) -> Result { 1097 | unsafe { 1098 | let mut message: *mut c_char = std::ptr::null_mut(); 1099 | let res: Error = transmute((TS3_FUNCTIONS 1100 | .as_ref() 1101 | .expect("Functions should be loaded") 1102 | .get_error_message)(error as u32, &mut message)); 1103 | match res { 1104 | Error::Ok => Ok(to_string!(message)), 1105 | _ => Err(res), 1106 | } 1107 | } 1108 | } 1109 | 1110 | // ********** Private Interface ********** 1111 | 1112 | /// Add the server with the specified id to the server list. 1113 | /// The currently available data of this server will be stored. 1114 | fn add_server(&mut self, server_id: ServerId) -> &mut ServerData { 1115 | self.servers.insert(server_id, ServerData::new(server_id)); 1116 | let server = self.servers.get_mut(&server_id).unwrap(); 1117 | server.update(); 1118 | server 1119 | } 1120 | 1121 | /// Returns true if a server was removed 1122 | fn remove_server(&mut self, server_id: ServerId) -> Option { 1123 | self.servers.remove(&server_id) 1124 | } 1125 | 1126 | /// Update the data of a connection with the data from the same connection 1127 | /// as an invoker if possible. 1128 | fn try_update_invoker(&mut self, server_id: ServerId, invoker: &InvokerData) { 1129 | if let Some(server) = self.get_mut_server(server_id) { 1130 | if let Some(connection) = server.get_mut_connection(invoker.get_id()) { 1131 | if connection.get_uid() != Ok(invoker.get_uid()) { 1132 | connection.uid = Ok(invoker.get_uid().clone()); 1133 | } 1134 | if connection.get_name() != Ok(invoker.get_name()) { 1135 | connection.name = Ok(invoker.get_name().clone()) 1136 | } 1137 | } 1138 | } 1139 | } 1140 | 1141 | /// A reusable function that takes a TeamSpeak3 api function like 1142 | /// `get_plugin_path` and returns the path. 1143 | /// The buffer that holds the path will be automatically enlarged up to a 1144 | /// limit. 1145 | /// The function that is colled takes a pointer to a string buffer that will 1146 | /// be filled and the max lenght of the buffer. 1147 | fn get_path(fun: F) -> String { 1148 | const START_SIZE: usize = 512; 1149 | const MAX_SIZE: usize = 100_000; 1150 | let mut size = START_SIZE; 1151 | loop { 1152 | let mut buf = vec![0 as u8; size]; 1153 | fun(buf.as_mut_ptr() as *mut c_char, size - 1); 1154 | // Test if the allocated buffer was long enough 1155 | if buf[size - 3] != 0 { 1156 | size *= 2; 1157 | if size > MAX_SIZE { 1158 | return String::new(); 1159 | } 1160 | } else { 1161 | // Be sure that the string is terminated 1162 | buf[size - 1] = 0; 1163 | let s = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }; 1164 | let result = s.to_string_lossy(); 1165 | return result.into_owned(); 1166 | } 1167 | } 1168 | } 1169 | 1170 | /// Get the mutable server that has the specified id, returns `None` if there is no 1171 | /// such server. 1172 | fn get_mut_server(&mut self, server_id: ServerId) -> Option<&mut ServerData> { 1173 | self.servers.get_mut(&server_id) 1174 | } 1175 | 1176 | fn get_server_unwrap<'a>(&'a self, server_id: ServerId) -> Server<'a> { 1177 | self.servers.get(&server_id).map(|s| Server::<'a>::new(&self, s)).unwrap_or_else(|| { 1178 | // Ignore here, there are too many messages when we are not yet 1179 | // fully connected (or already disconnected), but sound is sent. 1180 | // self.log_or_print(format!("Can't find server {:?}\n{:?}", 1181 | // server_id, backtrace::Backtrace::new()), "rust-ts3plugin", ::LogLevel::Warning); 1182 | Server::new_err(&self, server_id) 1183 | }) 1184 | } 1185 | 1186 | // ********** Public Interface ********** 1187 | 1188 | /// Get the raw TeamSpeak api functions. 1189 | /// These functions can be used to invoke actions that are not yet 1190 | /// implemented by this library. You should file a bug report or make a pull 1191 | /// request if you need to use this function. 1192 | pub unsafe fn get_raw_api() -> &'static Ts3Functions { TS3_FUNCTIONS.as_ref().unwrap() } 1193 | 1194 | /// Get the plugin id assigned by TeamSpeak. 1195 | pub fn get_plugin_id(&self) -> &str { &self.plugin_id } 1196 | 1197 | /// Get all servers to which this client is currently connected. 1198 | pub fn get_servers<'a>(&'a self) -> Vec> { 1199 | self.servers.values().map(|s| Server::new(&self, &s)).collect() 1200 | } 1201 | 1202 | /// Log a message using the TeamSpeak logging API. 1203 | pub fn log_message, S2: AsRef>( 1204 | &self, message: S1, channel: S2, severity: LogLevel, 1205 | ) -> Result<(), Error> { 1206 | TsApi::static_log_message(message, channel, severity) 1207 | } 1208 | 1209 | /// Log a message using the TeamSpeak logging API. 1210 | /// If that fails, print the message to stdout. 1211 | pub fn log_or_print, S2: AsRef>( 1212 | &self, message: S1, channel: S2, severity: LogLevel, 1213 | ) { 1214 | TsApi::static_log_or_print(message, channel, severity) 1215 | } 1216 | 1217 | /// Get the server that has the specified id, returns `None` if there is no 1218 | /// such server. 1219 | pub fn get_server(&self, server_id: ServerId) -> Option { 1220 | self.servers.get(&server_id).map(|s| Server::new(&self, s)) 1221 | } 1222 | 1223 | pub fn get_permission(&self, _permission_id: PermissionId) -> Option<&Permission> { todo!() } 1224 | 1225 | /// Print a message to the currently selected tab. This is only 1226 | /// visible in the window of this client and will not be sent to the server. 1227 | pub fn print_message>(&self, message: S) { 1228 | unsafe { 1229 | let text = to_cstring!(message.as_ref()); 1230 | (TS3_FUNCTIONS 1231 | .as_ref() 1232 | .expect("Functions should be loaded") 1233 | .print_message_to_current_tab)(text.as_ptr()); 1234 | } 1235 | } 1236 | 1237 | /// Get the application path of the TeamSpeak executable. 1238 | pub fn get_app_path(&self) -> String { 1239 | unsafe { 1240 | TsApi::get_path(|p, l| { 1241 | (TS3_FUNCTIONS.as_ref().expect("Functions should be loaded").get_app_path)(p, l) 1242 | }) 1243 | } 1244 | } 1245 | 1246 | /// Get the resource path of TeamSpeak. 1247 | pub fn get_resources_path(&self) -> String { 1248 | unsafe { 1249 | TsApi::get_path(|p, l| { 1250 | (TS3_FUNCTIONS.as_ref().expect("Functions should be loaded").get_resources_path)( 1251 | p, l, 1252 | ) 1253 | }) 1254 | } 1255 | } 1256 | 1257 | /// Get the path, where configuration files are stored. 1258 | /// This is e.g. `~/.ts3client` on linux or `%AppData%/TS3Client` on Windows. 1259 | pub fn get_config_path(&self) -> String { 1260 | unsafe { 1261 | TsApi::get_path(|p, l| { 1262 | (TS3_FUNCTIONS.as_ref().expect("Functions should be loaded").get_config_path)(p, l) 1263 | }) 1264 | } 1265 | } 1266 | 1267 | /// Get the path where TeamSpeak plugins are stored. 1268 | pub fn get_plugin_path(&self) -> String { 1269 | unsafe { 1270 | TsApi::get_path(|p, l| { 1271 | (TS3_FUNCTIONS.as_ref().expect("Functions should be loaded").get_plugin_path)( 1272 | p, 1273 | l, 1274 | to_cstring!(self.plugin_id.as_str()).as_ptr(), 1275 | ) 1276 | }) 1277 | } 1278 | } 1279 | } 1280 | -------------------------------------------------------------------------------- /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() -> ::ConfigureOffer 63 | where Self: Sized { 64 | ::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: &::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: &::TsApi, server: &::Server, status: ::ConnectStatus, error: ::Error, 86 | ) { 87 | } 88 | 89 | /// Called if a server is stopped. The server sends also a stop message. 90 | fn server_stop(&mut self, api: &::TsApi, server: &::Server, message: String) {} 91 | 92 | /// Called if a server error occurs. 93 | /// Return `false` if the TeamSpeak client should handle the error normally or 94 | /// `true` if the client should ignore the error. 95 | fn server_error( 96 | &mut self, api: &::TsApi, server: &::Server, error: ::Error, message: String, 97 | return_code: String, extra_message: String, 98 | ) -> bool { 99 | false 100 | } 101 | 102 | /// Called if someone edited the server. 103 | fn server_edited(&mut self, api: &::TsApi, server: &::Server, invoker: Option<&::Invoker>) {} 104 | 105 | /// Called when the user requests the server info by middle-clicking on the server. 106 | fn server_connection_info(&mut self, api: &::TsApi, server: &::Server) {} 107 | 108 | fn connection_info(&mut self, api: &::TsApi, server: &::Server, connection: &::Connection) {} 109 | 110 | fn connection_properties_changed( 111 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 112 | old_connection: &::Connection, changes: ::ConnectionChanges, invoker: &::Invoker, 113 | ) { 114 | } 115 | 116 | /// If the plugin was informed about a new connection. If appeared is true, the connection 117 | /// was previously not known to the plugin, if appeared is false, the connection left 118 | /// the view of connection. 119 | fn connection_announced( 120 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, appeared: bool, 121 | ) { 122 | } 123 | 124 | /// Called, if a connection connects to the server. This is also called for our own 125 | /// connection. 126 | fn connection_changed( 127 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, connected: bool, 128 | message: String, 129 | ) { 130 | } 131 | 132 | /// Called if a connection switched the channel. 133 | fn connection_move( 134 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 135 | old_channel: &::Channel, new_channel: &::Channel, visibility: ::Visibility, 136 | ) { 137 | } 138 | 139 | /// Called if a connection was moved by another connection. 140 | fn connection_moved( 141 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 142 | old_channel: &::Channel, new_channel: &::Channel, visibility: ::Visibility, 143 | invoker: &::Invoker, 144 | ) { 145 | } 146 | 147 | /// Called when a connection times out. 148 | fn connection_timeout(&mut self, api: &::TsApi, server: &::Server, connection: &::Connection) {} 149 | 150 | /// Called if a channel is announced to the client. 151 | /// This will be called for each channel when connecting to a server. 152 | fn channel_announced(&mut self, api: &::TsApi, server: &::Server, channel: &::Channel) {} 153 | 154 | /// Called if the channel description was changed. 155 | fn channel_description_updated( 156 | &mut self, api: &::TsApi, server: &::Server, channel: &::Channel, 157 | ) { 158 | } 159 | 160 | /// Called if the channel data are updated and available. 161 | /// This happens e.g. when the user clicked on the channel for the first time. 162 | fn channel_updated( 163 | &mut self, api: &::TsApi, server: &::Server, channel: &::Channel, old_channel: &::Channel, 164 | ) { 165 | } 166 | 167 | /// Called if a channel was created. 168 | /// The invoker is `None` if the server created the channel. 169 | fn channel_created( 170 | &mut self, api: &::TsApi, server: &::Server, channel: &::Channel, 171 | invoker: Option<&::Invoker>, 172 | ) { 173 | } 174 | 175 | /// Called if a channel was deleted. 176 | /// The invoker is `None` if the server deleted the channel. 177 | fn channel_deleted( 178 | &mut self, api: &::TsApi, server: &::Server, channel: &::Channel, 179 | invoker: Option<&::Invoker>, 180 | ) { 181 | } 182 | 183 | /// Called if a channel was edited. 184 | fn channel_edited( 185 | &mut self, api: &::TsApi, server: &::Server, channel: &::Channel, old_channel: &::Channel, 186 | invoker: &::Invoker, 187 | ) { 188 | } 189 | 190 | /// Called if the channel password was updated. 191 | fn channel_password_updated(&mut self, api: &::TsApi, server: &::Server, channel: &::Channel) {} 192 | 193 | /// The current parent id of the channel is the old one, the new 194 | /// parent id is given as a parameter. 195 | fn channel_moved( 196 | &mut self, api: &::TsApi, server: &::Server, channel: &::Channel, 197 | new_parent_channel: &::Channel, invoker: Option<&::Invoker>, 198 | ) { 199 | } 200 | 201 | /// A message was received. `ignored` describes, if the friend and fool system 202 | /// of TeamSpeak ignored the message. 203 | /// Return `false` if the TeamSpeak client should handle the message normally or 204 | /// `true` if the client should ignore the message. 205 | fn message( 206 | &mut self, api: &::TsApi, server: &::Server, invoker: &::Invoker, 207 | target: ::MessageReceiver, message: String, ignored: bool, 208 | ) -> bool { 209 | false 210 | } 211 | 212 | /// A user poked us. `ignored` describes, if the friend and fool system 213 | /// of TeamSpeak ignored the message. 214 | /// Return `false` if the TeamSpeak client should handle the poke normally or 215 | /// `true` if the client should ignore the poke. 216 | fn poke( 217 | &mut self, api: &::TsApi, server: &::Server, invoker: &::Invoker, message: String, 218 | ignored: bool, 219 | ) -> bool { 220 | false 221 | } 222 | 223 | fn channel_kick( 224 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 225 | old_channel: &::Channel, new_channel: &::Channel, visibility: ::Visibility, 226 | invoker: &::Invoker, message: String, 227 | ) { 228 | } 229 | 230 | fn server_kick( 231 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 232 | invoker: &::Invoker, message: String, 233 | ) { 234 | } 235 | 236 | fn server_ban( 237 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 238 | invoker: &::Invoker, message: String, time: u64, 239 | ) { 240 | } 241 | 242 | /// The old values of `talking` and `whispering` are available from the connection. 243 | /// They will be updated after this functions returned. 244 | fn talking_changed( 245 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 246 | talking: ::TalkStatus, whispering: bool, 247 | ) { 248 | } 249 | 250 | /// Called if the avatar of a client is updated. 251 | /// This also happens when the avatar is discovered for the first time. 252 | /// The avatar information are only fetched if requested, e.g. if the 253 | /// user clicks on a connection. 254 | fn avatar_changed( 255 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 256 | path: Option, 257 | ) { 258 | } 259 | 260 | /// Called if a channel group is assigned to a connection. 261 | fn connection_channel_group_changed( 262 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 263 | channel_group: &::ChannelGroup, channel: &::Channel, invoker: &::Invoker, 264 | ) { 265 | } 266 | 267 | /// Called if a server group is added to a connection. 268 | fn connection_server_group_added( 269 | &mut self, api: &::TsApi, server: &::Server, connection: &::Invoker, 270 | server_group: &::ServerGroup, invoker: &::Invoker, 271 | ) { 272 | } 273 | 274 | /// Called if a server group is removed from a connection. 275 | fn connection_server_group_removed( 276 | &mut self, api: &::TsApi, server: &::Server, connection: &::Invoker, 277 | server_group: &::ServerGroup, invoker: &::Invoker, 278 | ) { 279 | } 280 | 281 | /// Called when a voice packet from a client was received. 282 | /// 283 | /// From the TeamSpeak documentation: 284 | /// The following event is called when a voice packet from a client (not own 285 | /// client) is decoded and about to be played over your sound device, but 286 | /// before it is 3D positioned and mixed with other sounds. You can use this 287 | /// function to alter the voice data (for example when you want to do 288 | /// effects on it) or to simply get voice data. The TeamSpeak client uses 289 | /// this function to record sessions. 290 | /// 291 | /// The voice data is available as 16 bit with 48 KHz. The channels are packed 292 | /// (interleaved). 293 | /// The callbacks with audio data are called from another thread than the 294 | /// other functions. 295 | fn playback_voice_data( 296 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 297 | samples: &mut [i16], channels: i32, 298 | ) { 299 | } 300 | 301 | /// Called when a voice packet from a client was positioned. 302 | /// 303 | /// From the TeamSpeak documentation: 304 | /// The following event is called when a voice packet from a client (not own 305 | /// client) is decoded and 3D positioned and about to be played over your 306 | /// sound device, but before it is mixed with other sounds. You can use this 307 | /// function to alter/get the voice data after 3D positioning. 308 | /// 309 | /// The voice data is available as 16 bit with 48 KHz. The channels are packed 310 | /// (interleaved). 311 | /// The callbacks with audio data are called from another thread than the 312 | /// other functions. 313 | fn post_process_voice_data( 314 | &mut self, api: &::TsApi, server: &::Server, connection: &::Connection, 315 | samples: &mut [i16], channels: i32, channel_speaker_array: &[::Speaker], 316 | channel_fill_mask: &mut u32, 317 | ) { 318 | } 319 | 320 | /// Called when all voice data were mixed. 321 | /// 322 | /// From the TeamSpeak documentation: 323 | /// The following event is called when all sounds that are about to be 324 | /// played back for this server connection are mixed. This is the last 325 | /// chance to alter/get sound. 326 | /// 327 | /// The voice data is available as 16 bit with 48 KHz. The channels are packed 328 | /// (interleaved). 329 | /// The callbacks with audio data are called from another thread than the 330 | /// other functions. 331 | fn mixed_playback_voice_data( 332 | &mut self, api: &::TsApi, server: &::Server, samples: &mut [i16], channels: i32, 333 | channel_speaker_array: &[::Speaker], channel_fill_mask: &mut u32, 334 | ) { 335 | } 336 | 337 | /// The recorded sound from the current capture device. 338 | /// 339 | /// `send` is set if the audio data will be send to the server. This attribute 340 | /// can be changed in this callback. 341 | /// The return value of this function describes if the sound data was altered. 342 | /// Return `true` if the sound was changed and `false` otherwise. 343 | /// The callbacks with audio data are called from another thread than the 344 | /// other functions. 345 | fn captured_voice_data( 346 | &mut self, api: &::TsApi, server: &::Server, samples: &mut [i16], channels: i32, 347 | send: &mut bool, 348 | ) -> bool { 349 | false 350 | } 351 | 352 | /// Return `false` if the TeamSpeak client should handle the error normally or 353 | /// `true` if the client should ignore the error. 354 | fn permission_error( 355 | &mut self, api: &::TsApi, server: &::Server, permission: &::Permission, error: ::Error, 356 | message: String, return_code: String, 357 | ) -> bool { 358 | false 359 | } 360 | 361 | /// Called when a message from another plugin is received. 362 | /// 363 | /// Messages can be sent with [`Server::send_plugin_message`]. 364 | /// The message is called `PluginCommand` by TeamSpeak. 365 | /// 366 | /// [`Server::send_plugin_message`]: ../struct.Server.html#method.send_plugin_message 367 | fn plugin_message( 368 | &mut self, api: &::TsApi, server: &::Server, plugin: String, message: String, 369 | invoker: Option<&::Invoker>, 370 | ) { 371 | } 372 | 373 | /// Called when the user enters a command in the chat box. 374 | /// 375 | /// Commands that are prefixed with the string, which is specified in 376 | /// [`Plugin::command`], are redirected to this function. 377 | /// The command prefix is not contained in the given argument. 378 | /// 379 | /// Return `true` if this function handled the command or `false` if not. 380 | /// 381 | /// [`Plugin::command`]: #method.command 382 | fn process_command(&mut self, api: &::TsApi, server: &::Server, command: String) -> bool { 383 | false 384 | } 385 | 386 | /// Called if the plugin is getting disabled (either by the user or if 387 | /// TeamSpeak is exiting). 388 | fn shutdown(&mut self, api: &::TsApi) {} 389 | } 390 | 391 | /// Save the `CString`s that are returned from the TeamSpeak API. 392 | /// We don't want to return invalid pointers. 393 | #[doc(hidden)] 394 | pub struct CreatePluginData { 395 | pub name: Option<::std::ffi::CString>, 396 | pub version: Option<::std::ffi::CString>, 397 | pub author: Option<::std::ffi::CString>, 398 | pub description: Option<::std::ffi::CString>, 399 | pub command: Option>, 400 | } 401 | 402 | lazy_static! { 403 | #[doc(hidden)] 404 | pub static ref CREATE_PLUGIN_DATA: Mutex = 405 | Mutex::new(CreatePluginData { 406 | name: None, 407 | version: None, 408 | author: None, 409 | description: None, 410 | command: None, 411 | }); 412 | } 413 | 414 | /// Create a plugin. 415 | /// 416 | /// This macro has to be called once per library to create the 417 | /// function interface that is used by TeamSpeak. The argument is the struct 418 | /// which implements the [`Plugin`] trait. 419 | /// 420 | /// # Examples 421 | /// 422 | /// ```ignore 423 | /// create_plugin!(MyTsPlugin); 424 | /// ``` 425 | /// 426 | /// [`Plugin`]: plugin/trait.Plugin.html 427 | #[macro_export] 428 | macro_rules! create_plugin { 429 | ($typename: ident) => { 430 | /// Initialise the plugin and return the error status. 431 | #[no_mangle] 432 | #[doc(hidden)] 433 | pub unsafe extern "C" fn ts3plugin_init() -> std::os::raw::c_int { 434 | match $crate::ts3interface::private_init::<$typename>() { 435 | Ok(_) => 0, 436 | Err($crate::InitError::Failure) => 1, 437 | Err($crate::InitError::FailureNoMessage) => -2, 438 | } 439 | } 440 | 441 | /// Unique name identifying this plugin. 442 | /// The result of this function has to be a null-terminated static string. 443 | /// Can be called before init. 444 | #[no_mangle] 445 | #[doc(hidden)] 446 | pub extern "C" fn ts3plugin_name() -> *const std::os::raw::c_char { 447 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 448 | if data.name.is_none() { 449 | let s = $typename::name(); 450 | if s == "MAGIC\0" { 451 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_NAME")) 452 | .expect("Crate name contains nul character"); 453 | data.name = Some(s); 454 | } else { 455 | let s = 456 | ::std::ffi::CString::new(s).expect("Plugin name contains nul character"); 457 | data.name = Some(s); 458 | } 459 | } 460 | data.name.as_ref().unwrap().as_ptr() 461 | } 462 | 463 | /// The version of the plugin. 464 | /// Can be called before init. 465 | #[no_mangle] 466 | #[doc(hidden)] 467 | pub extern "C" fn ts3plugin_version() -> *const std::os::raw::c_char { 468 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 469 | if data.version.is_none() { 470 | let s = $typename::version(); 471 | if s == "MAGIC\0" { 472 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_VERSION")) 473 | .expect("Crate version contains nul character"); 474 | data.version = Some(s); 475 | } else { 476 | let s = 477 | ::std::ffi::CString::new(s).expect("Plugin version contains nul character"); 478 | data.version = Some(s); 479 | } 480 | } 481 | data.version.as_ref().unwrap().as_ptr() 482 | } 483 | 484 | /// The author of the plugin. 485 | /// Can be called before init. 486 | #[no_mangle] 487 | #[doc(hidden)] 488 | pub extern "C" fn ts3plugin_author() -> *const std::os::raw::c_char { 489 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 490 | if data.author.is_none() { 491 | let s = $typename::author(); 492 | if s == "MAGIC\0" { 493 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_AUTHORS")) 494 | .expect("Crate author contains nul character"); 495 | data.author = Some(s); 496 | } else { 497 | let s = 498 | ::std::ffi::CString::new(s).expect("Plugin author contains nul character"); 499 | data.author = Some(s); 500 | } 501 | } 502 | data.author.as_ref().unwrap().as_ptr() 503 | } 504 | 505 | /// The desription of the plugin. 506 | /// Can be called before init. 507 | #[no_mangle] 508 | #[doc(hidden)] 509 | pub extern "C" fn ts3plugin_description() -> *const std::os::raw::c_char { 510 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 511 | if data.description.is_none() { 512 | let s = $typename::description(); 513 | if s == "MAGIC\0" { 514 | let s = ::std::ffi::CString::new(env!("CARGO_PKG_DESCRIPTION")) 515 | .expect("Crate description contains nul character"); 516 | data.description = Some(s); 517 | } else { 518 | let s = ::std::ffi::CString::new(s) 519 | .expect("Plugin description contains nul character"); 520 | data.description = Some(s); 521 | } 522 | } 523 | data.description.as_ref().unwrap().as_ptr() 524 | } 525 | 526 | /// If the plugin offers the possibility to be configured by the user. 527 | /// Can be called before init. 528 | #[allow(non_snake_case)] 529 | #[no_mangle] 530 | #[doc(hidden)] 531 | pub extern "C" fn ts3plugin_commandKeyword() -> *const std::os::raw::c_char { 532 | let mut data = CREATE_PLUGIN_DATA.lock().unwrap(); 533 | if data.command.is_none() { 534 | data.command = Some(if let Some(s) = $typename::command() { 535 | let s = ::std::ffi::CString::new(s).expect("String contains nul character"); 536 | Some(s) 537 | } else { 538 | None 539 | }) 540 | } 541 | if let &Some(ref s) = data.command.as_ref().unwrap() { 542 | s.as_ptr() 543 | } else { 544 | std::ptr::null() 545 | } 546 | } 547 | 548 | /// If the plugin offers the possibility to be configured by the user. 549 | /// Can be called before init. 550 | #[allow(non_snake_case)] 551 | #[no_mangle] 552 | #[doc(hidden)] 553 | pub extern "C" fn ts3plugin_offersConfigure() -> std::os::raw::c_int { 554 | $typename::configurable() as std::os::raw::c_int 555 | } 556 | 557 | /// If the plugin should be loaded automatically. 558 | /// Can be called before init. 559 | #[allow(non_snake_case)] 560 | #[no_mangle] 561 | #[doc(hidden)] 562 | pub extern "C" fn ts3plugin_requestAutoload() -> std::os::raw::c_int { 563 | if $typename::autoload() { 1 } else { 0 } 564 | } 565 | }; 566 | } 567 | -------------------------------------------------------------------------------- /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 plugin::Plugin; 11 | 12 | lazy_static! { 13 | /// The api, plugin and plugin id 14 | pub(crate) static ref DATA: Mutex<(Option<(::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, $error: expr) => { 29 | $api.log_or_print( 30 | format!("Error {:?} ({}) in in {}:L{}", $error, $description, filename!(), line!()), 31 | "rust-ts3plugin", 32 | ::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<(), ::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 = ::TsApi::new(plugin_id); 48 | if let Err(error) = api.load() { 49 | error!(api, "Can't create TsApi", error); 50 | return Err(::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 | #[no_mangle] 68 | #[doc(hidden)] 69 | pub extern "C" fn ts3plugin_apiVersion() -> c_int { 26 } 70 | 71 | #[allow(non_snake_case)] 72 | #[no_mangle] 73 | #[doc(hidden)] 74 | pub unsafe extern "C" fn ts3plugin_setFunctionPointers(funs: Ts3Functions) { 75 | ::TS3_FUNCTIONS = Some(funs); 76 | } 77 | 78 | /// Called when the plugin should be unloaded. 79 | #[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 | #[allow(non_snake_case)] 93 | #[no_mangle] 94 | #[doc(hidden)] 95 | pub unsafe extern "C" fn ts3plugin_registerPluginID(plugin_id: *const c_char) { 96 | let mut data = DATA.lock().unwrap(); 97 | data.1 = Some(to_string!(plugin_id)); 98 | } 99 | 100 | #[allow(non_snake_case)] 101 | #[no_mangle] 102 | #[doc(hidden)] 103 | pub unsafe extern "C" fn ts3plugin_onConnectStatusChangeEvent( 104 | server_id: u64, status: c_int, error: c_uint, 105 | ) { 106 | let server_id = ::ServerId(server_id); 107 | let status = transmute(status); 108 | let error = transmute(error); 109 | let mut data = DATA.lock().unwrap(); 110 | let data = data.0.as_mut().unwrap(); 111 | let api = &mut data.0; 112 | let plugin = &mut data.1; 113 | // Add the server if we can get information about it 114 | // and don't have that server cached already. 115 | if status != ConnectStatus::Connecting && api.get_server(server_id).is_none() { 116 | api.add_server(server_id); 117 | } 118 | { 119 | let server = api.get_server_unwrap(server_id); 120 | // Execute plugin callback 121 | plugin.connect_status_change(api, &server, status, error); 122 | } 123 | // Remove server if we disconnected 124 | if status == ConnectStatus::Disconnected { 125 | api.remove_server(server_id); 126 | } 127 | } 128 | 129 | #[allow(non_snake_case)] 130 | #[no_mangle] 131 | #[doc(hidden)] 132 | pub unsafe extern "C" fn ts3plugin_onServerStopEvent(server_id: u64, message: *const c_char) { 133 | let server_id = ::ServerId(server_id); 134 | let message = to_string!(message); 135 | let mut data = DATA.lock().unwrap(); 136 | let data = data.0.as_mut().unwrap(); 137 | let api = &mut data.0; 138 | let plugin = &mut data.1; 139 | let server = api.get_server_unwrap(server_id); 140 | plugin.server_stop(api, &server, message); 141 | } 142 | 143 | #[allow(non_snake_case)] 144 | #[no_mangle] 145 | #[doc(hidden)] 146 | pub unsafe extern "C" fn ts3plugin_onServerErrorEvent( 147 | server_id: u64, message: *const c_char, error: c_uint, return_code: *const c_char, 148 | extra_message: *const c_char, 149 | ) -> c_int { 150 | let server_id = ::ServerId(server_id); 151 | let message = to_string!(message); 152 | let error = transmute(error); 153 | let return_code = to_string!(return_code); 154 | let extra_message = to_string!(extra_message); 155 | let mut data = DATA.lock().unwrap(); 156 | let data = data.0.as_mut().unwrap(); 157 | let api = &mut data.0; 158 | let plugin = &mut data.1; 159 | let server = api.get_server_unwrap(server_id); 160 | let b = plugin.server_error(api, &server, error, message, return_code, extra_message); 161 | if b { 1 } else { 0 } 162 | } 163 | 164 | #[allow(non_snake_case)] 165 | #[no_mangle] 166 | #[doc(hidden)] 167 | pub unsafe extern "C" fn ts3plugin_onServerEditedEvent( 168 | server_id: u64, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 169 | ) { 170 | let server_id = ::ServerId(server_id); 171 | let invoker = if invoker_id == 0 { 172 | None 173 | } else { 174 | Some(::InvokerData::new( 175 | ::ConnectionId(invoker_id), 176 | to_string!(invoker_uid), 177 | to_string!(invoker_name), 178 | )) 179 | }; 180 | let mut data = DATA.lock().unwrap(); 181 | let data = data.0.as_mut().unwrap(); 182 | let api = &mut data.0; 183 | let plugin = &mut data.1; 184 | if let Some(ref invoker) = invoker { 185 | api.try_update_invoker(server_id, invoker); 186 | } 187 | if let Some(ref mut server) = api.get_mut_server(server_id) { 188 | server.update(); 189 | } 190 | let server = api.get_server_unwrap(server_id); 191 | plugin.server_edited(api, &server, invoker.map(|i| ::Invoker::new(server.clone(), i)).as_ref()); 192 | } 193 | 194 | #[allow(non_snake_case)] 195 | #[no_mangle] 196 | #[doc(hidden)] 197 | pub unsafe extern "C" fn ts3plugin_onServerConnectionInfoEvent(server_id: u64) { 198 | let server_id = ::ServerId(server_id); 199 | let mut data = DATA.lock().unwrap(); 200 | let data = data.0.as_mut().unwrap(); 201 | let api = &mut data.0; 202 | let plugin = &mut data.1; 203 | let server = api.get_server_unwrap(server_id); 204 | plugin.server_connection_info(api, &server); 205 | } 206 | 207 | #[allow(non_snake_case)] 208 | #[no_mangle] 209 | #[doc(hidden)] 210 | pub unsafe extern "C" fn ts3plugin_onConnectionInfoEvent(server_id: u64, connection_id: u16) { 211 | let server_id = ::ServerId(server_id); 212 | let connection_id = ::ConnectionId(connection_id); 213 | let mut data = DATA.lock().unwrap(); 214 | let data = data.0.as_mut().unwrap(); 215 | let api = &mut data.0; 216 | let plugin = &mut data.1; 217 | let server = api.get_server_unwrap(server_id); 218 | let connection = server.get_connection_unwrap(connection_id); 219 | plugin.connection_info(api, &server, &connection); 220 | } 221 | 222 | #[allow(non_snake_case)] 223 | #[no_mangle] 224 | #[doc(hidden)] 225 | pub unsafe extern "C" fn ts3plugin_onUpdateClientEvent( 226 | server_id: u64, connection_id: u16, invoker_id: u16, invoker_name: *const c_char, 227 | invoker_uid: *const c_char, 228 | ) { 229 | let server_id = ::ServerId(server_id); 230 | let connection_id = ::ConnectionId(connection_id); 231 | let invoker_id = ::ConnectionId(invoker_id); 232 | let invoker_name = to_string!(invoker_name); 233 | let invoker_uid = to_string!(invoker_uid); 234 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 235 | let mut data = DATA.lock().unwrap(); 236 | let data = data.0.as_mut().unwrap(); 237 | let api = &mut data.0; 238 | let plugin = &mut data.1; 239 | api.try_update_invoker(server_id, &invoker); 240 | 241 | // Save the old connection 242 | let old_connection; 243 | { 244 | let server = api.get_mut_server(server_id).unwrap(); 245 | // Try to get the old channel 246 | old_connection = server 247 | .remove_connection(connection_id) 248 | .unwrap_or(::ConnectionData::new(server_id, connection_id)); 249 | let connection = server.add_connection(connection_id); 250 | // Copy optional data from old connection 251 | connection.update_from(&old_connection); 252 | } 253 | let server = api.get_server_unwrap(server_id); 254 | let connection = server.get_connection_unwrap(connection_id); 255 | let old_connection = ::Connection::new(api, &old_connection); 256 | plugin.connection_properties_changed( 257 | api, 258 | &server, 259 | &connection, 260 | &old_connection, 261 | ::get_connection_changes(old_connection.properties(), connection.properties()), 262 | &::Invoker::new(server.clone(), invoker), 263 | ); 264 | } 265 | 266 | #[allow(non_snake_case)] 267 | #[no_mangle] 268 | #[doc(hidden)] 269 | pub unsafe extern "C" fn ts3plugin_onClientMoveEvent( 270 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 271 | visibility: c_int, move_message: *const c_char, 272 | ) { 273 | let server_id = ::ServerId(server_id); 274 | let connection_id = ::ConnectionId(connection_id); 275 | let old_channel_id = ::ChannelId(old_channel_id); 276 | let new_channel_id = ::ChannelId(new_channel_id); 277 | let visibility = transmute(visibility); 278 | let move_message = to_string!(move_message); 279 | let mut data = DATA.lock().unwrap(); 280 | let data = data.0.as_mut().unwrap(); 281 | let api = &mut data.0; 282 | let plugin = &mut data.1; 283 | if old_channel_id == ::ChannelId(0) { 284 | // Connection connected, this will also be called for ourselves 285 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 286 | let server = api.get_server_unwrap(server_id); 287 | let connection = server.get_connection_unwrap(connection_id); 288 | plugin.connection_changed(api, &server, &connection, true, move_message) 289 | } else if new_channel_id == ::ChannelId(0) { 290 | // Connection disconnected 291 | { 292 | let server = api.get_server_unwrap(server_id); 293 | let connection = server.get_connection_unwrap(connection_id); 294 | plugin.connection_changed(api, &server, &connection, false, move_message); 295 | } 296 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 297 | } else if old_channel_id == new_channel_id { 298 | // Connection announced 299 | match visibility { 300 | Visibility::Enter => { 301 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 302 | let server = api.get_server_unwrap(server_id); 303 | let connection = server.get_connection_unwrap(connection_id); 304 | plugin.connection_announced(api, &server, &connection, true); 305 | } 306 | Visibility::Leave => { 307 | { 308 | let server = api.get_server_unwrap(server_id); 309 | let connection = server.get_connection_unwrap(connection_id); 310 | plugin.connection_announced(api, &server, &connection, false); 311 | } 312 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 313 | } 314 | Visibility::Retain => {} 315 | } 316 | } else { 317 | // Connection switched channel 318 | // Add the connection if it entered visibility 319 | if visibility == Visibility::Enter { 320 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 321 | } 322 | // Update the channel 323 | { 324 | if let Some(connection) = 325 | api.get_mut_server(server_id).and_then(|s| s.get_mut_connection(connection_id)) 326 | { 327 | connection.channel_id = Ok(new_channel_id); 328 | } 329 | } 330 | { 331 | let server = api.get_server_unwrap(server_id); 332 | let connection = server.get_connection_unwrap(connection_id); 333 | let old_channel = server.get_channel_unwrap(old_channel_id); 334 | let new_channel = server.get_channel_unwrap(new_channel_id); 335 | plugin.connection_move( 336 | api, 337 | &server, 338 | &connection, 339 | &old_channel, 340 | &new_channel, 341 | visibility, 342 | ); 343 | } 344 | // Remove the connection if it left visibility 345 | if visibility == Visibility::Leave { 346 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 347 | } 348 | } 349 | } 350 | 351 | #[allow(non_snake_case)] 352 | #[no_mangle] 353 | #[doc(hidden)] 354 | pub unsafe extern "C" fn ts3plugin_onClientMoveMovedEvent( 355 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 356 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 357 | move_message: *const c_char, 358 | ) { 359 | let server_id = ::ServerId(server_id); 360 | let connection_id = ::ConnectionId(connection_id); 361 | let old_channel_id = ::ChannelId(old_channel_id); 362 | let new_channel_id = ::ChannelId(new_channel_id); 363 | let visibility = transmute(visibility); 364 | let invoker_id = ::ConnectionId(invoker_id); 365 | let invoker_name = to_string!(invoker_name); 366 | let invoker_uid = to_string!(invoker_uid); 367 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 368 | let move_message = to_string!(move_message); 369 | let mut data = DATA.lock().unwrap(); 370 | let data = data.0.as_mut().unwrap(); 371 | let api = &mut data.0; 372 | let plugin = &mut data.1; 373 | // Appart from the invoker, the same code as for ClientMove 374 | api.try_update_invoker(server_id, &invoker); 375 | if old_channel_id == ::ChannelId(0) { 376 | // Connection connected, this will also be called for ourselves 377 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 378 | let server = api.get_server_unwrap(server_id); 379 | let connection = server.get_connection_unwrap(connection_id); 380 | plugin.connection_changed(api, &server, &connection, true, move_message) 381 | } else if new_channel_id == ::ChannelId(0) { 382 | // Connection disconnected 383 | { 384 | let server = api.get_server_unwrap(server_id); 385 | let connection = server.get_connection_unwrap(connection_id); 386 | plugin.connection_changed(api, &server, &connection, false, move_message); 387 | } 388 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 389 | } else if old_channel_id == new_channel_id { 390 | // Connection announced 391 | match visibility { 392 | Visibility::Enter => { 393 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 394 | let server = api.get_server_unwrap(server_id); 395 | let connection = server.get_connection_unwrap(connection_id); 396 | plugin.connection_announced(api, &server, &connection, true); 397 | } 398 | Visibility::Leave => { 399 | { 400 | let server = api.get_server_unwrap(server_id); 401 | let connection = server.get_connection_unwrap(connection_id); 402 | plugin.connection_announced(api, &server, &connection, false); 403 | } 404 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 405 | } 406 | Visibility::Retain => {} 407 | } 408 | } else { 409 | // Connection switched channel 410 | // Add the connection if it entered visibility 411 | if visibility == Visibility::Enter { 412 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 413 | } 414 | // Update the channel 415 | { 416 | if let Some(connection) = 417 | api.get_mut_server(server_id).and_then(|s| s.get_mut_connection(connection_id)) 418 | { 419 | connection.channel_id = Ok(new_channel_id); 420 | } 421 | } 422 | { 423 | let server = api.get_server_unwrap(server_id); 424 | let connection = server.get_connection_unwrap(connection_id); 425 | let old_channel = server.get_channel_unwrap(old_channel_id); 426 | let new_channel = server.get_channel_unwrap(new_channel_id); 427 | plugin.connection_moved( 428 | api, 429 | &server, 430 | &connection, 431 | &old_channel, 432 | &new_channel, 433 | visibility, 434 | &::Invoker::new(server.clone(), invoker), 435 | ); 436 | } 437 | // Remove the connection if it left visibility 438 | if visibility == Visibility::Leave { 439 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 440 | } 441 | } 442 | } 443 | 444 | #[allow(non_snake_case, unused_variables)] 445 | #[no_mangle] 446 | #[doc(hidden)] 447 | pub unsafe extern "C" fn ts3plugin_onClientMoveSubscriptionEvent( 448 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, visibility: c_int, 449 | ) { 450 | let server_id = ::ServerId(server_id); 451 | let connection_id = ::ConnectionId(connection_id); 452 | //let old_channel_id = ::ChannelId(old_channel_id); 453 | //let new_channel_id = ::ChannelId(new_channel_id); 454 | let visibility = transmute(visibility); 455 | let mut data = DATA.lock().unwrap(); 456 | let data = data.0.as_mut().unwrap(); 457 | let api = &mut data.0; 458 | let plugin = &mut data.1; 459 | // Connection announced 460 | match visibility { 461 | Visibility::Enter => { 462 | api.get_mut_server(server_id).unwrap().add_connection(connection_id); 463 | let server = api.get_server_unwrap(server_id); 464 | let connection = server.get_connection_unwrap(connection_id); 465 | plugin.connection_announced(api, &server, &connection, true); 466 | } 467 | Visibility::Leave => { 468 | { 469 | let server = api.get_server_unwrap(server_id); 470 | let connection = server.get_connection_unwrap(connection_id); 471 | plugin.connection_announced(api, &server, &connection, false); 472 | } 473 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 474 | } 475 | Visibility::Retain => {} 476 | } 477 | } 478 | 479 | #[allow(non_snake_case, unused_variables)] 480 | #[no_mangle] 481 | #[doc(hidden)] 482 | pub unsafe extern "C" fn ts3plugin_onClientMoveTimeoutEvent( 483 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 484 | visibility: c_int, timeout_message: *const c_char, 485 | ) { 486 | let server_id = ::ServerId(server_id); 487 | let connection_id = ::ConnectionId(connection_id); 488 | //let old_channel_id = ::ChannelId(old_channel_id); 489 | //let new_channel_id = ::ChannelId(new_channel_id); 490 | //let visibility = transmute(visibility); 491 | let timeout_message = to_string!(timeout_message); 492 | let mut data = DATA.lock().unwrap(); 493 | let data = data.0.as_mut().unwrap(); 494 | let api = &mut data.0; 495 | let plugin = &mut data.1; 496 | { 497 | let server = api.get_server_unwrap(server_id); 498 | let connection = server.get_connection_unwrap(connection_id); 499 | plugin.connection_timeout(api, &server, &connection); 500 | } 501 | api.get_mut_server(server_id).unwrap().remove_connection(connection_id); 502 | } 503 | 504 | #[allow(non_snake_case, unused_variables)] 505 | #[no_mangle] 506 | #[doc(hidden)] 507 | pub unsafe extern "C" fn ts3plugin_onNewChannelEvent( 508 | server_id: u64, channel_id: u64, parent_channel_id: u64, 509 | ) { 510 | let server_id = ::ServerId(server_id); 511 | let channel_id = ::ChannelId(channel_id); 512 | //let parent_channel_id = ::ChannelId(parent_channel_id); 513 | let mut data = DATA.lock().unwrap(); 514 | let data = data.0.as_mut().unwrap(); 515 | let api = &mut data.0; 516 | let plugin = &mut data.1; 517 | let err = api.get_mut_server(server_id).unwrap().add_channel(channel_id).err(); 518 | if let Some(error) = err { 519 | error!(api, "Can't get channel information", error); 520 | } 521 | let server = api.get_server_unwrap(server_id); 522 | let channel = server.get_channel_unwrap(channel_id); 523 | plugin.channel_announced(api, &server, &channel); 524 | } 525 | 526 | #[allow(non_snake_case)] 527 | #[no_mangle] 528 | #[doc(hidden)] 529 | pub unsafe extern "C" fn ts3plugin_onChannelDescriptionUpdateEvent( 530 | server_id: u64, channel_id: u64, 531 | ) { 532 | let server_id = ::ServerId(server_id); 533 | let channel_id = ::ChannelId(channel_id); 534 | let mut data = DATA.lock().unwrap(); 535 | let data = data.0.as_mut().unwrap(); 536 | let api = &mut data.0; 537 | let plugin = &mut data.1; 538 | // FIXME 539 | /*// Seems like I really like constructions like that, I failed to do it simpler 540 | // because I can't borrow api to print an error message in the inner part. 541 | if let Err(error) = if let Some(channel) = api.get_mut_server(server_id) 542 | .unwrap().get_mut_channel(channel_id) { 543 | channel.optional_data.update_description(); 544 | channel.get_optional_data().get_description().map(|_| ()) 545 | } else { 546 | Ok(()) 547 | } { 548 | error!(api, "Can't get channel description", error); 549 | }*/ 550 | let server = api.get_server_unwrap(server_id); 551 | let channel = server.get_channel_unwrap(channel_id); 552 | plugin.channel_description_updated(api, &server, &channel); 553 | } 554 | 555 | #[allow(non_snake_case)] 556 | #[no_mangle] 557 | #[doc(hidden)] 558 | pub unsafe extern "C" fn ts3plugin_onUpdateChannelEvent(server_id: u64, channel_id: u64) { 559 | let server_id = ::ServerId(server_id); 560 | let channel_id = ::ChannelId(channel_id); 561 | let mut data = DATA.lock().unwrap(); 562 | let data = data.0.as_mut().unwrap(); 563 | let api = &mut data.0; 564 | let plugin = &mut data.1; 565 | let old_channel; 566 | if let Err(error) = { 567 | let server = api.get_mut_server(server_id).unwrap(); 568 | // Try to get the old channel 569 | old_channel = 570 | server.remove_channel(channel_id).unwrap_or(::ChannelData::new(server_id, channel_id)); 571 | match server.add_channel(channel_id) { 572 | Ok(_) => { 573 | let channel = server.get_mut_channel(channel_id).unwrap(); 574 | // Copy optional data from old channel 575 | channel.update_from(&old_channel); 576 | Ok(()) 577 | } 578 | Err(error) => Err(error), 579 | } 580 | } { 581 | error!(api, "Can't get channel information", error); 582 | } 583 | let server = api.get_server_unwrap(server_id); 584 | let channel = server.get_channel_unwrap(channel_id); 585 | plugin.channel_updated(api, &server, &channel, &::Channel::new(api, &old_channel)); 586 | } 587 | 588 | #[allow(non_snake_case, unused_variables)] 589 | #[no_mangle] 590 | #[doc(hidden)] 591 | pub unsafe extern "C" fn ts3plugin_onNewChannelCreatedEvent( 592 | server_id: u64, channel_id: u64, parent_channel_id: u64, invoker_id: u16, 593 | invoker_name: *const c_char, invoker_uid: *const c_char, 594 | ) { 595 | let server_id = ::ServerId(server_id); 596 | let channel_id = ::ChannelId(channel_id); 597 | let parent_channel_id = ::ChannelId(parent_channel_id); 598 | let invoker = if invoker_id == 0 { 599 | None 600 | } else { 601 | Some(::InvokerData::new( 602 | ::ConnectionId(invoker_id), 603 | to_string!(invoker_uid), 604 | to_string!(invoker_name), 605 | )) 606 | }; 607 | let mut data = DATA.lock().unwrap(); 608 | let data = data.0.as_mut().unwrap(); 609 | let api = &mut data.0; 610 | let plugin = &mut data.1; 611 | if let Some(ref invoker) = invoker { 612 | api.try_update_invoker(server_id, invoker); 613 | } 614 | if let Some(error) = match api.get_mut_server(server_id).unwrap().add_channel(channel_id) { 615 | Ok(channel) => { 616 | channel.parent_channel_id = Ok(parent_channel_id); 617 | None 618 | } 619 | Err(error) => Some(error), 620 | } { 621 | error!(api, "Can't get channel information", error); 622 | } 623 | let server = api.get_server_unwrap(server_id); 624 | let channel = server.get_channel_unwrap(channel_id); 625 | plugin.channel_created( 626 | api, 627 | &server, 628 | &channel, 629 | invoker.map(|i| ::Invoker::new(server.clone(), i)).as_ref(), 630 | ); 631 | } 632 | 633 | #[allow(non_snake_case)] 634 | #[no_mangle] 635 | #[doc(hidden)] 636 | pub unsafe extern "C" fn ts3plugin_onDelChannelEvent( 637 | server_id: u64, channel_id: u64, invoker_id: u16, invoker_name: *const c_char, 638 | invoker_uid: *const c_char, 639 | ) { 640 | let server_id = ::ServerId(server_id); 641 | let channel_id = ::ChannelId(channel_id); 642 | let invoker = if invoker_id == 0 { 643 | None 644 | } else { 645 | Some(::InvokerData::new( 646 | ::ConnectionId(invoker_id), 647 | to_string!(invoker_uid), 648 | to_string!(invoker_name), 649 | )) 650 | }; 651 | let mut data = DATA.lock().unwrap(); 652 | let data = data.0.as_mut().unwrap(); 653 | let api = &mut data.0; 654 | let plugin = &mut data.1; 655 | if let Some(ref invoker) = invoker { 656 | api.try_update_invoker(server_id, invoker); 657 | } 658 | { 659 | let server = api.get_server_unwrap(server_id); 660 | let channel = server.get_channel_unwrap(channel_id); 661 | plugin.channel_deleted( 662 | api, 663 | &server, 664 | &channel, 665 | invoker.map(|i| ::Invoker::new(server.clone(), i)).as_ref(), 666 | ); 667 | } 668 | if api.get_mut_server(server_id).and_then(|s| s.remove_channel(channel_id)).is_none() { 669 | api.log_or_print("Can't remove channel", "rust-ts3plugin", ::LogLevel::Error); 670 | } 671 | } 672 | 673 | #[allow(non_snake_case)] 674 | #[no_mangle] 675 | #[doc(hidden)] 676 | pub unsafe extern "C" fn ts3plugin_onUpdateChannelEditedEvent( 677 | server_id: u64, channel_id: u64, invoker_id: u16, invoker_name: *const c_char, 678 | invoker_uid: *const c_char, 679 | ) { 680 | let server_id = ::ServerId(server_id); 681 | let channel_id = ::ChannelId(channel_id); 682 | let invoker_id = ::ConnectionId(invoker_id); 683 | let invoker_name = to_string!(invoker_name); 684 | let invoker_uid = to_string!(invoker_uid); 685 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 686 | let mut data = DATA.lock().unwrap(); 687 | let data = data.0.as_mut().unwrap(); 688 | let api = &mut data.0; 689 | let plugin = &mut data.1; 690 | api.try_update_invoker(server_id, &invoker); 691 | let old_channel; 692 | if let Err(error) = { 693 | let server = api.get_mut_server(server_id).unwrap(); 694 | // Try to get the old channel 695 | old_channel = 696 | server.remove_channel(channel_id).unwrap_or(::ChannelData::new(server_id, channel_id)); 697 | match server.add_channel(channel_id) { 698 | Ok(_) => { 699 | let channel = server.get_mut_channel(channel_id).unwrap(); 700 | // Copy optional data from old channel 701 | channel.update_from(&old_channel); 702 | Ok(()) 703 | } 704 | Err(error) => Err(error), 705 | } 706 | } { 707 | error!(api, "Can't get channel information", error); 708 | } 709 | let server = api.get_server_unwrap(server_id); 710 | let channel = server.get_channel_unwrap(channel_id); 711 | plugin.channel_edited( 712 | api, 713 | &server, 714 | &channel, 715 | &::Channel::new(api, &old_channel), 716 | &::Invoker::new(server.clone(), invoker), 717 | ); 718 | } 719 | 720 | #[allow(non_snake_case)] 721 | #[no_mangle] 722 | #[doc(hidden)] 723 | pub unsafe extern "C" fn ts3plugin_onChannelPasswordChangedEvent(server_id: u64, channel_id: u64) { 724 | let server_id = ::ServerId(server_id); 725 | let channel_id = ::ChannelId(channel_id); 726 | let mut data = DATA.lock().unwrap(); 727 | let data = data.0.as_mut().unwrap(); 728 | let api = &mut data.0; 729 | let plugin = &mut data.1; 730 | let server = api.get_server_unwrap(server_id); 731 | let channel = server.get_channel_unwrap(channel_id); 732 | plugin.channel_password_updated(api, &server, &channel); 733 | } 734 | 735 | #[allow(non_snake_case)] 736 | #[no_mangle] 737 | #[doc(hidden)] 738 | pub unsafe extern "C" fn ts3plugin_onChannelMoveEvent( 739 | server_id: u64, channel_id: u64, new_parent_channel_id: u64, invoker_id: u16, 740 | invoker_name: *const c_char, invoker_uid: *const c_char, 741 | ) { 742 | let server_id = ::ServerId(server_id); 743 | let channel_id = ::ChannelId(channel_id); 744 | let new_parent_channel_id = ::ChannelId(new_parent_channel_id); 745 | let invoker = if invoker_id == 0 { 746 | None 747 | } else { 748 | Some(::InvokerData::new( 749 | ::ConnectionId(invoker_id), 750 | to_string!(invoker_uid), 751 | to_string!(invoker_name), 752 | )) 753 | }; 754 | let mut data = DATA.lock().unwrap(); 755 | let data = data.0.as_mut().unwrap(); 756 | let api = &mut data.0; 757 | let plugin = &mut data.1; 758 | if let Some(ref invoker) = invoker { 759 | api.try_update_invoker(server_id, invoker); 760 | } 761 | { 762 | let server = api.get_server_unwrap(server_id); 763 | let channel = server.get_channel_unwrap(channel_id); 764 | let new_parent_channel = server.get_channel_unwrap(new_parent_channel_id); 765 | plugin.channel_moved( 766 | api, 767 | &server, 768 | &channel, 769 | &new_parent_channel, 770 | invoker.map(|i| ::Invoker::new(server.clone(), i)).as_ref(), 771 | ); 772 | } 773 | if let Some(channel) = api.get_mut_server(server_id).and_then(|s| s.get_mut_channel(channel_id)) 774 | { 775 | channel.parent_channel_id = Ok(new_parent_channel_id); 776 | } 777 | } 778 | 779 | // Ignore clippy warnings, we can't change the TeamSpeak interface 780 | #[allow(clippy::too_many_arguments)] 781 | #[allow(non_snake_case)] 782 | #[no_mangle] 783 | #[doc(hidden)] 784 | pub unsafe extern "C" fn ts3plugin_onTextMessageEvent( 785 | server_id: u64, target_mode: u16, receiver_id: u16, invoker_id: u16, 786 | invoker_name: *const c_char, invoker_uid: *const c_char, message: *const c_char, 787 | ignored: c_int, 788 | ) -> c_int { 789 | let server_id = ::ServerId(server_id); 790 | let target_mode = transmute(target_mode as i32); 791 | let receiver_id = ::ConnectionId(receiver_id); 792 | let invoker_id = ::ConnectionId(invoker_id); 793 | let invoker_name = to_string!(invoker_name); 794 | let invoker_uid = to_string!(invoker_uid); 795 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 796 | let message = to_string!(message); 797 | let ignored = ignored != 0; 798 | let mut data = DATA.lock().unwrap(); 799 | let data = data.0.as_mut().unwrap(); 800 | let api = &mut data.0; 801 | let plugin = &mut data.1; 802 | api.try_update_invoker(server_id, &invoker); 803 | let message_receiver = match target_mode { 804 | ::TextMessageTargetMode::Client => ::MessageReceiver::Connection(receiver_id), 805 | ::TextMessageTargetMode::Channel => ::MessageReceiver::Channel, 806 | ::TextMessageTargetMode::Server => ::MessageReceiver::Server, 807 | _ => { 808 | api.log_or_print( 809 | "Got invalid TextMessageTargetMode", 810 | "rust-ts3plugin", 811 | ::LogLevel::Error, 812 | ); 813 | ::MessageReceiver::Server 814 | } 815 | }; 816 | let server = api.get_server_unwrap(server_id); 817 | if plugin.message( 818 | api, 819 | &server, 820 | &::Invoker::new(server.clone(), invoker), 821 | message_receiver, 822 | message, 823 | ignored, 824 | ) { 825 | 1 826 | } else { 827 | 0 828 | } 829 | } 830 | 831 | #[allow(non_snake_case)] 832 | #[no_mangle] 833 | #[doc(hidden)] 834 | pub unsafe extern "C" fn ts3plugin_onClientPokeEvent( 835 | server_id: u64, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 836 | message: *const c_char, ignored: c_int, 837 | ) -> c_int { 838 | let server_id = ::ServerId(server_id); 839 | let invoker_id = ::ConnectionId(invoker_id); 840 | let invoker_name = to_string!(invoker_name); 841 | let invoker_uid = to_string!(invoker_uid); 842 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 843 | let message = to_string!(message); 844 | let ignored = ignored != 0; 845 | let mut data = DATA.lock().unwrap(); 846 | let data = data.0.as_mut().unwrap(); 847 | let api = &mut data.0; 848 | let plugin = &mut data.1; 849 | api.try_update_invoker(server_id, &invoker); 850 | let server = api.get_server_unwrap(server_id); 851 | if plugin.poke(api, &server, &::Invoker::new(server.clone(), invoker), message, ignored) { 852 | 1 853 | } else { 854 | 0 855 | } 856 | } 857 | 858 | #[allow(clippy::too_many_arguments)] 859 | #[allow(non_snake_case)] 860 | #[no_mangle] 861 | #[doc(hidden)] 862 | pub unsafe extern "C" fn ts3plugin_onClientKickFromChannelEvent( 863 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 864 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 865 | message: *const c_char, 866 | ) { 867 | let server_id = ::ServerId(server_id); 868 | let connection_id = ::ConnectionId(connection_id); 869 | let old_channel_id = ::ChannelId(old_channel_id); 870 | let new_channel_id = ::ChannelId(new_channel_id); 871 | let visibility = transmute(visibility); 872 | let invoker_id = ::ConnectionId(invoker_id); 873 | let invoker_name = to_string!(invoker_name); 874 | let invoker_uid = to_string!(invoker_uid); 875 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 876 | let message = to_string!(message); 877 | let mut data = DATA.lock().unwrap(); 878 | let data = data.0.as_mut().unwrap(); 879 | let api = &mut data.0; 880 | let plugin = &mut data.1; 881 | api.try_update_invoker(server_id, &invoker); 882 | { 883 | let server = api.get_server_unwrap(server_id); 884 | let connection = server.get_connection_unwrap(connection_id); 885 | let old_channel = server.get_channel_unwrap(old_channel_id); 886 | let new_channel = server.get_channel_unwrap(new_channel_id); 887 | plugin.channel_kick( 888 | api, 889 | &server, 890 | &connection, 891 | &old_channel, 892 | &new_channel, 893 | visibility, 894 | &::Invoker::new(server.clone(), invoker), 895 | message, 896 | ); 897 | } 898 | // Remove the kicked connection if it is not visible anymore 899 | if visibility == ::Visibility::Leave { 900 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 901 | } else if let Some(connection) = api.get_mut_server(server_id).and_then(|s| 902 | // Update the current channel of the connection 903 | s.get_mut_connection(connection_id)) 904 | { 905 | connection.channel_id = Ok(new_channel_id); 906 | } 907 | } 908 | 909 | #[allow(clippy::too_many_arguments)] 910 | #[allow(non_snake_case, unused_variables)] 911 | #[no_mangle] 912 | #[doc(hidden)] 913 | pub unsafe extern "C" fn ts3plugin_onClientKickFromServerEvent( 914 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 915 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 916 | message: *const c_char, 917 | ) { 918 | let server_id = ::ServerId(server_id); 919 | let connection_id = ::ConnectionId(connection_id); 920 | let old_channel_id = ::ChannelId(old_channel_id); 921 | let new_channel_id = ::ChannelId(new_channel_id); 922 | //let visibility = transmute(visibility); 923 | let invoker_id = ::ConnectionId(invoker_id); 924 | let invoker_name = to_string!(invoker_name); 925 | let invoker_uid = to_string!(invoker_uid); 926 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 927 | let message = to_string!(message); 928 | let mut data = DATA.lock().unwrap(); 929 | let data = data.0.as_mut().unwrap(); 930 | let api = &mut data.0; 931 | let plugin = &mut data.1; 932 | api.try_update_invoker(server_id, &invoker); 933 | { 934 | let server = api.get_server_unwrap(server_id); 935 | let connection = server.get_connection_unwrap(connection_id); 936 | plugin.server_kick( 937 | api, 938 | &server, 939 | &connection, 940 | &::Invoker::new(server.clone(), invoker), 941 | message, 942 | ); 943 | } 944 | // Remove the kicked connection 945 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 946 | } 947 | 948 | #[allow(clippy::too_many_arguments)] 949 | #[allow(non_snake_case, unused_variables)] 950 | #[no_mangle] 951 | #[doc(hidden)] 952 | pub unsafe extern "C" fn ts3plugin_onClientBanFromServerEvent( 953 | server_id: u64, connection_id: u16, old_channel_id: u64, new_channel_id: u64, 954 | visibility: c_int, invoker_id: u16, invoker_name: *const c_char, invoker_uid: *const c_char, 955 | time: u64, message: *const c_char, 956 | ) { 957 | let server_id = ::ServerId(server_id); 958 | let connection_id = ::ConnectionId(connection_id); 959 | //let old_channel_id = ::ChannelId(old_channel_id); 960 | //let new_channel_id = ::ChannelId(new_channel_id); 961 | //let visibility = transmute(visibility); 962 | let invoker_id = ::ConnectionId(invoker_id); 963 | let invoker_name = to_string!(invoker_name); 964 | let invoker_uid = to_string!(invoker_uid); 965 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 966 | let message = to_string!(message); 967 | let mut data = DATA.lock().unwrap(); 968 | let data = data.0.as_mut().unwrap(); 969 | let api = &mut data.0; 970 | let plugin = &mut data.1; 971 | api.try_update_invoker(server_id, &invoker); 972 | { 973 | let server = api.get_server_unwrap(server_id); 974 | let connection = server.get_connection_unwrap(connection_id); 975 | plugin.server_ban( 976 | api, 977 | &server, 978 | &connection, 979 | &::Invoker::new(server.clone(), invoker), 980 | message, 981 | time, 982 | ); 983 | } 984 | // Remove the banned connection 985 | api.get_mut_server(server_id).map(|s| s.remove_connection(connection_id)); 986 | } 987 | 988 | #[allow(non_snake_case)] 989 | #[no_mangle] 990 | #[doc(hidden)] 991 | pub unsafe extern "C" fn ts3plugin_onTalkStatusChangeEvent( 992 | server_id: u64, talking: c_int, whispering: c_int, connection_id: u16, 993 | ) { 994 | let server_id = ::ServerId(server_id); 995 | let talking = transmute(talking); 996 | let whispering = whispering != 0; 997 | let connection_id = ::ConnectionId(connection_id); 998 | let mut data = DATA.lock().unwrap(); 999 | let data = data.0.as_mut().unwrap(); 1000 | let api = &mut data.0; 1001 | let plugin = &mut data.1; 1002 | { 1003 | let server = api.get_server_unwrap(server_id); 1004 | let connection = server.get_connection_unwrap(connection_id); 1005 | plugin.talking_changed(api, &server, &connection, talking, whispering); 1006 | } 1007 | // Update the connection 1008 | if let Some(connection) = 1009 | api.get_mut_server(server_id).and_then(|s| s.get_mut_connection(connection_id)) 1010 | { 1011 | connection.talking = Ok(talking); 1012 | connection.whispering = Ok(whispering); 1013 | } 1014 | } 1015 | 1016 | #[allow(non_snake_case)] 1017 | #[no_mangle] 1018 | #[doc(hidden)] 1019 | pub unsafe extern "C" fn ts3plugin_onAvatarUpdated( 1020 | server_id: u64, connection_id: u16, avatar_path: *const c_char, 1021 | ) { 1022 | let server_id = ::ServerId(server_id); 1023 | let connection_id = ::ConnectionId(connection_id); 1024 | let path = if avatar_path.is_null() { None } else { Some(to_string!(avatar_path)) }; 1025 | let mut data = DATA.lock().unwrap(); 1026 | let data = data.0.as_mut().unwrap(); 1027 | let api = &mut data.0; 1028 | let plugin = &mut data.1; 1029 | let server = api.get_server_unwrap(server_id); 1030 | let connection = server.get_connection_unwrap(connection_id); 1031 | plugin.avatar_changed(api, &server, &connection, path); 1032 | } 1033 | #[allow(non_snake_case)] 1034 | #[no_mangle] 1035 | #[doc(hidden)] 1036 | pub unsafe extern "C" fn ts3plugin_onClientChannelGroupChangedEvent( 1037 | server_id: u64, channel_group_id: u64, channel_id: u64, connection_id: u16, invoker_id: u16, 1038 | invoker_name: *const c_char, invoker_uid: *const c_char, 1039 | ) { 1040 | let server_id = ::ServerId(server_id); 1041 | let channel_group_id = ::ChannelGroupId(channel_group_id); 1042 | let channel_id = ::ChannelId(channel_id); 1043 | let connection_id = ::ConnectionId(connection_id); 1044 | let invoker_id = ::ConnectionId(invoker_id); 1045 | let invoker_name = to_string!(invoker_name); 1046 | let invoker_uid = to_string!(invoker_uid); 1047 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 1048 | let mut data = DATA.lock().unwrap(); 1049 | let data = data.0.as_mut().unwrap(); 1050 | let api = &mut data.0; 1051 | let plugin = &mut data.1; 1052 | api.try_update_invoker(server_id, &invoker); 1053 | let server = api.get_server_unwrap(server_id); 1054 | let connection = server.get_connection_unwrap(connection_id); 1055 | let channel_group = server.get_channel_group_unwrap(channel_group_id); 1056 | let channel = server.get_channel_unwrap(channel_id); 1057 | plugin.connection_channel_group_changed( 1058 | api, 1059 | &server, 1060 | &connection, 1061 | &channel_group, 1062 | &channel, 1063 | &::Invoker::new(server.clone(), invoker), 1064 | ); 1065 | } 1066 | 1067 | #[allow(clippy::too_many_arguments)] 1068 | #[allow(non_snake_case)] 1069 | #[no_mangle] 1070 | #[doc(hidden)] 1071 | pub unsafe extern "C" fn ts3plugin_onServerGroupClientAddedEvent( 1072 | server_id: u64, connection_id: u16, connection_name: *const c_char, 1073 | connection_uid: *const c_char, server_group_id: u64, invoker_id: u16, 1074 | invoker_name: *const c_char, invoker_uid: *const c_char, 1075 | ) { 1076 | let server_id = ::ServerId(server_id); 1077 | let connection_id = ::ConnectionId(connection_id); 1078 | let connection_name = to_string!(connection_name); 1079 | let connection_uid = to_string!(connection_uid); 1080 | let connection = ::InvokerData::new(connection_id, connection_uid, connection_name); 1081 | let server_group_id = ::ServerGroupId(server_group_id); 1082 | let invoker_id = ::ConnectionId(invoker_id); 1083 | let invoker_name = to_string!(invoker_name); 1084 | let invoker_uid = to_string!(invoker_uid); 1085 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 1086 | let mut data = DATA.lock().unwrap(); 1087 | let data = data.0.as_mut().unwrap(); 1088 | let api = &mut data.0; 1089 | let plugin = &mut data.1; 1090 | api.try_update_invoker(server_id, &invoker); 1091 | let server = api.get_server_unwrap(server_id); 1092 | let server_group = server.get_server_group_unwrap(server_group_id); 1093 | plugin.connection_server_group_added( 1094 | api, 1095 | &server, 1096 | &::Invoker::new(server.clone(), connection), 1097 | &server_group, 1098 | &::Invoker::new(server.clone(), invoker), 1099 | ); 1100 | } 1101 | 1102 | #[allow(clippy::too_many_arguments)] 1103 | #[allow(non_snake_case)] 1104 | #[no_mangle] 1105 | #[doc(hidden)] 1106 | pub unsafe extern "C" fn ts3plugin_onServerGroupClientDeletedEvent( 1107 | server_id: u64, connection_id: u16, connection_name: *const c_char, 1108 | connection_uid: *const c_char, server_group_id: u64, invoker_id: u16, 1109 | invoker_name: *const c_char, invoker_uid: *const c_char, 1110 | ) { 1111 | let server_id = ::ServerId(server_id); 1112 | let connection_id = ::ConnectionId(connection_id); 1113 | let connection_name = to_string!(connection_name); 1114 | let connection_uid = to_string!(connection_uid); 1115 | let connection = ::InvokerData::new(connection_id, connection_uid, connection_name); 1116 | let server_group_id = ::ServerGroupId(server_group_id); 1117 | let invoker_id = ::ConnectionId(invoker_id); 1118 | let invoker_name = to_string!(invoker_name); 1119 | let invoker_uid = to_string!(invoker_uid); 1120 | let invoker = ::InvokerData::new(invoker_id, invoker_uid, invoker_name); 1121 | let mut data = DATA.lock().unwrap(); 1122 | let data = data.0.as_mut().unwrap(); 1123 | let api = &mut data.0; 1124 | let plugin = &mut data.1; 1125 | api.try_update_invoker(server_id, &invoker); 1126 | let server = api.get_server_unwrap(server_id); 1127 | let server_group = server.get_server_group_unwrap(server_group_id); 1128 | plugin.connection_server_group_removed( 1129 | api, 1130 | &server, 1131 | &::Invoker::new(server.clone(), connection), 1132 | &server_group, 1133 | &::Invoker::new(server.clone(), invoker), 1134 | ); 1135 | } 1136 | 1137 | #[allow(non_snake_case)] 1138 | #[no_mangle] 1139 | #[doc(hidden)] 1140 | pub unsafe extern "C" fn ts3plugin_onServerPermissionErrorEvent( 1141 | server_id: u64, message: *const c_char, error: c_uint, return_code: *const c_char, 1142 | permission_id: c_uint, 1143 | ) -> c_int { 1144 | let server_id = ::ServerId(server_id); 1145 | let message = to_string!(message); 1146 | let error = transmute(error); 1147 | let return_code = to_string!(return_code); 1148 | let permission_id = ::PermissionId(permission_id); 1149 | let mut data = DATA.lock().unwrap(); 1150 | let data = data.0.as_mut().unwrap(); 1151 | let api = &mut data.0; 1152 | let plugin = &mut data.1; 1153 | let server = api.get_server_unwrap(server_id); 1154 | let permission = api.get_permission(permission_id).unwrap(); 1155 | if plugin.permission_error(api, &server, permission, error, message, return_code) { 1156 | 1 1157 | } else { 1158 | 0 1159 | } 1160 | } 1161 | 1162 | #[allow(non_snake_case)] 1163 | #[no_mangle] 1164 | #[doc(hidden)] 1165 | pub unsafe extern "C" fn ts3plugin_onEditPlaybackVoiceDataEvent( 1166 | server_id: u64, connection_id: u16, samples: *mut c_short, sample_count: c_int, channels: c_int, 1167 | ) { 1168 | let server_id = ::ServerId(server_id); 1169 | let connection_id = ::ConnectionId(connection_id); 1170 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1171 | let mut data = DATA.lock().unwrap(); 1172 | let data = data.0.as_mut().unwrap(); 1173 | let api = &mut data.0; 1174 | let plugin = &mut data.1; 1175 | let server = api.get_server_unwrap(server_id); 1176 | let connection = server.get_connection_unwrap(connection_id); 1177 | plugin.playback_voice_data(api, &server, &connection, samples, channels); 1178 | } 1179 | 1180 | #[allow(non_snake_case)] 1181 | #[no_mangle] 1182 | #[doc(hidden)] 1183 | pub unsafe extern "C" fn ts3plugin_onEditPostProcessVoiceDataEvent( 1184 | server_id: u64, connection_id: u16, samples: *mut c_short, sample_count: c_int, 1185 | channels: c_int, channel_speaker_array: *const c_uint, channel_fill_mask: *mut c_uint, 1186 | ) { 1187 | let server_id = ::ServerId(server_id); 1188 | let connection_id = ::ConnectionId(connection_id); 1189 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1190 | let channel_speaker_array = 1191 | slice::from_raw_parts(channel_speaker_array as *mut ::Speaker, channels as usize); 1192 | let channel_fill_mask = channel_fill_mask.as_mut().unwrap(); 1193 | let mut data = DATA.lock().unwrap(); 1194 | let data = data.0.as_mut().unwrap(); 1195 | let api = &mut data.0; 1196 | let plugin = &mut data.1; 1197 | let server = api.get_server_unwrap(server_id); 1198 | let connection = server.get_connection_unwrap(connection_id); 1199 | plugin.post_process_voice_data( 1200 | api, 1201 | &server, 1202 | &connection, 1203 | samples, 1204 | channels, 1205 | channel_speaker_array, 1206 | channel_fill_mask, 1207 | ); 1208 | } 1209 | 1210 | #[allow(non_snake_case)] 1211 | #[no_mangle] 1212 | #[doc(hidden)] 1213 | pub unsafe extern "C" fn ts3plugin_onEditMixedPlaybackVoiceDataEvent( 1214 | server_id: u64, samples: *mut c_short, sample_count: c_int, channels: c_int, 1215 | channel_speaker_array: *const c_uint, channel_fill_mask: *mut c_uint, 1216 | ) { 1217 | let server_id = ::ServerId(server_id); 1218 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1219 | let channel_speaker_array = 1220 | slice::from_raw_parts(channel_speaker_array as *mut ::Speaker, channels as usize); 1221 | let channel_fill_mask = channel_fill_mask.as_mut().unwrap(); 1222 | let mut data = DATA.lock().unwrap(); 1223 | let data = data.0.as_mut().unwrap(); 1224 | let api = &mut data.0; 1225 | let plugin = &mut data.1; 1226 | let server = api.get_server_unwrap(server_id); 1227 | plugin.mixed_playback_voice_data( 1228 | api, 1229 | &server, 1230 | samples, 1231 | channels, 1232 | channel_speaker_array, 1233 | channel_fill_mask, 1234 | ); 1235 | } 1236 | 1237 | #[allow(non_snake_case)] 1238 | #[no_mangle] 1239 | #[doc(hidden)] 1240 | pub unsafe extern "C" fn ts3plugin_onEditCapturedVoiceDataEvent( 1241 | server_id: u64, samples: *mut c_short, sample_count: c_int, channels: c_int, edited: *mut c_int, 1242 | ) { 1243 | let server_id = ::ServerId(server_id); 1244 | let samples = slice::from_raw_parts_mut(samples, (sample_count * channels) as usize); 1245 | let mut send = (*edited & 2) != 0; 1246 | let mut data = DATA.lock().unwrap(); 1247 | let data = data.0.as_mut().unwrap(); 1248 | let api = &mut data.0; 1249 | let plugin = &mut data.1; 1250 | let server = api.get_server_unwrap(server_id); 1251 | // Set the first bit if the sound data were edited 1252 | *edited |= plugin.captured_voice_data(api, &server, samples, channels, &mut send) as c_int; 1253 | // Set the second bit of `edited` to `send` 1254 | *edited = (*edited & !2) | ((send as c_int) << 1); 1255 | } 1256 | 1257 | #[allow(non_snake_case)] 1258 | #[no_mangle] 1259 | #[doc(hidden)] 1260 | pub unsafe extern "C" fn ts3plugin_onPluginCommandEvent( 1261 | server_id: u64, plugin_name: *const c_char, plugin_command: *const c_char, invoker_id: u16, 1262 | invoker_name: *const c_char, invoker_uid: *const c_char, 1263 | ) { 1264 | let server_id = ::ServerId(server_id); 1265 | let invoker = if invoker_id == 0 { 1266 | None 1267 | } else { 1268 | Some(::InvokerData::new( 1269 | ::ConnectionId(invoker_id), 1270 | to_string!(invoker_uid), 1271 | to_string!(invoker_name), 1272 | )) 1273 | }; 1274 | let mut data = DATA.lock().unwrap(); 1275 | let data = data.0.as_mut().unwrap(); 1276 | let api = &mut data.0; 1277 | let plugin = &mut data.1; 1278 | if let Some(ref invoker) = invoker { 1279 | api.try_update_invoker(server_id, invoker); 1280 | } 1281 | let server = api.get_server_unwrap(server_id); 1282 | plugin.plugin_message( 1283 | api, 1284 | &server, 1285 | to_string!(plugin_name), 1286 | to_string!(plugin_command), 1287 | invoker.map(|i| ::Invoker::new(server.clone(), i)).as_ref(), 1288 | ); 1289 | } 1290 | 1291 | #[allow(non_snake_case)] 1292 | #[no_mangle] 1293 | #[doc(hidden)] 1294 | pub unsafe extern "C" fn ts3plugin_processCommand(server_id: u64, command: *const c_char) -> c_int { 1295 | let server_id = ::ServerId(server_id); 1296 | let mut data = DATA.lock().unwrap(); 1297 | let data = data.0.as_mut().unwrap(); 1298 | let api = &mut data.0; 1299 | let plugin = &mut data.1; 1300 | let server = api.get_server_unwrap(server_id); 1301 | if plugin.process_command(api, &server, to_string!(command)) { 0 } else { 1 } 1302 | } 1303 | --------------------------------------------------------------------------------