Add a link to a similar project.
[gandi_dns_update.git] / src / main.rs
1 /*
2 * API Reference: https://api.gandi.net/docs/livedns/
3 * Some similar implementations:
4 * - https://github.com/rmarchant/gandi-ddns/blob/master/gandi_ddns.py
5 * - https://github.com/brianhp2/gandi-automatic-dns
6 */
7
8 #![cfg_attr(debug_assertions, allow(unused_variables, unused_imports, dead_code))]
9
10 use std::{ fmt::format, fs::File, net::{ IpAddr, Ipv4Addr }, thread, time };
11 use ron::{ de::from_reader, ser::to_writer };
12 use serde::{ Deserialize, Serialize };
13 use serde_json::Value;
14
15 type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
16
17 #[derive(Debug)]
18 struct Error {
19 message: String
20 }
21
22 impl std::fmt::Display for Error {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.write_str(&self.message)
25 }
26 }
27
28 impl std::error::Error for Error { }
29
30 #[derive(Debug, Deserialize, Serialize)]
31 struct Config {
32 delay_between_check: time::Duration,
33 api_key: String,
34 domains: Vec<String>,
35 }
36
37 impl Config {
38 fn default() -> Self {
39 Config { delay_between_check: time::Duration::from_secs(60), api_key: String::from(""), domains: Vec::new() }
40 }
41
42 fn read(file_path: &str) -> Result<Config> {
43 match File::open(file_path) {
44 Ok(file) => from_reader(file).map_err(|e| e.into()),
45 // The file doesn't exit -> create it with default values.
46 Err(_) => {
47 let file = File::create(file_path)?;
48 let default_config = Config::default();
49 to_writer(file, &default_config)?;
50 Ok(default_config)
51 }
52 }
53 }
54 }
55
56 const FILE_CONF: &str = "config.ron";
57
58 fn main() -> Result<()> {
59 println!("GANDI DynDNS");
60
61 let config = Config::read(FILE_CONF)?;
62
63 println!("Configuration: {:?}", config);
64
65 loop {
66 let time_beginning_loop = time::Instant::now();
67
68 check_and_update_dns(&config.api_key);
69
70 let elapsed = time::Instant::now() - time_beginning_loop;
71
72 if elapsed < config.delay_between_check {
73 let to_wait = config.delay_between_check - elapsed;
74 thread::sleep(to_wait);
75 }
76
77 }
78 }
79
80 fn check_and_update_dns(api_key: &str) {
81 /*
82 */
83 //dbg!(get_real_ip());
84
85 get_current_record_ip(api_key);
86 }
87
88 fn get_real_ip() -> Result<Ipv4Addr> {
89
90 let url = "https://api.ipify.org";
91 let client = reqwest::blocking::Client::new();
92
93 match client.get(url).send() {
94 Ok(resp) =>
95 if resp.status().is_success() {
96 let content = resp.text().unwrap();
97 match content.parse::<IpAddr>() {
98 Ok(IpAddr::V4(ip_v4)) => Ok(ip_v4),
99 /*Err(_)*/ _ => Err(Box::new(Error { message: String::from("Can't parse IPv4 from ipify") }))
100 }
101 //println!("Content:\n{:?}", content);
102 } else {
103 //println!("Request unsuccessful:\n{:#?}", resp);
104 Err(Box::new(Error { message: format!("Request unsuccessful: {:#?}", resp) }))
105 },
106
107 Err(error) => {
108 //println!("Error during request: {:?}", error);
109 Err(Box::new(Error { message: format!("Error during request: {:?}", error) }))
110 }
111 }
112 }
113
114 fn request_livedns_gandi(api_key: &str, url_fragment: &str) -> Result<Value> {
115 let url = format!("https://api.gandi.net/v5/livedns/{}", url_fragment);
116 let client = reqwest::blocking::Client::new();
117
118 match client.get(&url).header("Authorization", format!("Apikey {}", api_key)).send() {
119 Ok(resp) =>
120 if resp.status().is_success() {
121 let content = resp.text().unwrap();
122 Ok(serde_json::from_str(&content).unwrap())
123 } else {
124 Err(Box::new(Error { message: format!("Request unsuccessful: {:#?}", resp) }))
125 },
126 Err(error) =>
127 Err(Box::new(Error { message: format!("Error during request: {:?}", error) }))
128 }
129 }
130
131 fn get_current_record_ip(api_key: &str) -> Result<Ipv4Addr> {
132
133 request_livedns_gandi(api_key, "domains/euphorik.ch/records/home/A")?; // TODO...
134 // .map()
135 //.map(|json_value| json_value["rrset_values"][0].as_str().unwrap())
136
137 Result::Err(Box::new(Error { message: String::new() }))
138
139
140 //let url = "https://api.gandi.net/v5/livedns/domains/euphorik.ch/records";
141 //let url = "https://api.gandi.net/v5/livedns/domains"; // ?sharing_id="
142 //let url = "https://api.gandi.net/v5/organization/organizations";
143 //let url = "https://api.gandi.net/v5/livedns/domains/euphorik.ch/records/home/A";
144
145 /*
146 let client = reqwest::blocking::Client::new();
147
148 match client.get(url).header("Authorization", format!("Apikey {}", api_key)).send() {
149 Ok(resp) =>
150 if resp.status().is_success() {
151 let content = resp.text().unwrap();
152
153 let json: Value = serde_json::from_str(&content).unwrap();
154 let prout = json["rrset_values"][0].as_str().unwrap();
155 println!("IP: {}", prout);
156
157 println!("Content:\n{}", serde_json::to_string_pretty(&json).unwrap());
158 } else {
159 println!("Request unsuccessful:\n{:#?}", resp);
160 },
161 Err(error) =>
162 println!("Error during request: {:?}", error)
163 }
164 */
165 }
166
167 fn update_record_ip() {
168 }