podping_api/
podping.rs

1pub mod api;
2pub mod request;
3
4use serde::{Deserialize, Serialize};
5
6const QUERY: &str = "podping";
7pub(crate) const HIVE_API: &str = "https://api.hive.blog";
8
9#[derive(Debug)]
10pub(crate) struct HiveClient {
11    pub client: reqwest::Client,
12    block: Option<u64>,
13}
14
15impl HiveClient {
16    pub(crate) fn new(block: Option<u64>) -> Self {
17        let client = reqwest::Client::new();
18        Self { client, block }
19    }
20
21    pub(crate) async fn init(&mut self) {
22        match &self.block {
23            Some(_) => {}
24            None => {
25                let block = self.get_head_block_number();
26                self.block = Some(block.await);
27            }
28        }
29    }
30
31    pub(crate) async fn next_block(&mut self) {
32        if let Some(block) = self.block_mut() {
33            *block += 1
34        }
35    }
36
37    /// Get the current head block number from hive
38    pub(crate) async fn get_head_block_number(&self) -> u64 {
39        self.get_dynamic_global_properties()
40            .await
41            .get_head_block_number()
42            .unwrap()
43    }
44
45    /// Get the complete dynamic global properties from hive
46    pub(crate) async fn get_dynamic_global_properties(&self) -> HiveResponse {
47        self.client
48            .post(HIVE_API)
49            .json(&HiveMessage::get_dynamic_global_properties())
50            .send()
51            .await
52            .unwrap()
53            .json::<HiveResponse>()
54            .await
55            .unwrap()
56    }
57
58    pub(crate) fn block(&self) -> Option<u64> {
59        self.block
60    }
61
62    pub(crate) fn block_mut(&mut self) -> &mut Option<u64> {
63        &mut self.block
64    }
65
66    pub(crate) fn set_block(&mut self, block: Option<u64>) {
67        self.block = block;
68    }
69
70    /// Extract Url's from the Operations
71    pub(crate) fn get_payloads(&self, operations: Vec<Operations>) -> Vec<String> {
72        // Extract the updated uri's
73        let mut payloads = vec![];
74        for tr in operations {
75            let json_payload = serde_json::from_str::<OpPayload>(&tr.json.unwrap()).unwrap();
76            payloads = json_payload.iris;
77            // println!("block: {:?}, reason: {:?}", self.block, json_payload.reason);
78            if json_payload.reason == "live" {
79                let item = format!("live: {:?}", self.block);
80                std::process::Command::new("notify-send")
81                    .arg(item)
82                    .output()
83                    .expect("failed to execute process");
84            }
85        }
86
87        if !payloads.is_empty() {
88            println!("block: {:?}, payloads: {:?}", self.block, payloads);
89        }
90        payloads
91    }
92    /// Get operations for the current block
93    /// We filter for notifications with the name `podping`
94    pub(crate) async fn get_operations(&self) -> Vec<Operations> {
95        let transactions = self.get_transactions().await;
96
97        let mut operations = vec![];
98        for transaction in transactions {
99            for operation in transaction.operations {
100                for op in operation {
101                    match op {
102                        HiveOperation::Operations(operation) => {
103                            if let Some(name) = &operation.id {
104                                // We only care about podping messages.
105                                if name == QUERY {
106                                    operations.push(operation);
107                                }
108                            }
109                        }
110                        // We don't care about any of these messages.
111                        HiveOperation::String(_)
112                        | HiveOperation::Vote(_)
113                        | HiveOperation::Transfer(_)
114                        | HiveOperation::Transactions(_)
115                        | HiveOperation::Comment(_) => {}
116                    }
117                }
118            }
119        }
120        operations
121    }
122    pub(crate) async fn get_block(&self) -> HiveResponse {
123        let block_response = HiveMessage::get_block(self.block.unwrap());
124        let resp = self
125            .client
126            .post(HIVE_API)
127            .json(&block_response)
128            .send()
129            .await
130            .unwrap()
131            .text()
132            .await
133            .unwrap();
134        // reqwest can't properly serialize the type into json,
135        // but serde_json can.
136        serde_json::from_str::<HiveResponse>(&resp).unwrap()
137    }
138
139    pub(crate) async fn get_transactions(&self) -> Vec<Transactions> {
140        let hive_response = &self.get_block().await;
141        if let Some(HiveResponseResult::Block(block)) = &hive_response.result {
142            block.transactions.clone()
143        } else {
144            vec![]
145        }
146    }
147}
148#[derive(Debug)]
149pub(crate) struct HiveClientBlocking {
150    pub client: reqwest::blocking::Client,
151    block: Option<u64>,
152}
153
154impl HiveClientBlocking {
155    pub(crate) fn new(block: Option<u64>) -> Self {
156        let client = reqwest::blocking::Client::new();
157        Self { client, block }
158    }
159    pub(crate) fn init(&mut self) {
160        match &self.block {
161            Some(_) => {}
162            None => {
163                let block = self.get_head_block_number();
164                self.block = Some(block);
165            }
166        }
167    }
168
169    pub(crate) fn get_head_block_number(&self) -> u64 {
170        let resp = self
171            .client
172            .post(HIVE_API)
173            .json(&HiveMessage::get_dynamic_global_properties())
174            .send()
175            .unwrap()
176            .json::<HiveResponse>()
177            .unwrap();
178        resp.get_head_block_number().unwrap()
179    }
180
181    /// Get operations for the current block
182    /// We filter for notifications with the name `podping`
183    pub(crate) fn get_operations(&self) -> Vec<Operations> {
184        let transactions = self.get_transactions();
185
186        let mut operations = vec![];
187        for transaction in transactions {
188            for operation in transaction.operations {
189                for op in operation {
190                    match op {
191                        HiveOperation::Operations(operation) => {
192                            if let Some(name) = &operation.id {
193                                // We only care about podping messages.
194                                if name == QUERY {
195                                    operations.push(operation);
196                                }
197                            }
198                        }
199                        // We don't care about any of these messages.
200                        HiveOperation::String(_)
201                        | HiveOperation::Vote(_)
202                        | HiveOperation::Transfer(_)
203                        | HiveOperation::Transactions(_)
204                        | HiveOperation::Comment(_) => {}
205                    }
206                }
207            }
208        }
209        operations
210    }
211
212    pub(crate) fn get_transactions(&self) -> Vec<Transactions> {
213        let block_response = HiveMessage::get_block(self.block.unwrap());
214        let resp = self
215            .client
216            .post(HIVE_API)
217            .json(&block_response)
218            .send()
219            .unwrap()
220            .text()
221            .unwrap();
222        // reqwest can't properly serialize the type into json,
223        // but serde_json can.
224        let hive_response = serde_json::from_str::<HiveResponse>(&resp).unwrap();
225
226        if let Some(HiveResponseResult::Block(block)) = hive_response.result {
227            block.transactions
228        } else {
229            vec![]
230        }
231    }
232
233    /// Extract Url's from the Operations
234    pub(crate) fn get_payloads(&self, operations: Vec<Operations>) -> Vec<String> {
235        // Extract the updated uri's
236        let mut payloads = vec![];
237        for tr in operations {
238            let mut json_payload = serde_json::from_str::<OpPayload>(&tr.json.unwrap()).unwrap();
239            payloads.append(&mut json_payload.iris);
240        }
241        payloads
242    }
243
244    pub(crate) fn block(&self) -> Option<u64> {
245        self.block
246    }
247
248    pub(crate) fn block_mut(&mut self) -> &mut Option<u64> {
249        &mut self.block
250    }
251
252    pub(crate) fn set_block(&mut self, block: Option<u64>) {
253        self.block = block;
254    }
255
256    pub(crate) fn next_block(&mut self) {
257        if let Some(block) = self.block_mut() {
258            *block += 1
259        }
260    }
261}
262
263#[derive(Debug, Serialize, Deserialize)]
264pub(crate) struct HiveRequest {
265    jsonrpc: String,
266    method: String,
267    id: u8,
268}
269
270#[derive(Debug, Serialize, Deserialize)]
271pub(crate) struct HiveRequestParams {
272    jsonrpc: String,
273    method: String,
274    params: Vec<ParamMember>,
275    id: u8,
276}
277
278#[derive(Debug, Serialize, Deserialize)]
279#[serde(untagged)]
280pub(crate) enum HiveMessage {
281    Request(HiveRequest),
282    RequestParams(HiveRequestParams),
283    Response(HiveResponse),
284}
285
286#[derive(Debug, Serialize, Deserialize)]
287#[serde(untagged)]
288pub(crate) enum ParamMember {
289    String(String),
290    Int(u16),
291    Int128(u128),
292    Int64(u64),
293    None,
294}
295
296impl HiveMessage {
297    pub(crate) fn get_followers() -> Self {
298        Self::RequestParams(HiveRequestParams {
299            jsonrpc: "2.0".into(),
300            method: "condenser_api.get_followers".into(),
301            params: vec![
302                ParamMember::String(QUERY.into()),
303                ParamMember::None,
304                ParamMember::String("blog".into()),
305                ParamMember::Int(100),
306            ],
307            id: 1,
308        })
309    }
310    pub(crate) fn get_methods() -> Self {
311        Self::Request(HiveRequest {
312            jsonrpc: "2.0".into(),
313            method: "jsonrpc.get_methods".into(),
314            id: 1,
315        })
316    }
317    pub(crate) fn get_dynamic_global_properties() -> Self {
318        Self::Request(HiveRequest {
319            jsonrpc: "2.0".into(),
320            method: "database_api.get_dynamic_global_properties".into(),
321            id: 1,
322        })
323    }
324    pub(crate) fn get_block(block: u64) -> Self {
325        Self::RequestParams(HiveRequestParams {
326            jsonrpc: "2.0".into(),
327            method: "condenser_api.get_block".into(),
328            params: vec![ParamMember::Int64(block)],
329            id: 1,
330        })
331    }
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct HiveResponse {
336    pub jsonrpc: String,
337    pub id: u16,
338    // Returns null (None), if the block is too advanced
339    pub result: Option<HiveResponseResult>,
340}
341
342impl HiveResponse {
343    pub(crate) fn get_head_block_number(&self) -> Option<u64> {
344        if let Some(response) = &self.result {
345            response.get_head_block_number()
346        } else {
347            None
348        }
349    }
350}
351
352impl HiveResponseResult {
353    pub(crate) fn get_head_block_number(&self) -> Option<u64> {
354        match self {
355            HiveResponseResult::DynamicGlobalProperties(properties) => {
356                Some(properties.head_block_number)
357            }
358            _ => None,
359        }
360    }
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
364#[serde(untagged)]
365pub enum HiveResponseResult {
366    DynamicGlobalProperties(HiveDynamicGlobalProperties),
367    Block(Block),
368}
369
370#[derive(Clone, Debug, Serialize, Deserialize)]
371pub struct HiveDynamicGlobalProperties {
372    id: u16,
373    head_block_number: u64,
374    // head_block_id: String,
375    // time: String,
376    // current_witness: string,
377    // total_pow: u128,
378    // num_pow_witnesses: u128,
379}
380
381#[derive(Clone, Debug, Serialize, Deserialize)]
382pub struct Block {
383    // id: u16,
384    // previous: String,
385    // timestamp: String,
386    pub transactions: Vec<Transactions>,
387    // pub transactions: HashMap<String, String>,
388    // operations:
389    // head_block_id: String,
390    // time: String,
391    // current_witness: string,
392    // total_pow: u128,
393    // num_pow_witnesses: u128,
394}
395
396#[derive(Clone, Debug, Serialize, Deserialize)]
397/// All transactions
398pub struct Transactions {
399    // ref_block_num: u64,
400    // ref_block_prefix: u64,
401    // pub(crate) expiration: String,
402    pub(crate) operations: Vec<Vec<HiveOperation>>,
403}
404
405#[derive(Clone, Debug, Serialize, Deserialize)]
406#[serde(untagged)]
407pub enum HiveOperation {
408    Operations(Operations),
409    String(String),
410    Vote(Vote),
411    Transfer(Transfer),
412    Transactions(Transactions),
413    Comment(Comment),
414}
415
416#[derive(Clone, Debug, Serialize, Deserialize)]
417pub struct Operations {
418    required_auths: Vec<String>,
419    required_posting_auths: Vec<String>,
420    pub id: Option<String>,
421    pub json: Option<String>,
422}
423
424#[derive(Clone, Debug, Serialize, Deserialize)]
425pub struct OpPayload {
426    version: String,
427    num_urls: Option<u8>,
428    pub medium: String,
429    pub reason: String,
430    // used to be named urls
431    pub iris: Vec<String>,
432}
433
434#[derive(Clone, Debug, Serialize, Deserialize)]
435pub struct Vote {
436    voter: Option<String>,
437    author: Option<String>,
438    permlink: Option<String>,
439    // weight: String,
440}
441
442#[derive(Clone, Debug, Serialize, Deserialize)]
443pub struct Transfer {
444    from: Option<String>,
445    to: Option<String>,
446    amount: Option<String>,
447    memo: Option<String>,
448    // weight: String,
449}
450
451#[derive(Clone, Debug, Serialize, Deserialize)]
452pub struct Comment {
453    parent_author: Option<String>,
454    parent_permlink: Option<String>,
455    author: Option<String>,
456    title: Option<String>,
457    // weight: String,
458}