passgen: more pass generation
This commit is contained in:
parent
789e31e2d9
commit
c801d3db51
5 changed files with 178 additions and 4 deletions
|
@ -120,7 +120,7 @@ pub async fn insert_or_update_now_arc<T: Clone>(
|
||||||
|
|
||||||
pub async fn insert_or_update_many<T: Clone>(
|
pub async fn insert_or_update_many<T: Clone>(
|
||||||
into: &dyn InsertOrUpdate<T>,
|
into: &dyn InsertOrUpdate<T>,
|
||||||
values: Vec<T>,
|
values: &Vec<T>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let i = Arc::new(into);
|
let i = Arc::new(into);
|
||||||
try_join_all(
|
try_join_all(
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
use futures_util::future::try_join_all;
|
||||||
use google_walletobjects1::api::{FlightClass, FlightObject};
|
use google_walletobjects1::api::{FlightClass, FlightObject};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::try_join;
|
use tokio::try_join;
|
||||||
|
@ -47,6 +49,9 @@ enum Commands {
|
||||||
/// Path to the image to read.
|
/// Path to the image to read.
|
||||||
image: PathBuf,
|
image: PathBuf,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// generates a add-to-wallet URL for a set of passes.
|
||||||
|
GenerateURL(GenerateURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
@ -81,6 +86,11 @@ struct UploadScan {
|
||||||
image: PathBuf,
|
image: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct GenerateURL {
|
||||||
|
pass_id: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
async fn load_thing<'a, T: Deserialize<'a>>(path: &PathBuf) -> Result<T> {
|
async fn load_thing<'a, T: Deserialize<'a>>(path: &PathBuf) -> Result<T> {
|
||||||
let data = tokio::fs::read(path).await?;
|
let data = tokio::fs::read(path).await?;
|
||||||
let mut deser = serde_json::Deserializer::from_reader(data.as_slice());
|
let mut deser = serde_json::Deserializer::from_reader(data.as_slice());
|
||||||
|
@ -95,7 +105,11 @@ async fn main() -> Result<()> {
|
||||||
let command = cli.command.unwrap();
|
let command = cli.command.unwrap();
|
||||||
|
|
||||||
if let Commands::Scan { image } = &command {
|
if let Commands::Scan { image } = &command {
|
||||||
println!("{}", scan(image)?);
|
let scanned = scan(image)?;
|
||||||
|
println!("{}", scanned);
|
||||||
|
let boarding_pass =
|
||||||
|
BoardingPass::parse(scanned.as_bytes().to_vec(), &Local::now().date_naive())?;
|
||||||
|
println!("{:#?}", boarding_pass);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -183,10 +197,89 @@ async fn main() -> Result<()> {
|
||||||
let (classes, passes) = passes_from_barcode(&cli.issuer_id, boarding_pass)?;
|
let (classes, passes) = passes_from_barcode(&cli.issuer_id, boarding_pass)?;
|
||||||
|
|
||||||
// Create all the classes.
|
// Create all the classes.
|
||||||
insert_or_update_many(&hub.flightclass(), classes).await?;
|
insert_or_update_many(&hub.flightclass(), &classes).await?;
|
||||||
|
|
||||||
// Create all the objects.
|
// Create all the objects.
|
||||||
insert_or_update_many(&hub.flightobject(), passes).await?;
|
insert_or_update_many(&hub.flightobject(), &passes).await?;
|
||||||
|
|
||||||
|
for pass in (&passes).iter() {
|
||||||
|
println!("Generated pass: {}", pass.id.clone().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now generate the URL.
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
AddToWallet {
|
||||||
|
service_account_email: sa.service_account_name.to_string(),
|
||||||
|
|
||||||
|
flight_classes: classes
|
||||||
|
.iter()
|
||||||
|
.map(|c| PassClassIdentifier {
|
||||||
|
id: c.id.clone().unwrap(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
flight_passes: passes
|
||||||
|
.iter()
|
||||||
|
.map(|p| PassIdentifier {
|
||||||
|
id: p.id.clone().unwrap(),
|
||||||
|
class_id: p.class_id.clone().unwrap(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
.to_url(&sa)?
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Commands::GenerateURL(generate_url) => {
|
||||||
|
let pass_ids: Vec<String> = generate_url
|
||||||
|
.pass_id
|
||||||
|
.iter()
|
||||||
|
.map(|pid| {
|
||||||
|
if pid.contains('.') {
|
||||||
|
pid.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", cli.issuer_id, pid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let passes_responses = try_join_all(
|
||||||
|
pass_ids
|
||||||
|
.iter()
|
||||||
|
.map(|pid| hub.flightobject().get(&pid).doit()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let passes: Vec<&FlightObject> = passes_responses
|
||||||
|
.iter()
|
||||||
|
.map(|result| {
|
||||||
|
let (_, pass) = result;
|
||||||
|
pass
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let class_ids: HashSet<String> =
|
||||||
|
HashSet::from_iter(passes.iter().map(|p| p.class_id.clone().unwrap()));
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
AddToWallet {
|
||||||
|
service_account_email: sa.service_account_name.to_string(),
|
||||||
|
|
||||||
|
flight_classes: class_ids
|
||||||
|
.iter()
|
||||||
|
.map(|c| PassClassIdentifier { id: c.clone() })
|
||||||
|
.collect(),
|
||||||
|
flight_passes: passes
|
||||||
|
.iter()
|
||||||
|
.map(|p| PassIdentifier {
|
||||||
|
id: p.id.clone().unwrap(),
|
||||||
|
class_id: p.class_id.clone().unwrap(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
.to_url(&sa)?
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,8 @@ impl CheckInSource {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BoardingPass {
|
pub struct BoardingPass {
|
||||||
|
pub initial_data: Vec<u8>,
|
||||||
|
|
||||||
pub passenger_name: String,
|
pub passenger_name: String,
|
||||||
pub eticket_indicator: char,
|
pub eticket_indicator: char,
|
||||||
|
|
||||||
|
@ -226,6 +228,8 @@ impl BoardingPass {
|
||||||
|
|
||||||
// We now have enough to start composing the boardingpass.
|
// We now have enough to start composing the boardingpass.
|
||||||
let mut boarding_pass = BoardingPass {
|
let mut boarding_pass = BoardingPass {
|
||||||
|
initial_data: data.clone(),
|
||||||
|
|
||||||
passenger_name,
|
passenger_name,
|
||||||
eticket_indicator,
|
eticket_indicator,
|
||||||
|
|
||||||
|
|
|
@ -34,4 +34,22 @@ pub static AIRLINE_DATA: phf::Map<&'static str, AirlineDefinition<'static>> = ph
|
||||||
hero_image_logo_url: Some("https://p.lukegb.com/raw/FormerlyDistinctToad.png"),
|
hero_image_logo_url: Some("https://p.lukegb.com/raw/FormerlyDistinctToad.png"),
|
||||||
boarding_privilege_logo_url: Some("https://p.lukegb.com/raw/DefinitelyVerifiedTitmouse.png"),
|
boarding_privilege_logo_url: Some("https://p.lukegb.com/raw/DefinitelyVerifiedTitmouse.png"),
|
||||||
},
|
},
|
||||||
|
"BA" => AirlineDefinition{
|
||||||
|
iata_code: "BA",
|
||||||
|
name: "British Airways",
|
||||||
|
boarding_policy: "GROUP_BASED",
|
||||||
|
seat_class_policy: "CLASS_BASED",
|
||||||
|
|
||||||
|
//boarding_pass_background_colour: "#b38f47", // BA Gold
|
||||||
|
//alliance_logo_url: Some("https://p.lukegb.com/raw/UltimatelySunnyMacaque.png"),
|
||||||
|
|
||||||
|
boarding_pass_background_colour: "#ffffff", // BA no-status
|
||||||
|
alliance_logo_url: Some("https://p.lukegb.com/raw/MultiplyAptHedgehog.png"),
|
||||||
|
|
||||||
|
frequent_flyer_program_name: Some("Executive Club Gold"),
|
||||||
|
|
||||||
|
logo_url: "https://p.lukegb.com/raw/HorriblyGreatTitmouse.png",
|
||||||
|
hero_image_logo_url: None,
|
||||||
|
boarding_privilege_logo_url: Some("https://p.lukegb.com/raw/VerticallyJustKoala.png"),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,6 +19,12 @@ pub fn passes_from_barcode(
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let frequent_flyer_program_data = if leg.frequent_flyer_airline.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
AIRLINE_DATA.get(&leg.frequent_flyer_airline.as_ref().unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
let id_prefix = format!(
|
let id_prefix = format!(
|
||||||
"{}.{}{}-{}-{}{}",
|
"{}.{}{}-{}-{}{}",
|
||||||
issuer_id,
|
issuer_id,
|
||||||
|
@ -63,9 +69,62 @@ pub fn passes_from_barcode(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let pass_barcode = String::from_utf8(boarding_pass.initial_data)?;
|
||||||
|
|
||||||
|
let eticket_number =
|
||||||
|
if leg.airline_numeric_code.is_some() && leg.document_serial_number.is_some() {
|
||||||
|
Some(format!(
|
||||||
|
"{}-{}",
|
||||||
|
leg.airline_numeric_code.as_ref().unwrap(),
|
||||||
|
leg.document_serial_number.as_ref().unwrap()
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Some("125-2150000000".to_string())
|
||||||
|
};
|
||||||
|
|
||||||
let pass = FlightObject {
|
let pass = FlightObject {
|
||||||
id: Some(pass_id),
|
id: Some(pass_id),
|
||||||
class_id: Some(class_id.clone()),
|
class_id: Some(class_id.clone()),
|
||||||
|
state: Some("ACTIVE".to_string()),
|
||||||
|
|
||||||
|
passenger_name: Some(boarding_pass.passenger_name.clone()),
|
||||||
|
reservation_info: Some(ReservationInfo {
|
||||||
|
confirmation_code: Some(leg.operating_carrier_pnr.clone()),
|
||||||
|
eticket_number,
|
||||||
|
frequent_flyer_info: if frequent_flyer_program_data.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(FrequentFlyerInfo {
|
||||||
|
frequent_flyer_program_name: frequent_flyer_program_data
|
||||||
|
.unwrap().frequent_flyer_program_name
|
||||||
|
.map(|n| localized_string("en-GB", n)),
|
||||||
|
frequent_flyer_number: leg.frequent_flyer_number.clone(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
|
||||||
|
boarding_and_seating_info: Some(BoardingAndSeatingInfo {
|
||||||
|
boarding_group: Some("GROUP1".to_string()),
|
||||||
|
seat_class: Some("Club World".to_string()),
|
||||||
|
seat_number: Some(leg.seat_number.clone()),
|
||||||
|
sequence_number: Some(leg.check_in_sequence.clone()),
|
||||||
|
boarding_privilege_image: airline_data
|
||||||
|
.boarding_privilege_logo_url
|
||||||
|
.clone()
|
||||||
|
.map(|u| url_to_image(u)),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
|
||||||
|
barcode: Some(Barcode {
|
||||||
|
type_: Some("aztec".to_string()),
|
||||||
|
value: Some(pass_barcode),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
|
||||||
|
hex_background_color: Some("#b38f47".to_string()),
|
||||||
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue