const FAKTURA: &str = "\
Stowarzyszenie „Warszawski Hackerspace”
NIP: 5252540655
ul. Wolność 2A
01-018 Warszawa";
const SRU_DIE: u8 = 6;
use std::sync::atomic::{
AtomicU8,
Ordering,
};
use crate::prelude::*;
use crate::utils;
#[derive(Debug, Deserialize)]
#[serde(default)]
#[doc(hidden)]
pub struct Config {
enabled: bool,
priv_rooms: Vec<OwnedRoomOrAliasId>,
}
impl Default for Config {
fn default() -> Self {
Self {
enabled: true,
priv_rooms: Vec::default(),
}
}
}
#[doc(hidden)]
pub fn load(bot: Rdzobot) {
if !bot.config().module.hswaw.enabled {
return;
}
bot.add_command(
clap::Command::new("!faktura")
.visible_alias("!faktury")
.about("Pokaże aktualne dane do faktury"),
on_cmd_faktura,
);
bot.add_command(LabelArgs::command(), on_cmd_label);
bot.add_command(clap::Command::new("!sru").about("Hackerspejsowa ruletka"), on_cmd_sru);
}
async fn on_cmd_faktura(
_arg_matches: clap::ArgMatches,
_event: OriginalSyncRoomMessageEvent,
_client: Client,
room: Room,
_bot: Rdzobot,
) -> anyhow::Result<()> {
room.send(RoomMessageEventContent::notice_plain(FAKTURA)).await?;
Ok(())
}
async fn on_cmd_sru(
_arg_matches: clap::ArgMatches,
event: OriginalSyncRoomMessageEvent,
_client: Client,
room: Room,
_bot: Rdzobot,
) -> anyhow::Result<()> {
static COUNTER: AtomicU8 = AtomicU8::new(SRU_DIE - 1);
let mut prev = COUNTER.load(Ordering::Relaxed);
let (mut lucky, mut next) = sru_roll(prev);
loop {
match COUNTER.compare_exchange_weak(prev, next, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => break,
Err(next_prev) => {
prev = next_prev;
(lucky, next) = sru_roll(prev);
}
}
}
if lucky {
room.send(RoomMessageEventContent::notice_plain(format!(
"You were lucky this time (chance 1/{})",
prev + 1,
)))
.await?;
} else if room
.kick_user(
&event.sender,
Some(format!("You were unlucky this time (chance 1/{})", prev + 1).as_str()),
)
.await
.is_err()
{
room.send(RoomMessageEventContent::notice_plain(format!(
"SRU! (chance 1/{}; can't kick)",
prev + 1,
)))
.await?;
}
Ok(())
}
fn sru_roll(prev: u8) -> (bool, u8) {
let n = (prev + 1) as u16;
let max: u8 = ((256u16 / n) * n - 1) as u8;
let mut random = utils::random();
let lucky = loop {
let b = random.next().unwrap();
if b <= max {
break b % (n as u8) != 0;
}
};
(lucky, if lucky { prev - 1 } else { SRU_DIE - 1 })
}
#[derive(clap::Parser)]
#[command(name = "!label")]
#[command(about = "Prints a sticker for labeling materials stored in heavy workshop")]
struct LabelArgs {
#[arg(long)]
print: bool,
nick: Vec<String>,
}
async fn on_cmd_label(
mut arg_matches: clap::ArgMatches,
event: OriginalSyncRoomMessageEvent,
client: Client,
room: Room,
bot: Rdzobot,
) -> anyhow::Result<()> {
let args = LabelArgs::from_arg_matches_mut(&mut arg_matches).unwrap();
let sender = utils::localpart_without_bridge_prefix(&event.sender).to_string();
let url = reqwest::Url::parse_with_params(
format!(
"https://label.hackerspace.pl/api/{}/100/",
if args.print { "print" } else { "preview" }
)
.as_str(),
&[
("usemarkup", "1"),
(
"text",
format!(
"{}\n<span size=\"60pt\" color=\"white\" bgcolor=\"black\"> {} </span>",
if args.nick.is_empty() {
sender.clone()
} else {
args.nick.join(" ")
},
chrono::Utc::now()
.with_timezone(&bot.config().timezone)
.format("%e.%m.%Y")
.to_string()
.trim(),
)
.as_str(),
),
],
)
.unwrap();
tracing::debug!("!print url={}", url.as_str());
if args.print {
if !utils::check_acl_room(client.clone(), &bot.config().module.hswaw.priv_rooms, &room)
.await?
{
room.send(RoomMessageEventContent::notice_plain("not in this channel").make_reply_to(
&event.clone().into_full_event(room.room_id().into()),
ForwardThread::No,
AddMentions::Yes,
))
.await?;
return Ok(());
}
if !kasownik_judgement(client.http_client().clone(), &sender).await? {
room.send(
RoomMessageEventContent::notice_plain("żeby drukować, trzeba płacić składki")
.make_reply_to(
&event.clone().into_full_event(room.room_id().into()),
ForwardThread::No,
AddMentions::Yes,
),
)
.await?;
return Ok(());
}
client
.http_client()
.post(url)
.basic_auth("rdzobot", bot.get_secret("http-basic", "rdzobot@label.hackerspace.pl"))
.send()
.await?
.bytes()
.await?;
} else {
let image = client
.http_client()
.get(url)
.basic_auth("rdzobot", bot.get_secret("http-basic", "rdzobot@label.hackerspace.pl"))
.send()
.await?
.bytes()
.await?;
room.send_attachment(
"label.png",
&mime::IMAGE_PNG,
image.into(),
matrix_sdk::attachment::AttachmentConfig::new(),
)
.await?;
}
Ok(())
}
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "status")]
#[allow(dead_code)]
enum KasownikJudgement {
#[serde(rename = "ok")]
Ok { content: bool, modified: String },
#[serde(rename = "error")]
Error { content: String, modified: String },
}
pub async fn kasownik_judgement(http: reqwest::Client, member: &str) -> anyhow::Result<bool> {
let url = reqwest::Url::parse(
format!("https://kasownik.hackerspace.pl/api/judgement/{member}.json").as_str(),
)?;
match http.get(url).send().await?.json::<KasownikJudgement>().await? {
KasownikJudgement::Ok { content, .. } => Ok(content),
KasownikJudgement::Error { .. } => Ok(false),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_kasownik_judgement() -> anyhow::Result<()> {
let http = reqwest::Client::new();
assert!(kasownik_judgement(http.clone(), "woju").await?); assert!(!kasownik_judgement(http.clone(), "root").await?);
Ok(())
}
}