1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3use tracing::info;
4
5#[derive(Debug, Deserialize, Serialize, Default, Clone)]
7pub struct YamlConfig {
8 #[serde(skip_serializing_if = "Option::is_none")]
9 pub password: Option<String>,
10 #[serde(skip_serializing_if = "Option::is_none")]
11 pub socket_port: Option<u16>,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub server_address: Option<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub enable_private_api: Option<bool>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub enable_facetime_private_api: Option<bool>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub enable_findmy_private_api: Option<bool>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub markdown_to_formatting: Option<bool>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub webhooks: Option<Vec<WebhookConfigEntry>>,
24}
25
26#[derive(Debug, Deserialize, Serialize, Clone)]
29#[serde(untagged)]
30pub enum WebhookConfigEntry {
31 Simple(String),
32 Detailed {
33 url: String,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 events: Option<Vec<String>>,
36 },
37}
38
39#[derive(Debug, Clone)]
41pub struct AppConfig {
42 pub password: String,
43 pub socket_port: u16,
44 pub server_address: String,
45 pub enable_private_api: bool,
46 pub enable_facetime_private_api: bool,
47 pub enable_findmy_private_api: bool,
48 pub markdown_to_formatting: bool,
49 pub webhooks: Vec<WebhookConfigEntry>,
50}
51
52impl Default for AppConfig {
53 fn default() -> Self {
54 Self {
55 password: String::new(),
56 socket_port: 1234,
57 server_address: String::new(),
58 enable_private_api: false,
59 enable_facetime_private_api: false,
60 enable_findmy_private_api: false,
61 markdown_to_formatting: false,
62 webhooks: vec![],
63 }
64 }
65}
66
67impl YamlConfig {
68 pub fn into_app_config(self) -> AppConfig {
72 let defaults = AppConfig::default();
73 let Self {
74 password,
75 socket_port,
76 server_address,
77 enable_private_api,
78 enable_facetime_private_api,
79 enable_findmy_private_api,
80 markdown_to_formatting,
81 webhooks,
82 } = self;
83 AppConfig {
84 password: password.unwrap_or(defaults.password),
85 socket_port: socket_port.unwrap_or(defaults.socket_port),
86 server_address: server_address.unwrap_or(defaults.server_address),
87 enable_private_api: enable_private_api.unwrap_or(defaults.enable_private_api),
88 enable_facetime_private_api: enable_facetime_private_api
89 .unwrap_or(defaults.enable_facetime_private_api),
90 enable_findmy_private_api: enable_findmy_private_api
91 .unwrap_or(defaults.enable_findmy_private_api),
92 markdown_to_formatting: markdown_to_formatting
93 .unwrap_or(defaults.markdown_to_formatting),
94 webhooks: webhooks.unwrap_or(defaults.webhooks),
95 }
96 }
97}
98
99pub struct AppPaths;
101
102impl AppPaths {
103 pub fn user_data() -> PathBuf {
105 home::home_dir()
106 .unwrap_or_else(|| PathBuf::from("/tmp"))
107 .join("Library")
108 .join("Application Support")
109 .join("imessage-rs")
110 }
111
112 pub fn pid_file() -> PathBuf {
114 Self::user_data().join(".imessage-rs.pid")
115 }
116
117 pub fn imessage_db() -> PathBuf {
119 home::home_dir()
120 .unwrap_or_else(|| PathBuf::from("/tmp"))
121 .join("Library")
122 .join("Messages")
123 .join("chat.db")
124 }
125
126 pub fn imessage_db_wal() -> PathBuf {
128 Self::imessage_db().with_extension("db-wal")
129 }
130
131 pub fn attachments_dir() -> PathBuf {
133 Self::user_data().join("Attachments")
134 }
135
136 pub fn messages_attachments_dir() -> PathBuf {
140 home::home_dir()
141 .unwrap_or_else(|| PathBuf::from("/tmp"))
142 .join("Library")
143 .join("Messages")
144 .join("Attachments")
145 .join("imessage-rs")
146 }
147
148 pub fn attachment_cache_dir() -> PathBuf {
150 Self::attachments_dir().join("Cached")
151 }
152
153 pub fn convert_dir() -> PathBuf {
155 Self::user_data().join("Convert")
156 }
157
158 pub fn yaml_config() -> PathBuf {
160 Self::user_data().join("config.yml")
161 }
162
163 pub fn version() -> &'static str {
165 env!("CARGO_PKG_VERSION")
166 }
167}
168
169pub fn setup_directories() -> anyhow::Result<()> {
171 let dirs = [
172 AppPaths::user_data(),
173 AppPaths::attachments_dir(),
174 AppPaths::attachment_cache_dir(),
175 AppPaths::convert_dir(),
176 ];
177
178 for dir in &dirs {
179 if !dir.exists() {
180 std::fs::create_dir_all(dir)?;
181 info!("Created directory: {}", dir.display());
182 }
183 }
184
185 Ok(())
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn default_config_values() {
194 let cfg = AppConfig::default();
195 assert_eq!(cfg.socket_port, 1234);
196 assert!(!cfg.enable_private_api);
197 }
198
199 #[test]
200 fn user_data_path_contains_imessage_rs() {
201 let path = AppPaths::user_data();
202 assert!(path.to_str().unwrap().contains("imessage-rs"));
203 }
204}