diff --git a/rust/passgen/src/insert_or_update.rs b/rust/passgen/src/insert_or_update.rs index 56f418acfb..c1178469f7 100644 --- a/rust/passgen/src/insert_or_update.rs +++ b/rust/passgen/src/insert_or_update.rs @@ -120,7 +120,7 @@ pub async fn insert_or_update_now_arc( pub async fn insert_or_update_many( into: &dyn InsertOrUpdate, - values: Vec, + values: &Vec, ) -> Result<()> { let i = Arc::new(into); try_join_all( diff --git a/rust/passgen/src/main.rs b/rust/passgen/src/main.rs index 452a53c6b8..d78a58554f 100644 --- a/rust/passgen/src/main.rs +++ b/rust/passgen/src/main.rs @@ -1,8 +1,10 @@ +use std::collections::HashSet; use std::path::PathBuf; use anyhow::{anyhow, Result}; use chrono::Local; use clap::{Args, Parser, Subcommand}; +use futures_util::future::try_join_all; use google_walletobjects1::api::{FlightClass, FlightObject}; use serde::Deserialize; use tokio::try_join; @@ -47,6 +49,9 @@ enum Commands { /// Path to the image to read. image: PathBuf, }, + + /// generates a add-to-wallet URL for a set of passes. + GenerateURL(GenerateURL), } #[derive(Args)] @@ -81,6 +86,11 @@ struct UploadScan { image: PathBuf, } +#[derive(Args)] +struct GenerateURL { + pass_id: Vec, +} + async fn load_thing<'a, T: Deserialize<'a>>(path: &PathBuf) -> Result { let data = tokio::fs::read(path).await?; let mut deser = serde_json::Deserializer::from_reader(data.as_slice()); @@ -95,7 +105,11 @@ async fn main() -> Result<()> { let command = cli.command.unwrap(); 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(()); }; @@ -183,10 +197,89 @@ async fn main() -> Result<()> { let (classes, passes) = passes_from_barcode(&cli.issuer_id, boarding_pass)?; // Create all the classes. - insert_or_update_many(&hub.flightclass(), classes).await?; + insert_or_update_many(&hub.flightclass(), &classes).await?; // 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 = 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 = + 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(()) } diff --git a/rust/passgen/src/resolution792.rs b/rust/passgen/src/resolution792.rs index 79244c52c9..dc169488be 100644 --- a/rust/passgen/src/resolution792.rs +++ b/rust/passgen/src/resolution792.rs @@ -117,6 +117,8 @@ impl CheckInSource { #[derive(Debug)] pub struct BoardingPass { + pub initial_data: Vec, + pub passenger_name: String, pub eticket_indicator: char, @@ -226,6 +228,8 @@ impl BoardingPass { // We now have enough to start composing the boardingpass. let mut boarding_pass = BoardingPass { + initial_data: data.clone(), + passenger_name, eticket_indicator, diff --git a/rust/passgen/src/static_data.rs b/rust/passgen/src/static_data.rs index 6bfb46536e..0618946c71 100644 --- a/rust/passgen/src/static_data.rs +++ b/rust/passgen/src/static_data.rs @@ -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"), 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"), + }, }; diff --git a/rust/passgen/src/walletobjects.rs b/rust/passgen/src/walletobjects.rs index 20cb187caf..515cbd3970 100644 --- a/rust/passgen/src/walletobjects.rs +++ b/rust/passgen/src/walletobjects.rs @@ -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!( "{}.{}{}-{}-{}{}", issuer_id, @@ -63,9 +69,62 @@ pub fn passes_from_barcode( ..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 { id: Some(pass_id), 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() };