mostro_client/parser/
orders.rs1use std::collections::HashMap;
2
3use crate::util::Event;
4use anyhow::Result;
5use chrono::DateTime;
6use comfy_table::presets::UTF8_FULL;
7use comfy_table::*;
8use log::{error, info};
9use mostro_core::prelude::*;
10use nostr_sdk::prelude::*;
11use uuid::Uuid;
12
13use crate::nip33::order_from_tags;
14
15pub fn parse_orders_events(
16 events: Events,
17 currency: Option<String>,
18 status: Option<Status>,
19 kind: Option<mostro_core::order::Kind>,
20) -> Vec<SmallOrder> {
21 let mut latest_by_id: HashMap<Uuid, SmallOrder> = HashMap::new();
23
24 for event in events.iter() {
25 let mut order = match order_from_tags(event.tags.clone()) {
27 Ok(o) => o,
28 Err(e) => {
29 error!("{e:?}");
30 continue;
31 }
32 };
33 let order_id = match order.id {
35 Some(id) => id,
36 None => {
37 info!("Order ID is none");
38 continue;
39 }
40 };
41 if order.kind.is_none() {
43 info!("Order kind is none");
44 continue;
45 }
46 order.created_at = Some(event.created_at.as_u64() as i64);
48 latest_by_id
50 .entry(order_id)
51 .and_modify(|existing| {
52 let new_ts = order.created_at.unwrap_or(0);
53 let old_ts = existing.created_at.unwrap_or(0);
54 if new_ts > old_ts {
55 *existing = order.clone();
56 }
57 })
58 .or_insert(order);
59 }
60
61 let mut requested: Vec<SmallOrder> = latest_by_id
62 .into_values()
63 .filter(|o| status.map(|s| o.status == Some(s)).unwrap_or(true))
64 .filter(|o| currency.as_ref().map(|c| o.fiat_code == *c).unwrap_or(true))
65 .filter(|o| {
66 kind.as_ref()
67 .map(|k| o.kind.as_ref() == Some(k))
68 .unwrap_or(true)
69 })
70 .collect();
71
72 requested.sort_by(|a, b| b.created_at.cmp(&a.created_at));
73 requested
74}
75
76pub fn print_order_preview(ord: Payload) -> Result<String, String> {
77 let single_order = match ord {
78 Payload::Order(o) => o,
79 _ => return Err("Error".to_string()),
80 };
81
82 let mut table = Table::new();
83
84 table
85 .load_preset(UTF8_FULL)
86 .set_content_arrangement(ContentArrangement::Dynamic)
87 .set_width(160)
88 .set_header(vec![
89 Cell::new("Buy/Sell")
90 .add_attribute(Attribute::Bold)
91 .set_alignment(CellAlignment::Center),
92 Cell::new("Sats Amount")
93 .add_attribute(Attribute::Bold)
94 .set_alignment(CellAlignment::Center),
95 Cell::new("Fiat Code")
96 .add_attribute(Attribute::Bold)
97 .set_alignment(CellAlignment::Center),
98 Cell::new("Fiat Amount")
99 .add_attribute(Attribute::Bold)
100 .set_alignment(CellAlignment::Center),
101 Cell::new("Payment method")
102 .add_attribute(Attribute::Bold)
103 .set_alignment(CellAlignment::Center),
104 Cell::new("Premium %")
105 .add_attribute(Attribute::Bold)
106 .set_alignment(CellAlignment::Center),
107 ]);
108
109 let r = Row::from(vec![
111 if let Some(k) = single_order.kind {
112 match k {
113 mostro_core::order::Kind::Buy => Cell::new(k.to_string())
114 .fg(Color::Green)
115 .set_alignment(CellAlignment::Center),
116 mostro_core::order::Kind::Sell => Cell::new(k.to_string())
117 .fg(Color::Red)
118 .set_alignment(CellAlignment::Center),
119 }
120 } else {
121 Cell::new("BUY/SELL").set_alignment(CellAlignment::Center)
122 },
123 if single_order.amount == 0 {
124 Cell::new("market price").set_alignment(CellAlignment::Center)
125 } else {
126 Cell::new(single_order.amount).set_alignment(CellAlignment::Center)
127 },
128 Cell::new(single_order.fiat_code.to_string()).set_alignment(CellAlignment::Center),
129 if single_order.min_amount.is_none() && single_order.max_amount.is_none() {
131 Cell::new(single_order.fiat_amount.to_string()).set_alignment(CellAlignment::Center)
132 } else {
133 let range_str = match (single_order.min_amount, single_order.max_amount) {
134 (Some(min), Some(max)) => format!("{}-{}", min, max),
135 (Some(min), None) => format!("{}-?", min),
136 (None, Some(max)) => format!("?-{}", max),
137 (None, None) => "?".to_string(),
138 };
139 Cell::new(range_str).set_alignment(CellAlignment::Center)
140 },
141 Cell::new(single_order.payment_method.to_string()).set_alignment(CellAlignment::Center),
142 Cell::new(single_order.premium.to_string()).set_alignment(CellAlignment::Center),
143 ]);
144
145 table.add_row(r);
146
147 Ok(table.to_string())
148}
149
150pub fn print_orders_table(orders_table: Vec<Event>) -> Result<String> {
151 let mut table = Table::new();
152 let orders_table: Vec<SmallOrder> = orders_table
154 .into_iter()
155 .filter_map(|event| {
156 if let Event::SmallOrder(order) = event {
157 Some(order)
158 } else {
159 None
160 }
161 })
162 .collect();
163
164 let mut rows: Vec<Row> = Vec::new();
166
167 if orders_table.is_empty() {
168 table
169 .load_preset(UTF8_FULL)
170 .set_content_arrangement(ContentArrangement::Dynamic)
171 .set_width(160)
172 .set_header(vec![Cell::new("Sorry...")
173 .add_attribute(Attribute::Bold)
174 .set_alignment(CellAlignment::Center)]);
175
176 let mut r = Row::new();
178
179 r.add_cell(
180 Cell::new("No offers found with requested parameters...")
181 .fg(Color::Red)
182 .set_alignment(CellAlignment::Center),
183 );
184
185 rows.push(r);
187 } else {
188 table
189 .load_preset(UTF8_FULL)
190 .set_content_arrangement(ContentArrangement::Dynamic)
191 .set_width(160)
192 .set_header(vec![
193 Cell::new("Buy/Sell")
194 .add_attribute(Attribute::Bold)
195 .set_alignment(CellAlignment::Center),
196 Cell::new("Order Id")
197 .add_attribute(Attribute::Bold)
198 .set_alignment(CellAlignment::Center),
199 Cell::new("Status")
200 .add_attribute(Attribute::Bold)
201 .set_alignment(CellAlignment::Center),
202 Cell::new("Amount")
203 .add_attribute(Attribute::Bold)
204 .set_alignment(CellAlignment::Center),
205 Cell::new("Fiat Code")
206 .add_attribute(Attribute::Bold)
207 .set_alignment(CellAlignment::Center),
208 Cell::new("Fiat Amount")
209 .add_attribute(Attribute::Bold)
210 .set_alignment(CellAlignment::Center),
211 Cell::new("Payment method")
212 .add_attribute(Attribute::Bold)
213 .set_alignment(CellAlignment::Center),
214 Cell::new("Created")
215 .add_attribute(Attribute::Bold)
216 .set_alignment(CellAlignment::Center),
217 ]);
218
219 for single_order in orders_table.into_iter() {
221 let date = DateTime::from_timestamp(single_order.created_at.unwrap_or(0), 0);
222
223 let r = Row::from(vec![
224 if let Some(k) = single_order.kind {
225 match k {
226 mostro_core::order::Kind::Buy => Cell::new(k.to_string())
227 .fg(Color::Green)
228 .set_alignment(CellAlignment::Center),
229 mostro_core::order::Kind::Sell => Cell::new(k.to_string())
230 .fg(Color::Red)
231 .set_alignment(CellAlignment::Center),
232 }
233 } else {
234 Cell::new("BUY/SELL").set_alignment(CellAlignment::Center)
235 },
236 Cell::new(
237 single_order
238 .id
239 .map(|id| id.to_string())
240 .unwrap_or_else(|| "N/A".to_string()),
241 )
242 .set_alignment(CellAlignment::Center),
243 Cell::new(
244 single_order
245 .status
246 .unwrap_or(mostro_core::order::Status::Active)
247 .to_string(),
248 )
249 .set_alignment(CellAlignment::Center),
250 if single_order.amount == 0 {
251 Cell::new("market price").set_alignment(CellAlignment::Center)
252 } else {
253 Cell::new(single_order.amount.to_string()).set_alignment(CellAlignment::Center)
254 },
255 Cell::new(single_order.fiat_code.to_string()).set_alignment(CellAlignment::Center),
256 if single_order.min_amount.is_none() && single_order.max_amount.is_none() {
258 Cell::new(single_order.fiat_amount.to_string())
259 .set_alignment(CellAlignment::Center)
260 } else {
261 let range_str = match (single_order.min_amount, single_order.max_amount) {
262 (Some(min), Some(max)) => format!("{}-{}", min, max),
263 (Some(min), None) => format!("{}-?", min),
264 (None, Some(max)) => format!("?-{}", max),
265 (None, None) => "?".to_string(),
266 };
267 Cell::new(range_str).set_alignment(CellAlignment::Center)
268 },
269 Cell::new(single_order.payment_method.to_string())
270 .set_alignment(CellAlignment::Center),
271 Cell::new(
272 date.map(|d| d.to_string())
273 .unwrap_or_else(|| "Invalid date".to_string()),
274 ),
275 ]);
276 rows.push(r);
277 }
278 }
279
280 table.add_rows(rows);
281
282 Ok(table.to_string())
283}
284
285#[cfg(test)]
286mod tests {}