passgen: more pass generation

This commit is contained in:
Luke Granger-Brown 2023-02-12 18:27:10 +00:00
parent 789e31e2d9
commit c801d3db51
5 changed files with 178 additions and 4 deletions

View file

@ -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(

View file

@ -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(())
} }

View file

@ -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,

View file

@ -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"),
},
}; };

View file

@ -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()
}; };