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