You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
3.7 KiB
Rust
152 lines
3.7 KiB
Rust
use std::{fmt::Display, future::Future, ops::Index, time::Duration};
|
|
|
|
use rand::Rng;
|
|
use tokio::{signal, time};
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct State([bool; 8]);
|
|
|
|
impl Index<usize> for State {
|
|
type Output = bool;
|
|
|
|
fn index(&self, index: usize) -> &Self::Output {
|
|
&self.0[index]
|
|
}
|
|
}
|
|
|
|
fn map_state(rule: u8, state: &State) -> State {
|
|
let mut new_state: [bool; 8] = [false; 8];
|
|
|
|
for i in 0..8 {
|
|
let pattern = ((i != 0 && state[i - 1]) as u8 * 4)
|
|
| (state[i] as u8 * 2)
|
|
| ((i != 7 && state[i + 1]) as u8);
|
|
new_state[i] = (rule >> pattern & 1) == 1;
|
|
}
|
|
|
|
State(new_state)
|
|
}
|
|
|
|
impl Display for State {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
self.0.map(|c| if c { "⬛" } else { "⬜" }).concat()
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Into<State> for u8 {
|
|
fn into(self) -> State {
|
|
let mut state = [false; 8];
|
|
|
|
for i in 0..8 {
|
|
state[i] = (self >> i & 1) == 1;
|
|
}
|
|
|
|
State(state)
|
|
}
|
|
}
|
|
|
|
async fn send_status(
|
|
rule: u8,
|
|
states: String,
|
|
) -> impl Future<Output = Result<reqwest::Response, reqwest::Error>> {
|
|
let client = reqwest::Client::new();
|
|
client
|
|
.post("https://mastodon.raptorpond.com/api/v1/statuses")
|
|
.header(
|
|
"Authorization",
|
|
format!(
|
|
"Bearer {}",
|
|
std::env::var("RULEBOT_MASTODON_TOKEN").unwrap_or_default()
|
|
),
|
|
)
|
|
.form(&[
|
|
("status", format!("Rule: {}\n{}", rule, states)),
|
|
("visibility", String::from("private")),
|
|
])
|
|
.send()
|
|
}
|
|
|
|
fn generate(seed: u16) -> String {
|
|
let rule: u8 = (seed & 0xff) as u8;
|
|
let init_state = ((seed >> 8 & 0xff) as u8).into();
|
|
|
|
(0..8)
|
|
.scan(init_state, |state, _i| {
|
|
let old_state = *state;
|
|
*state = map_state(rule, state);
|
|
Some(old_state)
|
|
})
|
|
.map(|state| state.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join("\n")
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
env_logger::init();
|
|
|
|
let generator = tokio::spawn(async {
|
|
let mut interval = time::interval(time::Duration::from_secs(
|
|
std::env::var("RULEBOT_POST_INTERVAL")
|
|
.unwrap_or("2".into())
|
|
.parse()
|
|
.unwrap(),
|
|
));
|
|
|
|
loop {
|
|
interval.tick().await;
|
|
|
|
let seed;
|
|
|
|
// Block to drop rng before switching context
|
|
{
|
|
let mut rng = rand::thread_rng();
|
|
|
|
seed = rng.gen();
|
|
}
|
|
|
|
let rule = (seed & 0xff) as u8;
|
|
|
|
// Generate the states
|
|
let states: String = generate(seed);
|
|
|
|
log::info!("Rule: {}\n{}", rule, states);
|
|
|
|
// Send toot
|
|
let sent =
|
|
tokio::time::timeout(Duration::from_secs(1), send_status(rule, states).await).await;
|
|
|
|
// Figure out what went wrong
|
|
match sent {
|
|
Ok(result) => match result {
|
|
Ok(response) => {
|
|
if response.status() != 200 {
|
|
log::error!("Send status error code: {}", response.status())
|
|
}
|
|
}
|
|
Err(err) => {
|
|
log::error!("Send status error: {}", err);
|
|
}
|
|
},
|
|
Err(_) => {
|
|
log::error!("Send status timed out!");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
match signal::ctrl_c().await {
|
|
Ok(()) => {}
|
|
Err(err) => {
|
|
log::error!("Unable to listen for shutdown signal: {}", err);
|
|
}
|
|
}
|
|
|
|
// Kill generator
|
|
generator.abort()
|
|
}
|