working
This commit is contained in:
parent
e69bcfc23d
commit
b09e8eafbb
16 changed files with 2531 additions and 258 deletions
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "tb-rs"
|
||||
name = "tb-spond-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
|
|
@ -20,7 +20,6 @@ serde_json = "1.0.149"
|
|||
serde_qs = "1.0.0"
|
||||
spond-api = { version = "0.1.0", path = "../api" }
|
||||
thiserror = "2.0.18"
|
||||
#spond-macros = { version = "0.1.0", path = "../macros" }
|
||||
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
|
||||
url = "2.5.8"
|
||||
xdg = "3.0.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clap::{Args, ArgGroup};
|
||||
use anyhow::{Error, Result};
|
||||
use clap::{ArgGroup, Args};
|
||||
use restson::RestClient;
|
||||
use anyhow::{Result, Error};
|
||||
use std::str::{FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
#[command(group(
|
||||
|
|
@ -29,13 +29,24 @@ fn bearer(mut client: RestClient, token: &str) -> Result<RestClient> {
|
|||
|
||||
impl Authentication {
|
||||
pub async fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
let client = match (self.access.as_ref(), self.refresh.as_ref(), self.email.as_ref(), self.phone.as_ref()) {
|
||||
(Some(ref v), None, None, None) => v.apply(client)?,
|
||||
(None, Some(ref v), None, None) => v.apply(client).await?,
|
||||
(None, None, Some(ref v), None) => v.apply(client).await?,
|
||||
(None, None, None, Some(ref v)) => v.apply(client).await?,
|
||||
let client = match (
|
||||
self.access.as_ref(),
|
||||
self.refresh.as_ref(),
|
||||
self.email.as_ref(),
|
||||
self.phone.as_ref(),
|
||||
) {
|
||||
(Some(v), None, None, None) => v.apply(client)?,
|
||||
(None, Some(v), None, None) => v.apply(client).await?,
|
||||
(None, None, Some(v), None) => v.apply(client).await?,
|
||||
(None, None, None, Some(v)) => v.apply(client).await?,
|
||||
(None, None, None, None) => client,
|
||||
(a, b, c, d) => anyhow::bail!("invalid authentication: {} + {} + {} + {}", a.is_some(), b.is_some(), c.is_some(), d.is_some()),
|
||||
(a, b, c, d) => anyhow::bail!(
|
||||
"invalid authentication: {} + {} + {} + {}",
|
||||
a.is_some(),
|
||||
b.is_some(),
|
||||
c.is_some(),
|
||||
d.is_some()
|
||||
),
|
||||
};
|
||||
Ok(client)
|
||||
}
|
||||
|
|
@ -63,12 +74,13 @@ impl FromStr for WithPassword {
|
|||
struct Email(WithPassword);
|
||||
impl Email {
|
||||
async fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
let tokens = spond_api::authentication::email(&client, &self.0.value, &self.0.password).await?;
|
||||
let tokens =
|
||||
spond_api::authentication::email(&client, &self.0.value, &self.0.password).await?;
|
||||
bearer(client, tokens.access.token.as_ref())
|
||||
}
|
||||
}
|
||||
impl FromStr for Email {
|
||||
type Err= <WithPassword as FromStr>::Err;
|
||||
type Err = <WithPassword as FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(WithPassword::from_str(s)?))
|
||||
|
|
@ -79,12 +91,13 @@ impl FromStr for Email {
|
|||
struct Phone(WithPassword);
|
||||
impl Phone {
|
||||
async fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
let tokens = spond_api::authentication::phone(&client, &self.0.value, &self.0.password).await?;
|
||||
let tokens =
|
||||
spond_api::authentication::phone(&client, &self.0.value, &self.0.password).await?;
|
||||
bearer(client, tokens.access.token.as_ref())
|
||||
}
|
||||
}
|
||||
impl FromStr for Phone {
|
||||
type Err= <WithPassword as FromStr>::Err;
|
||||
type Err = <WithPassword as FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(WithPassword::from_str(s)?))
|
||||
|
|
|
|||
303
cli/src/main.rs
303
cli/src/main.rs
|
|
@ -1,8 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use restson::RestClient;
|
||||
use anyhow::Result;
|
||||
use url::Url;
|
||||
use spond_api as api;
|
||||
use url::Url;
|
||||
use xdg::BaseDirectories as xdg;
|
||||
|
||||
mod authentication;
|
||||
|
|
@ -33,7 +33,7 @@ impl Cli {
|
|||
pub async fn client(&self) -> Result<RestClient> {
|
||||
let base = self.base.join("/core/v1/")?;
|
||||
let client = RestClient::new(base.as_str())?;
|
||||
Ok(self.authentication.apply(client).await?)
|
||||
self.authentication.apply(client).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ struct Seed(u64);
|
|||
|
||||
impl Default for Seed {
|
||||
fn default() -> Self {
|
||||
use rand::{rng, RngExt};
|
||||
use rand::{RngExt, rng};
|
||||
Self(rng().random())
|
||||
}
|
||||
}
|
||||
|
|
@ -68,28 +68,27 @@ impl std::fmt::Display for Seed {
|
|||
}
|
||||
|
||||
impl Seed {
|
||||
pub fn shuffle<'a, T, F, W>(&self, input: &'a [T], weight: F) -> Result<Vec<T>>
|
||||
pub fn shuffle<T, F, W>(&self, input: &[T], weight: F) -> Result<Vec<T>>
|
||||
where
|
||||
F: Fn(T) -> W,
|
||||
W: Into<f64>,
|
||||
T: Copy
|
||||
T: Copy,
|
||||
{
|
||||
use rand::{SeedableRng, rngs::StdRng};
|
||||
let len = input.len();
|
||||
|
||||
|
||||
let sample = rand::seq::index::sample_weighted(
|
||||
&mut StdRng::seed_from_u64(self.0),
|
||||
len,
|
||||
|idx| weight(input[idx]),
|
||||
len,
|
||||
)?;
|
||||
|
||||
|
||||
log::debug!("sample: {:?}", sample);
|
||||
Ok(sample.into_iter().map(move |idx| input[idx]).collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Weights<Id>(std::collections::HashMap<Id, usize>);
|
||||
|
||||
|
|
@ -135,33 +134,30 @@ where
|
|||
|
||||
impl<Id> Weights<Id>
|
||||
where
|
||||
Id: Eq + Hash + Copy + serde::de::DeserializeOwned
|
||||
Id: Eq + Hash + Copy + serde::de::DeserializeOwned,
|
||||
{
|
||||
pub fn load(serie: api::SeriesId) -> Result<Self> {
|
||||
let path = Self::path(serie)?;
|
||||
log::debug!("load {path:?}");
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)?;
|
||||
let file = std::fs::OpenOptions::new().read(true).open(path)?;
|
||||
|
||||
let data: std::collections::HashMap<Id, usize> = serde_json::from_reader(file)?;
|
||||
Ok(Self(data))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<Id> Weights<Id>
|
||||
where
|
||||
Id: Eq + Hash + Copy + serde::Serialize
|
||||
Id: Eq + Hash + Copy + serde::Serialize,
|
||||
{
|
||||
pub fn store(&self, serie: api::SeriesId) -> Result<()> {
|
||||
use std::fs::{File, rename};
|
||||
use std::io::{BufWriter, Write};
|
||||
|
||||
|
||||
let path = Self::path(serie)?;
|
||||
log::debug!("store {path:?}");
|
||||
let tmp = path.with_extension("json.tmp");
|
||||
log::trace!("temporary: {tmp:?}");
|
||||
|
||||
// create temporary file
|
||||
let file = File::create(&tmp)?;
|
||||
|
|
@ -180,6 +176,7 @@ where
|
|||
drop(writer);
|
||||
|
||||
// atomic replace old file
|
||||
log::trace!("rename {tmp:?} -> {path:?}");
|
||||
rename(&tmp, &path)?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -195,12 +192,14 @@ async fn main() -> Result<()> {
|
|||
let series = cli.series.map(api::SeriesId::new);
|
||||
let heading = cli.heading.as_ref();
|
||||
let vip: Vec<api::MemberId> = if let Some(ref vip) = cli.vip {
|
||||
vip.into_iter().map(|id| api::MemberId::new(*id)).collect()
|
||||
vip.iter().map(|id| api::MemberId::new(*id)).collect()
|
||||
} else {
|
||||
[
|
||||
0xEB07B45E45E6449386E70A7411816B6Fu128,
|
||||
0xD05F8574AC544C8DB1A7DC5B6347AA49u128,
|
||||
].map(|x| api::MemberId::new(x.into())).into()
|
||||
]
|
||||
.map(|x| api::MemberId::new(x.into()))
|
||||
.into()
|
||||
};
|
||||
let client = cli.client().await?;
|
||||
let client = &client;
|
||||
|
|
@ -213,189 +212,123 @@ async fn main() -> Result<()> {
|
|||
log::info!("heading: {heading}");
|
||||
}
|
||||
|
||||
if true {
|
||||
let now = chrono::Utc::now();
|
||||
let sponds = api::spond::search()
|
||||
.include_comments(true)
|
||||
.order(api::Order::Ascending)
|
||||
.max(1000)
|
||||
.min_start_timestamp(now)
|
||||
.max_end_timestamp(now + chrono::Duration::weeks(1))
|
||||
.call(client).await?;
|
||||
let now = chrono::Utc::now();
|
||||
let sponds = api::spond::search()
|
||||
.include_comments(true)
|
||||
.order(api::Order::Ascending)
|
||||
.max(1000)
|
||||
.min_start_timestamp(now)
|
||||
.max_end_timestamp(now + chrono::Duration::weeks(1))
|
||||
.call(client)
|
||||
.await?;
|
||||
|
||||
for spond in sponds.iter()
|
||||
.filter(|spond| {
|
||||
let result = series.is_none_or(|series| spond.series_id.is_some_and(|remote| remote == series))
|
||||
&& heading.is_none_or(|heading| spond.heading == *heading);
|
||||
log::trace!("{}: {:?} == {:?} => {:?}", spond.heading, spond.series_id, series, result);
|
||||
result
|
||||
})
|
||||
{
|
||||
log::debug!("{:?}", spond.responses);
|
||||
for spond in sponds.iter().filter(|spond| {
|
||||
let result = series
|
||||
.is_none_or(|series| spond.series_id.is_some_and(|remote| remote == series))
|
||||
&& heading.is_none_or(|heading| spond.heading == *heading);
|
||||
log::trace!(
|
||||
"{}: {:?} == {:?} => {:?}",
|
||||
spond.heading,
|
||||
spond.series_id,
|
||||
series,
|
||||
result
|
||||
);
|
||||
result
|
||||
}) {
|
||||
log::debug!("{:?}", spond.responses);
|
||||
|
||||
let spond = &spond;
|
||||
let decline = |id: &api::MemberId| {
|
||||
log::info!("remove {0}", *id);
|
||||
spond.decline(*id).call(client)
|
||||
};
|
||||
let accept = |id: &api::MemberId| {
|
||||
log::info!("accept {0}", *id);
|
||||
spond.accept(*id).call(client)
|
||||
};
|
||||
let spond = &spond;
|
||||
let decline = |id: &api::MemberId| {
|
||||
log::info!("remove {0}", *id);
|
||||
spond.decline(*id).call(client)
|
||||
};
|
||||
let accept = |id: &api::MemberId| {
|
||||
log::info!("accept {0}", *id);
|
||||
spond.accept(*id).call(client)
|
||||
};
|
||||
|
||||
let mut weights = spond.series_id.and_then(|series| Weights::load(series).ok()).unwrap_or_else(Weights::default);
|
||||
log::info!("{weights:?}");
|
||||
let mut weights = spond
|
||||
.series_id
|
||||
.and_then(|series| Weights::load(series).ok())
|
||||
.unwrap_or_else(Weights::default);
|
||||
log::info!("{weights:?}");
|
||||
|
||||
let (vip, interested) = {
|
||||
let mut r = (Vec::new(), Vec::new());
|
||||
for id in spond.responses.accepted_ids.iter()
|
||||
.chain(spond.responses.waitinglist_ids.iter()) {
|
||||
(if vip.contains(id) { &mut r.0 } else { &mut r.1 }).push(*id);
|
||||
}
|
||||
(r.0, seed.shuffle(&r.1, |idx| weights.weight(idx))?)
|
||||
};
|
||||
|
||||
// remove all registered participants
|
||||
let results = futures::future::join_all(interested.iter().map(|id|decline(id))).await;
|
||||
log::debug!("{results:?}");
|
||||
|
||||
// register them in order
|
||||
let mut responses = None;
|
||||
for id in interested.iter() {
|
||||
responses = Some(accept(id).await?);
|
||||
let (vip, interested) = {
|
||||
let mut r = (Vec::new(), Vec::new());
|
||||
for id in spond
|
||||
.responses
|
||||
.accepted_ids
|
||||
.iter()
|
||||
.chain(spond.responses.waitinglist_ids.iter())
|
||||
{
|
||||
(if vip.contains(id) { &mut r.0 } else { &mut r.1 }).push(*id);
|
||||
}
|
||||
(r.0, seed.shuffle(&r.1, |idx| weights.weight(idx))?)
|
||||
};
|
||||
|
||||
if let Some(responses) = responses {
|
||||
log::debug!("{responses:?}");
|
||||
// remove all registered participants
|
||||
let results = futures::future::join_all(interested.iter().map(&decline)).await;
|
||||
log::debug!("{results:?}");
|
||||
|
||||
let reorder = |mut responses: api::Responses| async move {
|
||||
// someone might have been registered right now
|
||||
let mut extra = Vec::new();
|
||||
loop {
|
||||
log::debug!("vip: {vip:?}");
|
||||
log::debug!("interested: {interested:?}");
|
||||
log::debug!("extra: {extra:?}");
|
||||
let reorder = responses.accepted_ids.iter()
|
||||
.chain(responses.waitinglist_ids.iter())
|
||||
.filter(|id| !(vip.contains(*id) || interested.contains(*id) || extra.contains(*id)))
|
||||
// register them in order
|
||||
let mut responses = None;
|
||||
for id in interested.iter() {
|
||||
responses = Some(accept(id).await?);
|
||||
}
|
||||
|
||||
if let Some(responses) = responses {
|
||||
log::debug!("{responses:?}");
|
||||
|
||||
let reorder = |mut responses: api::Responses| async move {
|
||||
// someone might have been registered right now
|
||||
let mut extra = Vec::new();
|
||||
loop {
|
||||
log::debug!("vip: {vip:?}");
|
||||
log::debug!("interested: {interested:?}");
|
||||
log::debug!("extra: {extra:?}");
|
||||
let reorder = responses
|
||||
.accepted_ids
|
||||
.iter()
|
||||
.chain(responses.waitinglist_ids.iter())
|
||||
.filter(|id| {
|
||||
!(vip.contains(*id)
|
||||
|| interested.contains(*id)
|
||||
|| extra.contains(*id))
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if reorder.is_empty() {
|
||||
let update = interested
|
||||
.iter()
|
||||
.filter(|id| responses.waitinglist_ids.contains(id))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if reorder.is_empty() {
|
||||
let update = interested.iter()
|
||||
.filter(|id| responses.waitinglist_ids.contains(id))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
break Ok::<Vec<api::MemberId>, anyhow::Error>(update);
|
||||
}
|
||||
let futures = futures::future::join_all(reorder.iter().map(|id|decline(id))).await;
|
||||
log::debug!("{futures:?}");
|
||||
|
||||
for id in reorder.into_iter() {
|
||||
responses = accept(&id).await?;
|
||||
extra.push(id);
|
||||
}
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||
break Ok::<Vec<api::MemberId>, anyhow::Error>(update);
|
||||
}
|
||||
};
|
||||
let futures =
|
||||
futures::future::join_all(reorder.iter().map(&decline)).await;
|
||||
log::debug!("{futures:?}");
|
||||
|
||||
let update = reorder(responses).await?;
|
||||
weights.update(&update);
|
||||
} else {
|
||||
weights = Weights::default();
|
||||
for id in reorder.into_iter() {
|
||||
responses = accept(&id).await?;
|
||||
extra.push(id);
|
||||
}
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!("{weights:?}");
|
||||
let update = reorder(responses).await?;
|
||||
weights.update(&update);
|
||||
} else {
|
||||
weights = Weights::default();
|
||||
};
|
||||
|
||||
if let Some(series) = spond.series_id {
|
||||
let _ = weights.store(series)?;
|
||||
}
|
||||
}
|
||||
log::debug!("{weights:?}");
|
||||
|
||||
|
||||
//for member in spond.responses.accepted_ids.iter()
|
||||
// .chain(spond.responses.waitinglist_ids.iter()) {
|
||||
// let result = map.insert(member, 1);
|
||||
// println!("{:?}: {:?}", member, result);
|
||||
//}
|
||||
//println!("{:?}", map);
|
||||
//println!("{:?}", &spond.responses);
|
||||
//let response = spond.response(member)
|
||||
// .accepted(false)
|
||||
// .call(&client)
|
||||
// .await?;
|
||||
//println!("{:?}", &response);
|
||||
} else if true {
|
||||
let profile = api::profile::identity(&client).await;
|
||||
if let Ok(profile) = profile {
|
||||
println!("profile: {:?}: {profile}", &profile.id);
|
||||
if let Some(series) = spond.series_id {
|
||||
weights.store(series)?;
|
||||
}
|
||||
} else if false {
|
||||
//let query = [
|
||||
// ("includeSponds", "true"),
|
||||
//];
|
||||
//let series = client.get_with::<_, Series>((0xCCBE049C31DA4FB691158E3FBC2DFBC8u128,), &query).await?.into_inner().0;
|
||||
let now = api::util::DateTime::default();
|
||||
let sponds = api::spond::search()
|
||||
.include_comments(true)
|
||||
.order(api::Order::Ascending)
|
||||
.max(100)
|
||||
.series_id(api::SeriesId::new(0x9333BDD4135E48BEAE88F1C3006A5FC0u128.into()))
|
||||
.min_end_timestamp(now)
|
||||
.min_start_timestamp(now)
|
||||
.call(&client).await?;
|
||||
for spond in sponds.iter() {
|
||||
println!("{spond:?}");
|
||||
//let spond = api::spond().call(&client, api::SpondId::new(0xF131CD46F80A42B9909D8E7F4018D8E1u128.into())).await?;
|
||||
//println!("{spond:?}");
|
||||
}
|
||||
//} else if true {
|
||||
//
|
||||
//#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
//struct Spond(serde_json::Value);
|
||||
//
|
||||
//#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
//struct Sponds(Vec<Spond>);
|
||||
//
|
||||
//impl restson::RestPath<()> for Sponds {
|
||||
// fn get_path(_: ()) -> std::result::Result<String, restson::Error> {
|
||||
// Ok(String::from("sponds"))
|
||||
// }
|
||||
//}
|
||||
// let now = &DateTime::default();
|
||||
// log::info!("{now:?} | {}", now.to_string());
|
||||
// let query = [
|
||||
// ("includeComments", "true"),
|
||||
// //("includeHidden", "true"),
|
||||
// ("addProfileInfo", "true"),
|
||||
// ("hidden", "true"),
|
||||
// ("scheduled", "true"),
|
||||
// ("order", "asc"),
|
||||
// ("max", "20"),
|
||||
// ("heading", "Schwimmtraining Donnerstag"),
|
||||
// ("seriesId", "CCBE049C31DA4FB691158E3FBC2DFBC8u128"),
|
||||
// ("minStartTimestamp", &now.to_string()),
|
||||
// ];
|
||||
//
|
||||
// for spond in client.get_with::<_, api::spond::Sponds>((), &query).await?.into_inner().0 {
|
||||
// //match spond.0 {
|
||||
// // serde_json::Value::Object(map) => {
|
||||
// // println!("{:?}", map);
|
||||
// // },
|
||||
// // _ => {},
|
||||
// //};
|
||||
// println!("{}", serde_json::to_string_pretty(&spond).unwrap());
|
||||
// }
|
||||
//} else {
|
||||
// let request = api::sponds()
|
||||
// .add_profile_info(false)
|
||||
// .comments(true)
|
||||
// .hidden(false)
|
||||
// .scheduled(true)
|
||||
// ;
|
||||
// for spond in request.call(&client).await? {
|
||||
// println!("{spond:?}");
|
||||
// }
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue