orangeconnex package tracking
Signed-off-by: Christine Dodrill <me@christine.website>
This commit is contained in:
parent
02ef9809c7
commit
3521b125cf
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE orangeconnex_packages;
|
||||
DROP INDEX orangeconnex_traces_time;
|
||||
DROP TABLE orangeconnex_traces;
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS orangeconnex_packages
|
||||
( tracking_number TEXT UNIQUE NOT NULL PRIMARY KEY
|
||||
, recieved BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS orangeconnex_traces
|
||||
( id TEXT UNIQUE NOT NULL PRIMARY KEY
|
||||
, tracking_number TEXT NOT NULL
|
||||
, description TEXT NOT NULL
|
||||
, city TEXT
|
||||
, country TEXT NOT NULL
|
||||
, time_recorded TEXT NOT NULL
|
||||
, time_zone TEXT NOT NULL
|
||||
, ts INTEGER NOT NULL
|
||||
, FOREIGN KEY (tracking_number)
|
||||
REFERENCES orangeconnex_packages(tracking_number)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX orangeconnex_traces_time
|
||||
ON orangeconnex_traces(time_recorded, time_zone, ts);
|
|
@ -12,6 +12,7 @@ use rocket::{
|
|||
use rocket_contrib::json::Json;
|
||||
use std::io::Read;
|
||||
|
||||
pub mod package_tracking;
|
||||
pub mod posse;
|
||||
pub mod switch;
|
||||
pub mod webmention;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pub mod orangeconnex;
|
|
@ -0,0 +1,55 @@
|
|||
use crate::{
|
||||
api::{Error, Result, StringBody},
|
||||
models, paseto, schema, MainDatabase,
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
use rocket_contrib::json::Json;
|
||||
|
||||
#[post("/packages/orangeconnex/track", data = "<tn>")]
|
||||
#[instrument(skip(conn), err)]
|
||||
pub fn track(conn: MainDatabase, tn: StringBody, tok: paseto::Token) -> Result<String> {
|
||||
use schema::orangeconnex_packages::table;
|
||||
let tn = tn.unwrap();
|
||||
|
||||
diesel::insert_into(table)
|
||||
.values(&models::OrangeConnexPackage {
|
||||
tracking_number: tn.clone(),
|
||||
recieved: false,
|
||||
})
|
||||
.execute(&*conn)
|
||||
.map_err(Error::Database)?;
|
||||
|
||||
Ok(format!("now tracking package {}", tn))
|
||||
}
|
||||
|
||||
#[get("/packages/orangeconnex/status?<tn>")]
|
||||
#[instrument(skip(conn), err)]
|
||||
pub fn status(
|
||||
conn: MainDatabase,
|
||||
tn: String,
|
||||
tok: paseto::Token,
|
||||
) -> Result<Json<Vec<models::OrangeConnexTrace>>> {
|
||||
use schema::orangeconnex_traces;
|
||||
|
||||
Ok(Json(
|
||||
orangeconnex_traces::table
|
||||
.load::<models::OrangeConnexTrace>(&*conn)
|
||||
.map_err(Error::Database)?,
|
||||
))
|
||||
}
|
||||
|
||||
#[post("/packages/orangeconnex/delivered?<tn>")]
|
||||
#[instrument(skip(conn), err)]
|
||||
pub fn recieved(conn: MainDatabase, tn: String, tok: paseto::Token) -> Result<String> {
|
||||
use schema::orangeconnex_packages::dsl::*;
|
||||
|
||||
diesel::update(orangeconnex_packages.find(tn.clone()))
|
||||
.set(&models::OrangeConnexPackage {
|
||||
tracking_number: tn.clone(),
|
||||
recieved: true,
|
||||
})
|
||||
.execute(&*conn)
|
||||
.map_err(Error::Database)?;
|
||||
|
||||
Ok(format!("{} is marked as recieved", tn))
|
||||
}
|
|
@ -2,8 +2,7 @@
|
|||
extern crate tracing;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use diesel::{prelude::*, SqliteConnection};
|
||||
use std::env;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use mi::{api::posse::*, *};
|
||||
|
||||
|
@ -16,14 +15,7 @@ fn main() -> Result<()> {
|
|||
let conn = establish_connection();
|
||||
|
||||
let feed = read_jsonfeed(BLOG_FEED_URL.to_string())?;
|
||||
let posts: Vec<models::Blogpost> = feed
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let post: models::Blogpost = item.into();
|
||||
post
|
||||
})
|
||||
.collect();
|
||||
let posts: Vec<models::Blogpost> = feed.items.into_iter().map(Into::into).collect();
|
||||
diesel::insert_into(schema::blogposts::table)
|
||||
.values(&posts)
|
||||
.execute(&conn)?;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use mi::{api::Error, establish_connection, models, schema, web::orange_connex};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
info!(
|
||||
"{} package tracking updater starting up",
|
||||
mi::APPLICATION_NAME
|
||||
);
|
||||
let conn = establish_connection();
|
||||
|
||||
// OrangeConnex package tracking
|
||||
{
|
||||
use schema::{
|
||||
orangeconnex_packages::dsl::*, orangeconnex_traces::dsl::orangeconnex_traces,
|
||||
};
|
||||
let packages = orangeconnex_packages
|
||||
.filter(recieved.eq(false))
|
||||
.load::<models::OrangeConnexPackage>(&conn)
|
||||
.map_err(Error::Database)?;
|
||||
|
||||
for package in packages.into_iter() {
|
||||
let info = orange_connex::get(package.tracking_number.clone())?
|
||||
.get_waybill()
|
||||
.unwrap();
|
||||
let tn = package.tracking_number;
|
||||
|
||||
let traces: Vec<models::OrangeConnexTrace> = info
|
||||
.traces
|
||||
.into_iter()
|
||||
.map(|tr| models::OrangeConnexTrace::from_trace(tr, tn.clone()))
|
||||
.collect();
|
||||
|
||||
let _ = diesel::insert_into(orangeconnex_traces)
|
||||
.values(&traces)
|
||||
//.on_conflict((time_recorded, time_zone, ts))
|
||||
//.do_nothing()
|
||||
.execute(&conn)
|
||||
.map_err(Error::Database);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -70,6 +70,9 @@ fn main() -> Result<()> {
|
|||
.mount(
|
||||
"/api",
|
||||
routes![
|
||||
api::package_tracking::orangeconnex::track,
|
||||
api::package_tracking::orangeconnex::status,
|
||||
api::package_tracking::orangeconnex::recieved,
|
||||
api::posse::notify,
|
||||
api::posse::refresh_blog,
|
||||
api::switch::current_front,
|
||||
|
|
|
@ -72,3 +72,39 @@ pub struct Blogpost {
|
|||
pub url: String,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Associations, Insertable, AsChangeset)]
|
||||
#[table_name = "orangeconnex_packages"]
|
||||
pub struct OrangeConnexPackage {
|
||||
pub tracking_number: String,
|
||||
pub recieved: bool,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Associations, Insertable, Serialize)]
|
||||
#[table_name = "orangeconnex_traces"]
|
||||
pub struct OrangeConnexTrace {
|
||||
pub id: String,
|
||||
pub tracking_number: String,
|
||||
pub description: String,
|
||||
pub city: Option<String>,
|
||||
pub country: String,
|
||||
pub time_recorded: String,
|
||||
pub time_zone: String,
|
||||
pub ts: i32,
|
||||
}
|
||||
|
||||
impl OrangeConnexTrace {
|
||||
pub fn from_trace(t: crate::web::orange_connex::Trace, tracking_number: String) -> Self {
|
||||
use rusty_ulid::generate_ulid_string;
|
||||
Self {
|
||||
id: generate_ulid_string(),
|
||||
tracking_number: tracking_number,
|
||||
description: t.event_desc,
|
||||
city: t.opr_city,
|
||||
country: t.opr_country,
|
||||
time_recorded: t.opr_time,
|
||||
time_zone: t.opr_time_zone,
|
||||
ts: t.opr_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,26 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
orangeconnex_packages (tracking_number) {
|
||||
tracking_number -> Text,
|
||||
recieved -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
orangeconnex_traces (id) {
|
||||
id -> Text,
|
||||
tracking_number -> Text,
|
||||
description -> Text,
|
||||
city -> Nullable<Text>,
|
||||
country -> Text,
|
||||
time_recorded -> Text,
|
||||
time_zone -> Text,
|
||||
ts -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
switches (id) {
|
||||
id -> Text,
|
||||
|
@ -39,11 +59,14 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
joinable!(orangeconnex_traces -> orangeconnex_packages (tracking_number));
|
||||
joinable!(switches -> members (member_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
blogposts,
|
||||
members,
|
||||
orangeconnex_packages,
|
||||
orangeconnex_traces,
|
||||
switches,
|
||||
weather,
|
||||
webmentions,
|
||||
|
|
|
@ -120,7 +120,7 @@ impl From<types::Forecast> for Forecast {
|
|||
} else {
|
||||
None
|
||||
},
|
||||
low: if f.temperatures.temperature.class.as_ref().unwrap() == "low" {
|
||||
low: if f.temperatures.temperature.class.unwrap() == "low" {
|
||||
f.temperatures.temperature.value
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod bridgy;
|
|||
pub mod canada_weather;
|
||||
pub mod discord_webhook;
|
||||
pub mod mastodon;
|
||||
pub mod orange_connex;
|
||||
pub mod pluralkit;
|
||||
pub mod switchcounter;
|
||||
pub mod twitter;
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
This module will fetch package tracking information from the OrangeConnex
|
||||
API. I am not sure that this is completely kosher. Users of this function must
|
||||
aggressively cache the results.
|
||||
*/
|
||||
use super::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An annoying wrapper type.
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Info {
|
||||
pub has_business_exception: bool,
|
||||
pub success: bool,
|
||||
pub result: Wrapper,
|
||||
}
|
||||
|
||||
impl Info {
|
||||
pub fn get_waybill(self) -> Option<Waybill> {
|
||||
if self.success == true && self.result.waybills.len() != 0 {
|
||||
Some(self.result.waybills[0].clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Another annoying wrapper type.
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Wrapper {
|
||||
pub not_exists_tracking_numbers: Vec<String>,
|
||||
pub waybills: Vec<Waybill>,
|
||||
}
|
||||
|
||||
/// The information about the most current status of a package, as well as its
|
||||
/// source and destination.
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Waybill {
|
||||
pub consignee_city_code: String,
|
||||
pub consignee_city_name: String,
|
||||
pub consignee_country_code: String,
|
||||
pub consignee_country_name: String,
|
||||
pub consignee_zip_code: String,
|
||||
pub consignment_city_code: String,
|
||||
pub consignment_city_name: String,
|
||||
pub consignment_country_code: String,
|
||||
pub consignment_country_name: String,
|
||||
pub consignment_zip_code: String,
|
||||
pub is_event_code: String,
|
||||
pub last_status: String,
|
||||
pub last_time: String,
|
||||
pub last_time_zone: String,
|
||||
pub last_timestamp: i64,
|
||||
pub last_zip_code: String,
|
||||
pub traces: Vec<Trace>,
|
||||
}
|
||||
|
||||
/// Each step in the package's journey to being delivered.
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Trace {
|
||||
pub event_desc: String,
|
||||
pub opr_city: Option<String>,
|
||||
pub opr_country: String,
|
||||
pub opr_time: String,
|
||||
pub opr_time_zone: String,
|
||||
pub opr_timestamp: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PostBody {
|
||||
tracking_numbers: Vec<String>,
|
||||
}
|
||||
|
||||
/// Fetch tracking information from OrangeConnex.
|
||||
pub fn get(tracking_number: String) -> Result<Info> {
|
||||
let resp = ureq::post(
|
||||
"https://azure-cn.orangeconnex.com/oc/capricorn-website/website/v1/tracking/traces",
|
||||
)
|
||||
.set(
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0",
|
||||
)
|
||||
.set("Accept", "*/*")
|
||||
.set("Accept-Language", "en-US")
|
||||
.set("Content-Type", "application/json;charset=utf-8")
|
||||
.set("Origin", "https://www.orangeconnex.com")
|
||||
.set("Connection", "keep-alive")
|
||||
.set(
|
||||
"Referer",
|
||||
&format!(
|
||||
"https://www.orangeconnex.com/tracking?language=en&trackingnumber={}",
|
||||
tracking_number
|
||||
),
|
||||
)
|
||||
.set("Cache-Control", "max-age=0")
|
||||
.send_json(serde_json::to_value(PostBody {
|
||||
tracking_numbers: vec![tracking_number],
|
||||
})?);
|
||||
|
||||
if resp.ok() {
|
||||
Ok(resp.into_json_deserialize()?)
|
||||
} else {
|
||||
Err(match resp.synthetic_error() {
|
||||
Some(why) => Error::UReq(why.to_string()),
|
||||
None => Error::HttpStatus(resp.status()),
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue