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 rocket_contrib::json::Json;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
pub mod package_tracking;
|
||||||
pub mod posse;
|
pub mod posse;
|
||||||
pub mod switch;
|
pub mod switch;
|
||||||
pub mod webmention;
|
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;
|
extern crate tracing;
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use diesel::{prelude::*, SqliteConnection};
|
use diesel::prelude::*;
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use mi::{api::posse::*, *};
|
use mi::{api::posse::*, *};
|
||||||
|
|
||||||
|
@ -16,14 +15,7 @@ fn main() -> Result<()> {
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
let feed = read_jsonfeed(BLOG_FEED_URL.to_string())?;
|
let feed = read_jsonfeed(BLOG_FEED_URL.to_string())?;
|
||||||
let posts: Vec<models::Blogpost> = feed
|
let posts: Vec<models::Blogpost> = feed.items.into_iter().map(Into::into).collect();
|
||||||
.items
|
|
||||||
.into_iter()
|
|
||||||
.map(|item| {
|
|
||||||
let post: models::Blogpost = item.into();
|
|
||||||
post
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
diesel::insert_into(schema::blogposts::table)
|
diesel::insert_into(schema::blogposts::table)
|
||||||
.values(&posts)
|
.values(&posts)
|
||||||
.execute(&conn)?;
|
.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(
|
.mount(
|
||||||
"/api",
|
"/api",
|
||||||
routes![
|
routes![
|
||||||
|
api::package_tracking::orangeconnex::track,
|
||||||
|
api::package_tracking::orangeconnex::status,
|
||||||
|
api::package_tracking::orangeconnex::recieved,
|
||||||
api::posse::notify,
|
api::posse::notify,
|
||||||
api::posse::refresh_blog,
|
api::posse::refresh_blog,
|
||||||
api::switch::current_front,
|
api::switch::current_front,
|
||||||
|
|
|
@ -72,3 +72,39 @@ pub struct Blogpost {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub title: 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! {
|
table! {
|
||||||
switches (id) {
|
switches (id) {
|
||||||
id -> Text,
|
id -> Text,
|
||||||
|
@ -39,11 +59,14 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
joinable!(orangeconnex_traces -> orangeconnex_packages (tracking_number));
|
||||||
joinable!(switches -> members (member_id));
|
joinable!(switches -> members (member_id));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
blogposts,
|
blogposts,
|
||||||
members,
|
members,
|
||||||
|
orangeconnex_packages,
|
||||||
|
orangeconnex_traces,
|
||||||
switches,
|
switches,
|
||||||
weather,
|
weather,
|
||||||
webmentions,
|
webmentions,
|
||||||
|
|
|
@ -120,7 +120,7 @@ impl From<types::Forecast> for Forecast {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
low: if f.temperatures.temperature.class.as_ref().unwrap() == "low" {
|
low: if f.temperatures.temperature.class.unwrap() == "low" {
|
||||||
f.temperatures.temperature.value
|
f.temperatures.temperature.value
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub mod bridgy;
|
||||||
pub mod canada_weather;
|
pub mod canada_weather;
|
||||||
pub mod discord_webhook;
|
pub mod discord_webhook;
|
||||||
pub mod mastodon;
|
pub mod mastodon;
|
||||||
|
pub mod orange_connex;
|
||||||
pub mod pluralkit;
|
pub mod pluralkit;
|
||||||
pub mod switchcounter;
|
pub mod switchcounter;
|
||||||
pub mod twitter;
|
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