Skip to main content

matrix_bridge_telegram/config/
parser.rs

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, &registration_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}