1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5use super::ConfigError;
6
7#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct Config {
9 pub bridge: BridgeConfig,
10 #[serde(default)]
11 pub registration: RegistrationConfig,
12 pub auth: AuthConfig,
13 pub logging: LoggingConfig,
14 pub database: DatabaseConfig,
15 pub room: RoomConfig,
16 pub portal: PortalConfig,
17 #[serde(default)]
18 pub limits: LimitsConfig,
19 pub ghosts: GhostsConfig,
20 #[serde(default)]
21 pub metrics: MetricsConfig,
22 pub telegram: TelegramConfig,
23}
24
25#[derive(Debug, Clone, Deserialize, Serialize)]
26pub struct BridgeConfig {
27 pub domain: String,
28 #[serde(default = "default_port")]
29 pub port: u16,
30 #[serde(default = "default_bind_address")]
31 pub bind_address: String,
32 #[serde(default)]
33 pub homeserver_url: String,
34 #[serde(default = "default_presence_interval")]
35 pub presence_interval: u64,
36 #[serde(default)]
37 pub disable_presence: bool,
38 #[serde(default)]
39 pub disable_typing_notifications: bool,
40 #[serde(default)]
41 pub disable_telegram_mentions: bool,
42 #[serde(default)]
43 pub disable_deletion_forwarding: bool,
44 #[serde(default)]
45 pub enable_self_service_bridging: bool,
46 #[serde(default)]
47 pub disable_portal_bridging: bool,
48 #[serde(default)]
49 pub disable_read_receipts: bool,
50 #[serde(default)]
51 pub disable_join_leave_notifications: bool,
52 #[serde(default)]
53 pub disable_invite_notifications: bool,
54 #[serde(default)]
55 pub disable_room_topic_notifications: bool,
56 #[serde(default)]
57 pub determine_code_language: bool,
58 #[serde(default)]
59 pub user_limit: Option<u32>,
60 #[serde(default)]
61 pub admin_mxid: Option<String>,
62 #[serde(default = "default_invalid_token_message")]
63 pub invalid_token_message: String,
64 #[serde(default)]
65 pub user_activity: Option<UserActivityConfig>,
66 #[serde(default)]
67 pub command_prefix: String,
68 #[serde(default)]
69 pub encryption: EncryptionConfig,
70}
71
72#[derive(Debug, Clone, Deserialize, Serialize)]
73pub struct EncryptionConfig {
74 #[serde(default)]
75 pub allow: bool,
76 #[serde(default)]
77 pub default: bool,
78 #[serde(default)]
79 pub require: bool,
80}
81
82impl Default for EncryptionConfig {
83 fn default() -> Self {
84 Self {
85 allow: false,
86 default: false,
87 require: false,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Deserialize, Serialize)]
93pub struct RegistrationConfig {
94 #[serde(alias = "id")]
95 pub bridge_id: String,
96 #[serde(default, alias = "as_token")]
97 pub appservice_token: String,
98 #[serde(default, alias = "hs_token")]
99 pub homeserver_token: String,
100 #[serde(default = "default_sender_localpart")]
101 pub sender_localpart: String,
102 #[serde(default)]
103 pub namespaces: RegistrationNamespaces,
104 #[serde(default)]
105 pub rate_limited: bool,
106 #[serde(
107 default = "default_registration_protocols",
108 alias = "protocol",
109 deserialize_with = "deserialize_registration_protocols"
110 )]
111 pub protocols: Vec<String>,
112}
113
114impl Default for RegistrationConfig {
115 fn default() -> Self {
116 Self {
117 bridge_id: String::new(),
118 appservice_token: String::new(),
119 homeserver_token: String::new(),
120 sender_localpart: default_sender_localpart(),
121 namespaces: RegistrationNamespaces::default(),
122 rate_limited: false,
123 protocols: default_registration_protocols(),
124 }
125 }
126}
127
128#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
129pub struct RegistrationNamespaces {
130 #[serde(default)]
131 pub users: Vec<RegistrationNamespaceEntry>,
132 #[serde(default)]
133 pub aliases: Vec<RegistrationNamespaceEntry>,
134 #[serde(default)]
135 pub rooms: Vec<RegistrationNamespaceEntry>,
136}
137
138impl RegistrationNamespaces {
139 fn is_empty(&self) -> bool {
140 self.users.is_empty() && self.aliases.is_empty() && self.rooms.is_empty()
141 }
142}
143
144#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
145pub struct RegistrationNamespaceEntry {
146 #[serde(default)]
147 pub exclusive: bool,
148 #[serde(default)]
149 pub regex: String,
150}
151
152#[derive(Debug, Clone, Deserialize, Serialize)]
153pub struct UserActivityConfig {
154 #[serde(default)]
155 pub min_user_active_days: u64,
156 #[serde(default)]
157 pub inactive_after_days: u64,
158}
159
160#[derive(Debug, Clone, Deserialize, Serialize)]
161pub struct AuthConfig {
162 pub api_id: i32,
163 pub api_hash: String,
164 #[serde(default)]
165 pub bot_token: Option<String>,
166}
167
168#[derive(Debug, Clone, Deserialize, Serialize)]
169pub struct LoggingConfig {
170 #[serde(alias = "console", default = "default_log_level")]
171 pub level: String,
172 #[serde(default = "default_line_date_format")]
173 pub line_date_format: String,
174 #[serde(default = "default_log_format")]
175 pub format: String,
176 #[serde(default)]
177 pub file: Option<String>,
178 #[serde(default)]
179 pub files: Vec<LoggingFileConfig>,
180}
181
182#[derive(Debug, Clone, Deserialize, Serialize)]
183pub struct LoggingFileConfig {
184 pub file: String,
185 #[serde(default = "default_log_file_level")]
186 pub level: String,
187 #[serde(default = "default_log_max_files")]
188 pub max_files: String,
189 #[serde(default = "default_log_max_size")]
190 pub max_size: String,
191 #[serde(default = "default_log_date_pattern")]
192 pub date_pattern: String,
193 #[serde(default)]
194 pub enabled: Vec<String>,
195 #[serde(default)]
196 pub disabled: Vec<String>,
197}
198
199#[derive(Debug, Clone, Deserialize, Serialize, Default)]
200pub struct DatabaseConfig {
201 #[serde(default)]
202 pub url: Option<String>,
203 #[serde(default)]
204 pub conn_string: Option<String>,
205 #[serde(default)]
206 pub filename: Option<String>,
207 #[serde(default)]
208 pub user_store_path: Option<String>,
209 #[serde(default)]
210 pub room_store_path: Option<String>,
211 #[serde(default)]
212 pub max_connections: Option<u32>,
213 #[serde(default)]
214 pub min_connections: Option<u32>,
215}
216
217impl DatabaseConfig {
218 pub fn db_type(&self) -> DbType {
219 let url = self.connection_string();
220 if url.starts_with("sqlite://") {
221 DbType::Sqlite
222 } else if url.starts_with("mysql://") || url.starts_with("mariadb://") {
223 DbType::Mysql
224 } else {
225 DbType::Postgres
226 }
227 }
228
229 pub fn connection_string(&self) -> String {
230 if let Some(ref url) = self.url {
231 url.clone()
232 } else if let Some(ref conn) = self.conn_string {
233 conn.clone()
234 } else if let Some(ref file) = self.filename {
235 format!("sqlite://{}", file)
236 } else {
237 String::new()
238 }
239 }
240
241 pub fn sqlite_path(&self) -> Option<String> {
242 if let DbType::Sqlite = self.db_type() {
243 let url = self.connection_string();
244 Some(url.strip_prefix("sqlite://").unwrap_or(&url).to_string())
245 } else {
246 None
247 }
248 }
249
250 pub fn max_connections(&self) -> Option<u32> {
251 match self.db_type() {
252 DbType::Postgres | DbType::Mysql => self.max_connections,
253 DbType::Sqlite => Some(1),
254 }
255 }
256
257 pub fn min_connections(&self) -> Option<u32> {
258 match self.db_type() {
259 DbType::Postgres | DbType::Mysql => self.min_connections,
260 DbType::Sqlite => Some(1),
261 }
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub enum DbType {
267 Postgres,
268 Sqlite,
269 Mysql,
270}
271
272#[derive(Debug, Clone, Deserialize, Serialize)]
273pub struct RoomConfig {
274 #[serde(default)]
275 pub default_visibility: String,
276 #[serde(default)]
277 pub room_alias_prefix: String,
278 #[serde(default)]
279 pub enable_room_creation: bool,
280 #[serde(default = "default_kick_for")]
281 pub kick_for: u64,
282}
283
284#[derive(Debug, Clone, Deserialize, Serialize)]
285pub struct PortalConfig {
286 #[serde(default = "default_username_template")]
287 pub username_template: String,
288 #[serde(default = "default_alias_template")]
289 pub alias_template: String,
290 #[serde(default = "default_displayname_template")]
291 pub displayname_template: String,
292 #[serde(default)]
293 pub displayname_preference: Vec<DisplaynamePreference>,
294 #[serde(default = "default_displayname_max_length")]
295 pub displayname_max_length: usize,
296 #[serde(default)]
297 pub public_portals: bool,
298 #[serde(default)]
299 pub sync_channel_members: bool,
300 #[serde(default)]
301 pub max_initial_member_sync: i32,
302 #[serde(default)]
303 pub federate_rooms: bool,
304}
305
306#[derive(Debug, Clone, Deserialize, Serialize, Default)]
307pub struct AnimatedStickerConfig {
308 #[serde(default = "default_sticker_target")]
309 pub target: String,
310 #[serde(default)]
311 pub convert_from_webm: bool,
312 #[serde(default)]
313 pub args: StickerArgs,
314}
315
316#[derive(Debug, Clone, Deserialize, Serialize, Default)]
317pub struct StickerArgs {
318 #[serde(default = "default_sticker_width")]
319 pub width: u32,
320 #[serde(default = "default_sticker_height")]
321 pub height: u32,
322 #[serde(default = "default_sticker_fps")]
323 pub fps: u32,
324}
325
326fn default_sticker_target() -> String {
327 "gif".to_string()
328}
329
330fn default_sticker_width() -> u32 {
331 256
332}
333
334fn default_sticker_height() -> u32 {
335 256
336}
337
338fn default_sticker_fps() -> u32 {
339 25
340}
341
342#[derive(Debug, Clone, Deserialize, Serialize)]
343pub enum DisplaynamePreference {
344 #[serde(rename = "full name")]
345 FullName,
346 #[serde(rename = "full name reversed")]
347 FullNameReversed,
348 #[serde(rename = "first name")]
349 FirstName,
350 #[serde(rename = "last name")]
351 LastName,
352 #[serde(rename = "username")]
353 Username,
354 #[serde(rename = "phone number")]
355 PhoneNumber,
356}
357
358impl Default for DisplaynamePreference {
359 fn default() -> Self {
360 DisplaynamePreference::FullName
361 }
362}
363
364#[derive(Debug, Clone, Deserialize, Serialize)]
365pub struct LimitsConfig {
366 #[serde(default = "default_room_ghost_join_delay")]
367 pub room_ghost_join_delay: u64,
368 #[serde(default = "default_telegram_send_delay")]
369 pub telegram_send_delay: u64,
370 #[serde(default = "default_room_count")]
371 pub room_count: i32,
372 #[serde(default = "default_matrix_event_age_limit_ms")]
373 pub matrix_event_age_limit_ms: u64,
374 #[serde(default = "default_max_telegram_delete")]
375 pub max_telegram_delete: usize,
376 #[serde(default = "default_image_as_file_size")]
377 pub image_as_file_size: u64,
378}
379
380impl Default for LimitsConfig {
381 fn default() -> Self {
382 Self {
383 room_ghost_join_delay: 6000,
384 telegram_send_delay: 1500,
385 room_count: -1,
386 matrix_event_age_limit_ms: 900_000,
387 max_telegram_delete: 10,
388 image_as_file_size: 10,
389 }
390 }
391}
392
393#[derive(Debug, Clone, Deserialize, Serialize)]
394pub struct GhostsConfig {
395 #[serde(default = "default_nick_pattern")]
396 pub nick_pattern: String,
397 #[serde(default = "default_username_pattern")]
398 pub username_pattern: String,
399 #[serde(default)]
400 pub username_template: String,
401 #[serde(default)]
402 pub displayname_template: String,
403 #[serde(default)]
404 pub avatar_url_template: Option<String>,
405}
406
407#[derive(Debug, Clone, Deserialize, Serialize, Default)]
408pub struct MetricsConfig {
409 #[serde(default)]
410 pub enabled: bool,
411 #[serde(default = "default_metrics_port")]
412 pub port: u16,
413 #[serde(default = "default_metrics_bind_address")]
414 pub bind_address: String,
415}
416
417#[derive(Debug, Clone, Deserialize, Serialize)]
418pub struct TelegramConfig {
419 #[serde(default)]
420 pub catch_up: bool,
421 #[serde(default)]
422 pub sequential_updates: bool,
423 #[serde(default)]
424 pub connection: TelegramConnectionConfig,
425}
426
427#[derive(Debug, Clone, Deserialize, Serialize)]
428pub struct TelegramConnectionConfig {
429 #[serde(default = "default_connection_timeout")]
430 pub timeout: u64,
431 #[serde(default = "default_connection_retries")]
432 pub retries: u32,
433 #[serde(default = "default_connection_retry_delay")]
434 pub retry_delay: u64,
435 #[serde(default = "default_flood_sleep_threshold")]
436 pub flood_sleep_threshold: u64,
437}
438
439impl Default for TelegramConnectionConfig {
440 fn default() -> Self {
441 Self {
442 timeout: 120,
443 retries: 5,
444 retry_delay: 1,
445 flood_sleep_threshold: 60,
446 }
447 }
448}
449
450fn default_port() -> u16 {
451 29317
452}
453
454fn default_bind_address() -> String {
455 "0.0.0.0".to_string()
456}
457
458fn default_registration_file() -> String {
459 "telegram-registration.yaml".to_string()
460}
461
462fn default_sender_localpart() -> String {
463 "_telegram_".to_string()
464}
465
466fn default_registration_protocols() -> Vec<String> {
467 vec!["telegram".to_string()]
468}
469
470fn deserialize_registration_protocols<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
471where
472 D: Deserializer<'de>,
473{
474 #[derive(Deserialize)]
475 #[serde(untagged)]
476 enum ProtocolValue {
477 Single(String),
478 Multiple(Vec<String>),
479 }
480
481 match ProtocolValue::deserialize(deserializer)? {
482 ProtocolValue::Single(protocol) => Ok(vec![protocol]),
483 ProtocolValue::Multiple(protocols) => Ok(protocols),
484 }
485}
486
487fn default_presence_interval() -> u64 {
488 500
489}
490
491fn default_invalid_token_message() -> String {
492 "Your Telegram API credentials seem to be invalid, and the bridge cannot function. Please update them in your bridge settings and restart the bridge".to_string()
493}
494
495fn default_log_level() -> String {
496 "info".to_string()
497}
498
499fn default_line_date_format() -> String {
500 "MMM-D HH:mm:ss.SSS".to_string()
501}
502
503fn default_log_format() -> String {
504 "pretty".to_string()
505}
506
507fn default_log_file_level() -> String {
508 "info".to_string()
509}
510
511fn default_log_max_files() -> String {
512 "14d".to_string()
513}
514
515fn default_log_max_size() -> String {
516 "50m".to_string()
517}
518
519fn default_log_date_pattern() -> String {
520 "YYYY-MM-DD".to_string()
521}
522
523fn default_kick_for() -> u64 {
524 30000
525}
526
527fn default_username_template() -> String {
528 "telegram_{userid}".to_string()
529}
530
531fn default_alias_template() -> String {
532 "telegram_{groupname}".to_string()
533}
534
535fn default_displayname_template() -> String {
536 "{displayname} (Telegram)".to_string()
537}
538
539fn default_displayname_max_length() -> usize {
540 100
541}
542
543fn default_room_ghost_join_delay() -> u64 {
544 6000
545}
546
547fn default_telegram_send_delay() -> u64 {
548 1500
549}
550
551fn default_room_count() -> i32 {
552 -1
553}
554
555fn default_matrix_event_age_limit_ms() -> u64 {
556 900_000
557}
558
559fn default_max_telegram_delete() -> usize {
560 10
561}
562
563fn default_image_as_file_size() -> u64 {
564 10
565}
566
567fn default_nick_pattern() -> String {
568 ":nick".to_string()
569}
570
571fn default_username_pattern() -> String {
572 ":username".to_string()
573}
574
575fn default_metrics_port() -> u16 {
576 9001
577}
578
579fn default_metrics_bind_address() -> String {
580 "127.0.0.1".to_string()
581}
582
583fn default_connection_timeout() -> u64 {
584 120
585}
586
587fn default_connection_retries() -> u32 {
588 5
589}
590
591fn default_connection_retry_delay() -> u64 {
592 1
593}
594
595fn default_flood_sleep_threshold() -> u64 {
596 60
597}
598
599impl Config {
600 pub fn load() -> Result<Self, ConfigError> {
601 let config_path = std::env::var("CONFIG_PATH")
602 .ok()
603 .or_else(|| Some("config.yaml".to_string()))
604 .unwrap();
605
606 Self::load_from_file(&config_path)
607 }
608
609 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
610 let content = std::fs::read_to_string(&path)?;
611 let mut config: Config = serde_yaml::from_str(&content)?;
612 config.apply_env_overrides();
613 config.load_registration(path.as_ref())?;
614 config.validate()?;
615 Ok(config)
616 }
617
618 pub fn validate(&self) -> Result<(), ConfigError> {
619 if self.bridge.domain.is_empty() {
620 return Err(ConfigError::InvalidConfig(
621 "bridge.domain cannot be empty".to_string(),
622 ));
623 }
624
625 if self.registration.bridge_id.is_empty() {
626 return Err(ConfigError::InvalidConfig(
627 "registration id cannot be empty (set registration.id or provide telegram-registration.yaml)"
628 .to_string(),
629 ));
630 }
631
632 if self.registration.appservice_token.is_empty() {
633 return Err(ConfigError::InvalidConfig(
634 "registration as_token cannot be empty (set registration.as_token or provide telegram-registration.yaml)"
635 .to_string(),
636 ));
637 }
638
639 if self.registration.homeserver_token.is_empty() {
640 return Err(ConfigError::InvalidConfig(
641 "registration hs_token cannot be empty (set registration.hs_token or provide telegram-registration.yaml)"
642 .to_string(),
643 ));
644 }
645
646 if self.auth.api_id == 0 {
647 return Err(ConfigError::InvalidConfig(
648 "auth.api_id cannot be empty or zero".to_string(),
649 ));
650 }
651
652 if self.auth.api_hash.is_empty() {
653 return Err(ConfigError::InvalidConfig(
654 "auth.api_hash cannot be empty".to_string(),
655 ));
656 }
657
658 if self.database.connection_string().is_empty() {
659 return Err(ConfigError::InvalidConfig(
660 "database connection string cannot be empty".to_string(),
661 ));
662 }
663
664 if self.bridge.port == 0 {
665 return Err(ConfigError::InvalidConfig(
666 "bridge.port must be between 1 and 65535".to_string(),
667 ));
668 }
669
670 Ok(())
671 }
672
673 fn apply_env_overrides(&mut self) {
674 if let Ok(value) = std::env::var("APPSERVICE_TELEGRAM_AUTH_API_ID") {
675 if let Ok(api_id) = value.parse() {
676 self.auth.api_id = api_id;
677 }
678 }
679 if let Ok(value) = std::env::var("APPSERVICE_TELEGRAM_AUTH_API_HASH") {
680 self.auth.api_hash = value;
681 }
682 if let Ok(value) = std::env::var("APPSERVICE_TELEGRAM_AUTH_BOT_TOKEN") {
683 self.auth.bot_token = Some(value);
684 }
685 if let Ok(value) = std::env::var("APPSERVICE_TELEGRAM_REGISTRATION_ID") {
686 self.registration.bridge_id = value;
687 }
688 if let Ok(value) = std::env::var("APPSERVICE_TELEGRAM_REGISTRATION_AS_TOKEN") {
689 self.registration.appservice_token = value;
690 }
691 if let Ok(value) = std::env::var("APPSERVICE_TELEGRAM_REGISTRATION_HS_TOKEN") {
692 self.registration.homeserver_token = value;
693 }
694 if let Ok(value) = std::env::var("APPSERVICE_TELEGRAM_REGISTRATION_SENDER_LOCALPART") {
695 self.registration.sender_localpart = value;
696 }
697 }
698
699 fn load_registration(&mut self, config_path: &Path) -> Result<(), ConfigError> {
700 let registration_path = std::env::var("REGISTRATION_PATH")
701 .ok()
702 .filter(|value| !value.trim().is_empty())
703 .unwrap_or_else(default_registration_file);
704 let registration_path = resolve_registration_path(config_path, ®istration_path);
705
706 if !registration_path.exists() {
707 return Ok(());
708 }
709
710 let content = std::fs::read_to_string(registration_path)?;
711 let registration: RegistrationConfig = serde_yaml::from_str(&content)?;
712
713 if self.registration.bridge_id.is_empty() {
714 self.registration.bridge_id = registration.bridge_id;
715 }
716 if self.registration.appservice_token.is_empty() {
717 self.registration.appservice_token = registration.appservice_token;
718 }
719 if self.registration.homeserver_token.is_empty() {
720 self.registration.homeserver_token = registration.homeserver_token;
721 }
722 if self.registration.sender_localpart == default_sender_localpart()
723 && registration.sender_localpart != default_sender_localpart()
724 {
725 self.registration.sender_localpart = registration.sender_localpart;
726 }
727 if self.registration.namespaces.is_empty() && !registration.namespaces.is_empty() {
728 self.registration.namespaces = registration.namespaces;
729 }
730 if !self.registration.rate_limited && registration.rate_limited {
731 self.registration.rate_limited = true;
732 }
733 if self.registration.protocols == default_registration_protocols()
734 && registration.protocols != default_registration_protocols()
735 {
736 self.registration.protocols = registration.protocols;
737 }
738
739 Ok(())
740 }
741}
742
743fn resolve_registration_path(config_path: &Path, registration_path: &str) -> PathBuf {
744 let registration_path = Path::new(registration_path);
745 if registration_path.is_absolute() {
746 registration_path.to_path_buf()
747 } else if let Some(parent) = config_path.parent() {
748 parent.join(registration_path)
749 } else {
750 registration_path.to_path_buf()
751 }
752}
753
754#[cfg(test)]
755mod tests {
756 use super::*;
757
758 #[test]
759 fn database_config_detects_sqlite() {
760 let config = DatabaseConfig {
761 url: Some("sqlite://./test.db".to_string()),
762 ..Default::default()
763 };
764 assert_eq!(config.db_type(), DbType::Sqlite);
765 }
766
767 #[test]
768 fn database_config_detects_postgres() {
769 let config = DatabaseConfig {
770 url: Some("postgresql://user:pass@localhost/db".to_string()),
771 ..Default::default()
772 };
773 assert_eq!(config.db_type(), DbType::Postgres);
774 }
775}